diff --git a/packages/web-ui/example/src/app.css b/packages/web-ui/example/src/app.css index 7535511..b7fb68b 100644 --- a/packages/web-ui/example/src/app.css +++ b/packages/web-ui/example/src/app.css @@ -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; diff --git a/packages/web-ui/example/src/components/cost-tracker.ts b/packages/web-ui/example/src/components/cost-tracker.ts index 5e6da19..25e84fe 100644 --- a/packages/web-ui/example/src/components/cost-tracker.ts +++ b/packages/web-ui/example/src/components/cost-tracker.ts @@ -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; } } diff --git a/packages/web-ui/example/src/components/empty-state.ts b/packages/web-ui/example/src/components/empty-state.ts index 406b6f4..41987ff 100644 --- a/packages/web-ui/example/src/components/empty-state.ts +++ b/packages/web-ui/example/src/components/empty-state.ts @@ -23,13 +23,7 @@ export class JaeEmptyState extends LitElement { if (!this.visible) return html``; if (this.faded) { - return html` -
- JAE -
- `; + return html``; } return html` diff --git a/packages/web-ui/example/src/components/memory-manager.ts b/packages/web-ui/example/src/components/memory-manager.ts index c9b9aed..1e36640 100644 --- a/packages/web-ui/example/src/components/memory-manager.ts +++ b/packages/web-ui/example/src/components/memory-manager.ts @@ -114,13 +114,13 @@ export class MemoryManager extends LitElement {
{ if (e.target === e.currentTarget) this.hide(); }}> -
+

🧠 Memory Manager

- { this.filter = (e.target as HTMLInputElement).value; }} /> diff --git a/packages/web-ui/example/src/main.ts b/packages/web-ui/example/src/main.ts index 322aee1..b493d4f 100644 --- a/packages/web-ui/example/src/main.ts +++ b/packages/web-ui/example/src/main.ts @@ -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) => { 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 => { 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} }} >
-
- +${!hasMessages ? html` +
+
+` : html``}
${chatPanel}
diff --git a/packages/web-ui/src/dialogs/ModelSelector.ts b/packages/web-ui/src/dialogs/ModelSelector.ts index f608120..ade5c8f 100644 --- a/packages/web-ui/src/dialogs/ModelSelector.ts +++ b/packages/web-ui/src/dialogs/ModelSelector.ts @@ -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,7 +379,9 @@ export class ModelSelector extends DialogBase { children: html`${icon(ImageIcon, "sm")} ${i18n("Vision")}`, })}
-
+ + ${this.renderProviderTabs()} +
diff --git a/packages/web-ui/src/index.ts b/packages/web-ui/src/index.ts index 952b15d..b3c18b3 100644 --- a/packages/web-ui/src/index.ts +++ b/packages/web-ui/src/index.ts @@ -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"; diff --git a/packages/web-ui/src/tools/index.ts b/packages/web-ui/src/tools/index.ts index a9b727d..8cb94bd 100644 --- a/packages/web-ui/src/tools/index.ts +++ b/packages/web-ui/src/tools/index.ts @@ -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";