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() {