fix: 9 web UI bugs - cost tracker, model badge, empty state, provider tabs, memory tools, session save, dark mode, view toggles
Some checks are pending
CI / build-check-test (push) Waiting to run
Some checks are pending
CI / build-check-test (push) Waiting to run
- Cost tracker: fix event type message -> message_end, handle usage.input fallback - Model badge: update immediately on model select via onModelSelect hook - Empty state: hide completely when hasMessages (not after LLM responds) - Provider tabs: add renderProviderTabs() to ModelSelector content + filtering - Memory tools: register memory_save/recall in tool index, export from web-ui, add to createTools - Session save: save before newSession, relax shouldSaveSession to user-only, title fallback - Dark mode: add text-foreground to memory-manager dialog + inputs - View toggles: add tool-message and thinking-block element CSS selectors - Empty state faded: return empty html instead of ghost mascot
This commit is contained in:
parent
63a773184c
commit
92a294a7a2
8 changed files with 55 additions and 24 deletions
|
|
@ -4,6 +4,7 @@
|
|||
/* ============================================================
|
||||
Utility message visibility toggles
|
||||
============================================================ */
|
||||
#chat-wrapper.hide-tool-calls tool-message,
|
||||
#chat-wrapper.hide-tool-calls [data-message-type="tool"],
|
||||
#chat-wrapper.hide-tool-calls [data-tool-call],
|
||||
#chat-wrapper.hide-tool-calls .tool-call-renderer,
|
||||
|
|
@ -11,6 +12,7 @@
|
|||
display: none !important;
|
||||
}
|
||||
|
||||
#chat-wrapper.hide-thinking thinking-block,
|
||||
#chat-wrapper.hide-thinking [data-message-type="thinking"],
|
||||
#chat-wrapper.hide-thinking .thinking-block {
|
||||
display: none !important;
|
||||
|
|
|
|||
|
|
@ -42,11 +42,11 @@ export class CostTracker extends LitElement {
|
|||
this.requestCount = 0;
|
||||
this.modelId = agent.state.model?.id || "";
|
||||
this.unsubscribe = agent.subscribe((event) => {
|
||||
if (event.type === "message" && event.message.role === "assistant") {
|
||||
if (event.type === "message_end" && event.message.role === "assistant") {
|
||||
const msg = event.message as any;
|
||||
if (msg.usage) {
|
||||
this.inputTokens += msg.usage.inputTokens || 0;
|
||||
this.outputTokens += msg.usage.outputTokens || 0;
|
||||
this.inputTokens += msg.usage.inputTokens || msg.usage.input || 0;
|
||||
this.outputTokens += msg.usage.outputTokens || msg.usage.output || 0;
|
||||
this.requestCount += 1;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,13 +23,7 @@ export class JaeEmptyState extends LitElement {
|
|||
if (!this.visible) return html``;
|
||||
|
||||
if (this.faded) {
|
||||
return html`
|
||||
<div class="flex flex-col items-center justify-center h-full select-none"
|
||||
style="pointer-events:none">
|
||||
<img src="/mascot/jae-default.png" alt="JAE"
|
||||
style="width:10rem;height:auto;opacity:0.04;filter:grayscale(40%)" />
|
||||
</div>
|
||||
`;
|
||||
return html``;
|
||||
}
|
||||
|
||||
return html`
|
||||
|
|
|
|||
|
|
@ -114,13 +114,13 @@ export class MemoryManager extends LitElement {
|
|||
<div class="fixed inset-0 z-50 flex items-center justify-center bg-black/50" @click=${(e: Event) => {
|
||||
if (e.target === e.currentTarget) this.hide();
|
||||
}}>
|
||||
<div class="bg-popover border border-border rounded-xl shadow-2xl w-full max-w-2xl mx-4 flex flex-col max-h-[80vh]">
|
||||
<div class="bg-popover text-foreground border border-border rounded-xl shadow-2xl w-full max-w-2xl mx-4 flex flex-col max-h-[80vh]">
|
||||
<div class="flex items-center justify-between px-6 py-4 border-b border-border shrink-0">
|
||||
<h2 class="font-semibold text-lg">🧠 Memory Manager</h2>
|
||||
<button class="text-muted-foreground hover:text-foreground" @click=${() => this.hide()}>✕</button>
|
||||
</div>
|
||||
<div class="px-6 py-3 border-b border-border shrink-0">
|
||||
<input type="text" placeholder="Filter memories..." class="w-full bg-secondary rounded-lg px-3 py-2 text-sm outline-none"
|
||||
<input type="text" placeholder="Filter memories..." class="w-full bg-secondary text-foreground rounded-lg px-3 py-2 text-sm outline-none"
|
||||
.value=${this.filter} @input=${(e: Event) => {
|
||||
this.filter = (e.target as HTMLInputElement).value;
|
||||
}} />
|
||||
|
|
|
|||
|
|
@ -15,13 +15,14 @@ import {
|
|||
SessionListDialog,
|
||||
SessionsStore,
|
||||
SettingsDialog,
|
||||
ModelSelector,
|
||||
SettingsStore,
|
||||
setAppStorage,
|
||||
} from "@jaeswift/jae-web-ui";
|
||||
import { html, render } from "lit";
|
||||
import { Brain, Download, History, Keyboard, Plus, Settings } from "lucide";
|
||||
import "./app.css";
|
||||
import { createImageGenTool, createTTSTool, createWebSearchTool } from "@jaeswift/jae-web-ui";
|
||||
import { createImageGenTool, createMemoryTools, createTTSTool, createWebSearchTool } from "@jaeswift/jae-web-ui";
|
||||
import { icon } from "@mariozechner/mini-lit";
|
||||
import { Button } from "@mariozechner/mini-lit/dist/Button.js";
|
||||
import { Input } from "@mariozechner/mini-lit/dist/Input.js";
|
||||
|
|
@ -252,15 +253,16 @@ const generateTitle = (messages: AgentMessage[]): string => {
|
|||
};
|
||||
|
||||
const shouldSaveSession = (messages: AgentMessage[]): boolean => {
|
||||
const hasUserMsg = messages.some((m: any) => m.role === "user" || m.role === "user-with-attachments");
|
||||
const hasAssistantMsg = messages.some((m: any) => m.role === "assistant");
|
||||
return hasUserMsg && hasAssistantMsg;
|
||||
return messages.some((m: any) => m.role === "user" || m.role === "user-with-attachments");
|
||||
};
|
||||
|
||||
const saveSession = async () => {
|
||||
if (!storage.sessions || !currentSessionId || !agent || !currentTitle) return;
|
||||
if (!storage.sessions || !currentSessionId || !agent) return;
|
||||
const state = agent.state;
|
||||
if (!shouldSaveSession(state.messages)) return;
|
||||
if (!currentTitle) {
|
||||
currentTitle = generateTitle(state.messages) || "Untitled chat";
|
||||
}
|
||||
try {
|
||||
const sessionData = {
|
||||
id: currentSessionId,
|
||||
|
|
@ -338,11 +340,27 @@ const createAgent = async (initialState?: Partial<AgentState>) => {
|
|||
createTools: async (runtimeProvidersFactory: any) => {
|
||||
const replTool = createJavaScriptReplTool();
|
||||
replTool.runtimeProvidersFactory = runtimeProvidersFactory;
|
||||
return [replTool, createWebSearchTool(), createImageGenTool(), createTTSTool()];
|
||||
return [replTool, createWebSearchTool(), createImageGenTool(), createTTSTool(), ...createMemoryTools()];
|
||||
},
|
||||
});
|
||||
costTracker.bindAgent(agent);
|
||||
chatPanel?.setAgent(agent);
|
||||
// Hook: live model badge + immediate empty state hide
|
||||
requestAnimationFrame(() => {
|
||||
if (chatPanel?.agentInterface) {
|
||||
(chatPanel.agentInterface as any).onModelSelect = () => {
|
||||
ModelSelector.open(agent.state.model, (model: any) => {
|
||||
agent.setModel(model);
|
||||
(chatPanel.agentInterface as any).requestUpdate();
|
||||
renderApp();
|
||||
});
|
||||
};
|
||||
(chatPanel.agentInterface as any).onBeforeSend = async () => {
|
||||
hasStarted = true;
|
||||
renderApp();
|
||||
};
|
||||
}
|
||||
});
|
||||
if (!currentSessionId) currentSessionId = crypto.randomUUID();
|
||||
};
|
||||
|
||||
|
|
@ -366,12 +384,19 @@ const loadSession = async (sessionId: string): Promise<boolean> => {
|
|||
return true;
|
||||
};
|
||||
|
||||
const newSession = () => {
|
||||
const newSession = async () => {
|
||||
// Save current session before resetting
|
||||
if (agent && agent.state.messages.length > 0 && currentSessionId) {
|
||||
if (!currentTitle) currentTitle = generateTitle(agent.state.messages) || "Untitled chat";
|
||||
await saveSession();
|
||||
}
|
||||
currentSessionId = undefined;
|
||||
currentTitle = "";
|
||||
isEditingTitle = false;
|
||||
hasStarted = false;
|
||||
createAgent().then(() => renderApp());
|
||||
await createAgent();
|
||||
await refreshSidebar();
|
||||
renderApp();
|
||||
};
|
||||
|
||||
const handleSuggestion = (e: Event) => {
|
||||
|
|
@ -548,9 +573,11 @@ ${sidebar}
|
|||
}}
|
||||
></div>
|
||||
<div class="flex flex-col flex-1 min-w-0 min-h-0 relative">
|
||||
<div class="absolute inset-x-0 top-0 z-10 flex flex-col" style="bottom:130px;pointer-events:${hasMessages ? "none" : "auto"}" @suggestion=${handleSuggestion}>
|
||||
<jae-empty-state .faded=${hasMessages} style="display:flex;flex-direction:column;flex:1;width:100%;min-height:0"></jae-empty-state>
|
||||
${!hasMessages ? html`
|
||||
<div class="absolute inset-x-0 top-0 z-10 flex flex-col" style="bottom:130px" @suggestion=${handleSuggestion}>
|
||||
<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" >
|
||||
${chatPanel}
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -246,6 +246,9 @@ export class ModelSelector extends DialogBase {
|
|||
if (this.filterVision) {
|
||||
filteredModels = filteredModels.filter(({ model }) => model.input.includes("image"));
|
||||
}
|
||||
if (this.filterProvider) {
|
||||
filteredModels = filteredModels.filter(({ provider }) => provider === this.filterProvider);
|
||||
}
|
||||
|
||||
// Sort: when not searching, current model first then by provider.
|
||||
// When searching, preserve the score-based order from above,
|
||||
|
|
@ -376,6 +379,8 @@ export class ModelSelector extends DialogBase {
|
|||
children: html`<span class="inline-flex items-center gap-1">${icon(ImageIcon, "sm")} ${i18n("Vision")}</span>`,
|
||||
})}
|
||||
</div>
|
||||
|
||||
${this.renderProviderTabs()}
|
||||
</div>
|
||||
|
||||
<!-- Scrollable model list -->
|
||||
|
|
|
|||
|
|
@ -118,6 +118,7 @@ export type { ToolRenderer, ToolRenderResult } from "./tools/types.js";
|
|||
export { createTTSTool, ttsTool } from "./tools/voice-tts.js";
|
||||
// Venice / community tools
|
||||
export { createWebSearchTool, webSearchTool } from "./tools/web-search.js";
|
||||
export { createMemoryTools, saveMemoryTool, recallMemoryTool } from "./tools/memory-tool.js";
|
||||
export type { Attachment } from "./utils/attachment-utils.js";
|
||||
// Utils
|
||||
export { loadAttachment } from "./utils/attachment-utils.js";
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import type { ToolResultMessage } from "@jaeswift/jae-ai";
|
||||
import "./javascript-repl.js"; // Auto-registers the renderer
|
||||
import "./extract-document.js"; // Auto-registers the renderer
|
||||
import "./extract-document.js";
|
||||
import "./memory-tool.js"; // Auto-registers the renderer
|
||||
import { getToolRenderer, registerToolRenderer } from "./renderer-registry.js";
|
||||
import { BashRenderer } from "./renderers/BashRenderer.js";
|
||||
import { DefaultRenderer } from "./renderers/DefaultRenderer.js";
|
||||
|
|
@ -48,3 +49,4 @@ export { getToolRenderer, registerToolRenderer };
|
|||
export { createImageGenTool, type ImageGenDetails, imageGenTool } from "./image-gen.js";
|
||||
export { createTTSTool, type TTSDetails, ttsTool } from "./voice-tts.js";
|
||||
export { createWebSearchTool, type WebSearchDetails, type WebSearchResult, webSearchTool } from "./web-search.js";
|
||||
export { createMemoryTools, saveMemoryTool, recallMemoryTool } from "./memory-tool.js";
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue