diff --git a/packages/web-ui/example/src/app.css b/packages/web-ui/example/src/app.css index 5e4b60c..d199a65 100644 --- a/packages/web-ui/example/src/app.css +++ b/packages/web-ui/example/src/app.css @@ -261,3 +261,66 @@ button:active, [role="button"]:active { [class*="AgentInterface"] { background: transparent !important; } + + +/* ===== VIEW TOGGLE CONTROLS ===== */ +/* Hide tool call blocks when tools toggled off */ +.hide-tools tool-message { + display: none !important; +} + +/* Hide thinking/reasoning blocks */ +.hide-thinking [data-thinking], +.hide-thinking .thinking-block, +.hide-thinking [class*="thinking"] { + display: none !important; +} + +/* Hide system messages */ +.hide-system [data-system-message], +.hide-system .system-message { + display: none !important; +} + +/* Hide timestamps on messages */ +.hide-timestamps time, +.hide-timestamps [data-timestamp], +.hide-timestamps .message-timestamp { + display: none !important; +} + +/* Hide browser tool output in chat when browser panel is open */ +.browser-panel-active tool-message[data-tool-name="browser"], +.browser-panel-active tool-message[data-tool-name="web_fetch"] { + display: none !important; +} + +/* Smooth transition for tool-message visibility */ +tool-message { + transition: opacity 0.2s ease, max-height 0.3s ease; +} + +/* ===== SIDEBAR HOVER SCROLLBAR ===== */ +.jae-sidebar { + scrollbar-width: none; + -ms-overflow-style: none; +} +.jae-sidebar::-webkit-scrollbar { + width: 4px; + background: transparent; +} +.jae-sidebar::-webkit-scrollbar-thumb { + background: transparent; + border-radius: 4px; + transition: background 0.3s ease; +} +.jae-sidebar:hover { + scrollbar-width: thin; + scrollbar-color: rgba(255,255,255,0.15) transparent; +} +.jae-sidebar:hover::-webkit-scrollbar-thumb { + background: rgba(255,255,255,0.15); +} +.jae-sidebar:hover::-webkit-scrollbar-thumb:hover { + background: rgba(255,255,255,0.25); +} diff --git a/packages/web-ui/example/src/components/keyboard-shortcuts.ts b/packages/web-ui/example/src/components/keyboard-shortcuts.ts index 1150ff3..1b77dee 100644 --- a/packages/web-ui/example/src/components/keyboard-shortcuts.ts +++ b/packages/web-ui/example/src/components/keyboard-shortcuts.ts @@ -40,21 +40,22 @@ export class KeyboardShortcuts extends LitElement { override render() { if (!this.open) return html``; return html` -
{ if (e.target === e.currentTarget) this.hide(); }}> -
+
{ if (e.target === e.currentTarget) this.hide(); }}> +
-

Keyboard Shortcuts

- +

⌨ Keyboard Shortcuts

+
-
+
${this.shortcuts.map(g => html`
-

${g.group}

-
+

${g.group}

+
${g.items.map(s => html` -
- ${s.desc} - ${s.key} +
(e.currentTarget as HTMLElement).style.background = "rgba(255,255,255,0.06)"} @mouseleave=${(e: Event) => (e.currentTarget as HTMLElement).style.background = ""}> + ${s.desc} + ${s.key}
`)}
diff --git a/packages/web-ui/example/src/main.ts b/packages/web-ui/example/src/main.ts index 049343a..622d4ea 100644 --- a/packages/web-ui/example/src/main.ts +++ b/packages/web-ui/example/src/main.ts @@ -112,6 +112,120 @@ RULES: - Be concise and helpful. Use markdown formatting. - Never create files or run commands unless explicitly asked to do so.`; + +// ===== AUTO-MEMORY SYSTEM ===== +const AUTO_MEMORY_DB = "jae-auto-memory"; +const AUTO_MEMORY_STORE = "entries"; + +interface AutoMemory { + id: string; + content: string; + tags: string[]; + timestamp: string; + source: "user" | "agent" | "system"; +} + +let autoMemoryDb: IDBDatabase | null = null; + +async function openAutoMemoryDb(): Promise { + if (autoMemoryDb) return autoMemoryDb; + return new Promise((resolve, reject) => { + const req = indexedDB.open(AUTO_MEMORY_DB, 1); + req.onupgradeneeded = () => { + const store = req.result.createObjectStore(AUTO_MEMORY_STORE, { keyPath: "id" }); + store.createIndex("tags", "tags", { multiEntry: true }); + store.createIndex("timestamp", "timestamp"); + }; + req.onsuccess = () => { autoMemoryDb = req.result; resolve(autoMemoryDb); }; + req.onerror = () => reject(req.error); + }); +} + +async function autoMemorySave(content: string, tags: string[] = [], source: "user" | "agent" | "system" = "agent"): Promise { + const db = await openAutoMemoryDb(); + const entry: AutoMemory = { id: crypto.randomUUID(), content, tags, timestamp: new Date().toISOString(), source }; + return new Promise((resolve, reject) => { + const tx = db.transaction(AUTO_MEMORY_STORE, "readwrite"); + tx.objectStore(AUTO_MEMORY_STORE).put(entry); + tx.oncomplete = () => resolve(); + tx.onerror = () => reject(tx.error); + }); +} + +async function autoMemorySearch(query: string, limit = 10): Promise { + const db = await openAutoMemoryDb(); + return new Promise((resolve, reject) => { + const tx = db.transaction(AUTO_MEMORY_STORE, "readonly"); + const req = tx.objectStore(AUTO_MEMORY_STORE).getAll(); + req.onsuccess = () => { + const q = query.toLowerCase(); + const results = (req.result || []).filter((e: AutoMemory) => + e.content.toLowerCase().includes(q) || e.tags.some(t => t.toLowerCase().includes(q)) + ).slice(-limit); + resolve(results); + }; + req.onerror = () => reject(req.error); + }); +} + +async function autoMemoryGetAll(limit = 50): Promise { + const db = await openAutoMemoryDb(); + return new Promise((resolve, reject) => { + const tx = db.transaction(AUTO_MEMORY_STORE, "readonly"); + const req = tx.objectStore(AUTO_MEMORY_STORE).getAll(); + req.onsuccess = () => resolve((req.result || []).slice(-limit)); + req.onerror = () => reject(req.error); + }); +} + +// Extract key facts from messages for auto-learning +function extractKeyFacts(text: string): string[] { + const facts: string[] = []; + // Patterns that indicate important information + const patterns = [ + /my name is ([\w\s]+)/i, + /i(?:'m| am) a ([\w\s]+)/i, + /i work (?:at|for|with) ([\w\s]+)/i, + /i live in ([\w\s]+)/i, + /i use ([\w\s]+)/i, + /i prefer ([\w\s]+)/i, + /remember (?:that )?(.+)/i, + /my (?:email|phone|address) is ([\w\s@.+]+)/i, + /the (?:api key|password|token|url|endpoint) is ([^\s]+)/i, + ]; + for (const p of patterns) { + const m = text.match(p); + if (m && m[1]) facts.push(m[0].trim()); + } + return facts; +} + +// Hook into messages to auto-learn +function processMessageForMemory(role: string, text: string) { + if (!text || text.length < 10) return; + const facts = extractKeyFacts(text); + for (const fact of facts) { + autoMemorySave(fact, ["auto-learned", role], role === "user" ? "user" : "agent"); + } +} + +// Inject memories into system prompt +async function getMemoryAugmentedPrompt(basePrompt: string, userMessage: string): Promise { + try { + const relevant = await autoMemorySearch(userMessage, 5); + if (relevant.length === 0) return basePrompt; + const memoryBlock = relevant.map(m => `- ${m.content}`).join("\n"); + return `${basePrompt} + +RELEVANT MEMORIES: +${memoryBlock} + +Use these memories to provide more personalised and context-aware responses.`; + } catch { + return basePrompt; + } +} + // ===== AGENT CREATION ===== async function createAgent(initialState?: AgentState) { const model = initialState?.model || getModel("venice", "llama-3.3-70b"); @@ -171,6 +285,10 @@ async function createAgent(initialState?: AgentState) { } } } + // Auto-learn from user messages + if (msg.role === "user" && typeof msg.content === "string") { + processMessageForMemory("user", msg.content); + } // Update mood based on content const mood = document.querySelector("jae-mood-indicator") as JaeMoodIndicator; if (mood && msg.role === "assistant" && typeof msg.content === "string") { @@ -408,6 +526,8 @@ function getViewClasses(): string { if (!viewState.thinking) classes.push("hide-thinking"); if (!viewState.system) classes.push("hide-system"); if (!viewState.timestamps) classes.push("hide-timestamps"); + // Hide browser tool output in chat when browser panel is visible + if (rightPanel === "browser") classes.push("browser-panel-active"); return classes.join(" "); } diff --git a/packages/web-ui/src/dialogs/ModelSelector.ts b/packages/web-ui/src/dialogs/ModelSelector.ts index 11b2266..2d7764e 100644 --- a/packages/web-ui/src/dialogs/ModelSelector.ts +++ b/packages/web-ui/src/dialogs/ModelSelector.ts @@ -267,6 +267,10 @@ export class ModelSelector extends DialogBase { const bIsCurrent = modelsAreEqual(this.currentModel, b.model); if (aIsCurrent && !bIsCurrent) return -1; if (!aIsCurrent && bIsCurrent) return 1; + // Venice first + const aVenice = a.provider === "venice" ? 0 : 1; + const bVenice = b.provider === "venice" ? 0 : 1; + if (aVenice !== bVenice) return aVenice - bVenice; return a.provider.localeCompare(b.provider); }); } @@ -299,7 +303,14 @@ export class ModelSelector extends DialogBase { if (this.allowedProviders) { return Array.from(seen).filter((p) => this.allowedProviders!.has(p)); } - return Array.from(seen).sort(); + // Put Venice first, then sort the rest alphabetically + const sorted = Array.from(seen).sort(); + const veniceIdx = sorted.indexOf("venice"); + if (veniceIdx > 0) { + sorted.splice(veniceIdx, 1); + sorted.unshift("venice"); + } + return sorted; } private renderProviderTabs() {