fix: model badge, empty-state cover, mascot centering, resizable panels, sidebar history
Some checks are pending
CI / build-check-test (push) Waiting to run

This commit is contained in:
JAE 2026-03-26 20:52:23 +00:00
parent 97cef8b4d3
commit 1514fabd50
2 changed files with 29 additions and 5 deletions

View file

@ -65,7 +65,7 @@ export class JaeSessionSidebar extends LitElement {
const sorted = [...pinned, ...rest];
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">
<span class="text-[11px] font-semibold text-muted-foreground uppercase tracking-widest">Chats</span>
<button @click=${() => this.onNewSession?.()}

View file

@ -81,6 +81,9 @@ let currentTitle = "";
let isEditingTitle = false;
let agent: Agent;
let rightPanel: 'none' | 'terminal' | 'browser' = 'none';
let sidebarWidth = 220;
let rightPanelWidth = 480;
let hasStarted = false;
let terminalPanel: JaeTerminalPanel | null = null;
let browserPanel: JaeBrowserPanel | null = null;
let chatPanel: ChatPanel;
@ -225,6 +228,7 @@ const createAgent = async (initialState?: Partial<AgentState>) => {
},
getProviderApiKey: async (provider: string) => providerKeys.get(provider),
onStateChange: async (state: AgentState, prevState: AgentState | undefined) => {
if (state.messages.length > 0) hasStarted = true;
if (prevState?.messages.length !== state.messages.length) {
if (!currentTitle) {
const generated = generateTitle(state.messages);
@ -254,6 +258,7 @@ const loadSession = async (sessionId: string): Promise<boolean> => {
const sessionData = await storage.sessions.get(sessionId);
if (!sessionData) return false;
currentSessionId = sessionId;
hasStarted = sessionData.messages.length > 0;
const metadata = await storage.sessions.getMetadata(sessionId);
currentTitle = metadata?.title || "";
await createAgent({
@ -270,6 +275,7 @@ const newSession = () => {
currentSessionId = undefined;
currentTitle = "";
isEditingTitle = false;
hasStarted = false;
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 app = document.getElementById("app");
if (!app) return;
const hasMessages = agent && agent.state.messages.length > 0;
const hasMessages = hasStarted || !!(agent?.state?.messages?.length);
render(html`
<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">
@ -310,7 +322,7 @@ const renderApp = () => {
? 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`<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 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 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}
</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">
${!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}>
<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>
` : html``}
<div id="chat-wrapper" class="flex flex-col flex-1 min-h-0" >
@ -339,7 +358,12 @@ ${chatPanel}
</div>
</div>
${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">
<button class="text-xs px-2 py-1 rounded ${
rightPanel === 'terminal'