fix: model badge, empty-state cover, mascot centering, resizable panels, sidebar history
Some checks are pending
CI / build-check-test (push) Waiting to run
Some checks are pending
CI / build-check-test (push) Waiting to run
This commit is contained in:
parent
97cef8b4d3
commit
1514fabd50
2 changed files with 29 additions and 5 deletions
|
|
@ -65,7 +65,7 @@ export class JaeSessionSidebar extends LitElement {
|
||||||
const sorted = [...pinned, ...rest];
|
const sorted = [...pinned, ...rest];
|
||||||
|
|
||||||
return html`
|
return html`
|
||||||
<div class="flex flex-col h-full border-r border-border bg-background shrink-0" style="width:200px">
|
<div class="flex flex-col h-full border-r border-border bg-background shrink-0" style="width:100%">
|
||||||
<div class="flex items-center justify-between px-3 py-2 border-b border-border shrink-0">
|
<div class="flex items-center justify-between px-3 py-2 border-b border-border shrink-0">
|
||||||
<span class="text-[11px] font-semibold text-muted-foreground uppercase tracking-widest">Chats</span>
|
<span class="text-[11px] font-semibold text-muted-foreground uppercase tracking-widest">Chats</span>
|
||||||
<button @click=${() => this.onNewSession?.()}
|
<button @click=${() => this.onNewSession?.()}
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,9 @@ let currentTitle = "";
|
||||||
let isEditingTitle = false;
|
let isEditingTitle = false;
|
||||||
let agent: Agent;
|
let agent: Agent;
|
||||||
let rightPanel: 'none' | 'terminal' | 'browser' = 'none';
|
let rightPanel: 'none' | 'terminal' | 'browser' = 'none';
|
||||||
|
let sidebarWidth = 220;
|
||||||
|
let rightPanelWidth = 480;
|
||||||
|
let hasStarted = false;
|
||||||
let terminalPanel: JaeTerminalPanel | null = null;
|
let terminalPanel: JaeTerminalPanel | null = null;
|
||||||
let browserPanel: JaeBrowserPanel | null = null;
|
let browserPanel: JaeBrowserPanel | null = null;
|
||||||
let chatPanel: ChatPanel;
|
let chatPanel: ChatPanel;
|
||||||
|
|
@ -225,6 +228,7 @@ const createAgent = async (initialState?: Partial<AgentState>) => {
|
||||||
},
|
},
|
||||||
getProviderApiKey: async (provider: string) => providerKeys.get(provider),
|
getProviderApiKey: async (provider: string) => providerKeys.get(provider),
|
||||||
onStateChange: async (state: AgentState, prevState: AgentState | undefined) => {
|
onStateChange: async (state: AgentState, prevState: AgentState | undefined) => {
|
||||||
|
if (state.messages.length > 0) hasStarted = true;
|
||||||
if (prevState?.messages.length !== state.messages.length) {
|
if (prevState?.messages.length !== state.messages.length) {
|
||||||
if (!currentTitle) {
|
if (!currentTitle) {
|
||||||
const generated = generateTitle(state.messages);
|
const generated = generateTitle(state.messages);
|
||||||
|
|
@ -254,6 +258,7 @@ const loadSession = async (sessionId: string): Promise<boolean> => {
|
||||||
const sessionData = await storage.sessions.get(sessionId);
|
const sessionData = await storage.sessions.get(sessionId);
|
||||||
if (!sessionData) return false;
|
if (!sessionData) return false;
|
||||||
currentSessionId = sessionId;
|
currentSessionId = sessionId;
|
||||||
|
hasStarted = sessionData.messages.length > 0;
|
||||||
const metadata = await storage.sessions.getMetadata(sessionId);
|
const metadata = await storage.sessions.getMetadata(sessionId);
|
||||||
currentTitle = metadata?.title || "";
|
currentTitle = metadata?.title || "";
|
||||||
await createAgent({
|
await createAgent({
|
||||||
|
|
@ -270,6 +275,7 @@ const newSession = () => {
|
||||||
currentSessionId = undefined;
|
currentSessionId = undefined;
|
||||||
currentTitle = "";
|
currentTitle = "";
|
||||||
isEditingTitle = false;
|
isEditingTitle = false;
|
||||||
|
hasStarted = false;
|
||||||
createAgent().then(() => renderApp());
|
createAgent().then(() => renderApp());
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -296,10 +302,16 @@ const handleSuggestion = (e: Event) => {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const getModelLabel = (): string | null => {
|
||||||
|
if (!agent?.state?.model) return null;
|
||||||
|
const m = agent.state.model as any;
|
||||||
|
return m.name || m.id || null;
|
||||||
|
};
|
||||||
|
|
||||||
const renderApp = () => {
|
const renderApp = () => {
|
||||||
const app = document.getElementById("app");
|
const app = document.getElementById("app");
|
||||||
if (!app) return;
|
if (!app) return;
|
||||||
const hasMessages = agent && agent.state.messages.length > 0;
|
const hasMessages = hasStarted || !!(agent?.state?.messages?.length);
|
||||||
render(html`
|
render(html`
|
||||||
<div class="w-full h-screen flex flex-col bg-background text-foreground overflow-hidden">
|
<div class="w-full h-screen flex flex-col bg-background text-foreground overflow-hidden">
|
||||||
<div class="flex items-center justify-between border-b border-border shrink-0" style="height:44px">
|
<div class="flex items-center justify-between border-b border-border shrink-0" style="height:44px">
|
||||||
|
|
@ -310,7 +322,7 @@ const renderApp = () => {
|
||||||
? isEditingTitle
|
? isEditingTitle
|
||||||
? html`<div class="flex items-center gap-2">${Input({ type: "text", value: currentTitle, className: "text-sm w-64", onChange: async (e: Event) => { const v = (e.target as HTMLInputElement).value.trim(); if (v && v !== currentTitle && storage.sessions && currentSessionId) { await storage.sessions.updateTitle(currentSessionId, v); currentTitle = v; await refreshSidebar(); } isEditingTitle = false; renderApp(); }, onKeyDown: async (e: KeyboardEvent) => { if (e.key === "Enter") { const v = (e.target as HTMLInputElement).value.trim(); if (v && v !== currentTitle && storage.sessions && currentSessionId) { await storage.sessions.updateTitle(currentSessionId, v); currentTitle = v; await refreshSidebar(); } isEditingTitle = false; renderApp(); } else if (e.key === "Escape") { isEditingTitle = false; renderApp(); } } })}</div>`
|
? html`<div class="flex items-center gap-2">${Input({ type: "text", value: currentTitle, className: "text-sm w-64", onChange: async (e: Event) => { const v = (e.target as HTMLInputElement).value.trim(); if (v && v !== currentTitle && storage.sessions && currentSessionId) { await storage.sessions.updateTitle(currentSessionId, v); currentTitle = v; await refreshSidebar(); } isEditingTitle = false; renderApp(); }, onKeyDown: async (e: KeyboardEvent) => { if (e.key === "Enter") { const v = (e.target as HTMLInputElement).value.trim(); if (v && v !== currentTitle && storage.sessions && currentSessionId) { await storage.sessions.updateTitle(currentSessionId, v); currentTitle = v; await refreshSidebar(); } isEditingTitle = false; renderApp(); } else if (e.key === "Escape") { isEditingTitle = false; renderApp(); } } })}</div>`
|
||||||
: html`<button class="px-2 py-1 text-sm text-foreground hover:bg-secondary rounded transition-colors max-w-xs truncate" @click=${() => { isEditingTitle = true; renderApp(); requestAnimationFrame(() => { const inp = app.querySelector('input[type="text"]') as HTMLInputElement; if (inp) { inp.focus(); inp.select(); } }); }} title="Click to edit">${currentTitle}</button>`
|
: html`<button class="px-2 py-1 text-sm text-foreground hover:bg-secondary rounded transition-colors max-w-xs truncate" @click=${() => { isEditingTitle = true; renderApp(); requestAnimationFrame(() => { const inp = app.querySelector('input[type="text"]') as HTMLInputElement; if (inp) { inp.focus(); inp.select(); } }); }} title="Click to edit">${currentTitle}</button>`
|
||||||
: html`<div class="flex items-center gap-2"><img src="/mascot/jae-default.png" alt="JAE" class="w-7 h-auto header-logo cursor-pointer" /><span class="text-base font-semibold text-foreground">JAE</span></div>`
|
: html`<div class="flex items-center gap-2"><img src="/mascot/jae-default.png" alt="JAE" class="w-7 h-auto header-logo cursor-pointer" /><span class="text-base font-semibold text-foreground">JAE</span>${getModelLabel() ? html`<span class="ml-1 text-[11px] text-muted-foreground bg-muted/80 px-1.5 py-0.5 rounded font-mono truncate max-w-[180px]" title="${getModelLabel()}">${getModelLabel()}</span>` : html``}</div>`
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="flex items-center gap-1 px-2">
|
<div class="flex items-center gap-1 px-2">
|
||||||
|
|
@ -327,11 +339,18 @@ ${Button({ variant: "ghost", size: "sm", children: icon(Settings, "sm"), onClick
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex flex-1 min-h-0 overflow-hidden">
|
<div class="flex flex-1 min-h-0 overflow-hidden">
|
||||||
|
<div id="sidebar-wrap" style="width:${sidebarWidth}px;min-width:150px;max-width:420px;flex-shrink:0;display:flex;flex-direction:column;overflow:hidden;transition:width 0.05s">
|
||||||
${sidebar}
|
${sidebar}
|
||||||
|
</div>
|
||||||
|
<div id="sb-resize" style="width:5px;cursor:col-resize;flex-shrink:0;background:transparent;z-index:10;transition:background 0.15s"
|
||||||
|
@mousedown=${(e: MouseEvent) => { e.preventDefault(); const sx=e.clientX,sw=sidebarWidth; const mv=(me: MouseEvent)=>{sidebarWidth=Math.max(150,Math.min(420,sw+me.clientX-sx));const w=document.getElementById("sidebar-wrap");if(w)w.style.width=sidebarWidth+"px";}; const up=()=>{document.removeEventListener("mousemove",mv);document.removeEventListener("mouseup",up);renderApp();}; document.addEventListener("mousemove",mv);document.addEventListener("mouseup",up); }}
|
||||||
|
@mouseenter=${(e: Event)=>{(e.currentTarget as HTMLElement).style.background="rgba(128,128,128,0.4)"}}
|
||||||
|
@mouseleave=${(e: Event)=>{(e.currentTarget as HTMLElement).style.background="transparent"}}
|
||||||
|
></div>
|
||||||
<div class="flex flex-col flex-1 min-w-0 min-h-0 relative">
|
<div class="flex flex-col flex-1 min-w-0 min-h-0 relative">
|
||||||
${!hasMessages ? html`
|
${!hasMessages ? html`
|
||||||
<div class="absolute inset-x-0 top-0 z-10 flex flex-col overflow-y-auto bg-background" style="bottom:130px" @suggestion=${handleSuggestion}>
|
<div class="absolute inset-x-0 top-0 z-10 flex flex-col overflow-y-auto bg-background" style="bottom:130px" @suggestion=${handleSuggestion}>
|
||||||
<jae-empty-state></jae-empty-state>
|
<jae-empty-state style="display:flex;flex-direction:column;flex:1;width:100%;min-height:0"></jae-empty-state>
|
||||||
</div>
|
</div>
|
||||||
` : html``}
|
` : html``}
|
||||||
<div id="chat-wrapper" class="flex flex-col flex-1 min-h-0" >
|
<div id="chat-wrapper" class="flex flex-col flex-1 min-h-0" >
|
||||||
|
|
@ -339,7 +358,12 @@ ${chatPanel}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
${rightPanel !== 'none' ? html`
|
${rightPanel !== 'none' ? html`
|
||||||
<div class="flex flex-col border-l border-border" style="width:480px;min-width:320px;max-width:600px">
|
<div id="rp-resize" style="width:5px;cursor:col-resize;flex-shrink:0;background:transparent;z-index:10;transition:background 0.15s"
|
||||||
|
@mousedown=${(e: MouseEvent) => { e.preventDefault(); const sx=e.clientX,sw=rightPanelWidth; const mv=(me: MouseEvent)=>{rightPanelWidth=Math.max(280,Math.min(800,sw-(me.clientX-sx)));const p=document.getElementById("right-panel");if(p)p.style.width=rightPanelWidth+"px";}; const up=()=>{document.removeEventListener("mousemove",mv);document.removeEventListener("mouseup",up);renderApp();}; document.addEventListener("mousemove",mv);document.addEventListener("mouseup",up); }}
|
||||||
|
@mouseenter=${(e: Event)=>{(e.currentTarget as HTMLElement).style.background="rgba(128,128,128,0.4)"}}
|
||||||
|
@mouseleave=${(e: Event)=>{(e.currentTarget as HTMLElement).style.background="transparent"}}
|
||||||
|
></div>
|
||||||
|
<div id="right-panel" class="flex flex-col border-l border-border" style="width:${rightPanelWidth}px;min-width:280px;max-width:800px;flex-shrink:0">
|
||||||
<div class="flex items-center gap-1 px-2 shrink-0 border-b border-border bg-muted/20" style="height:36px">
|
<div class="flex items-center gap-1 px-2 shrink-0 border-b border-border bg-muted/20" style="height:36px">
|
||||||
<button class="text-xs px-2 py-1 rounded ${
|
<button class="text-xs px-2 py-1 rounded ${
|
||||||
rightPanel === 'terminal'
|
rightPanel === 'terminal'
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue