fix: dark mode shortcuts, Venice priority, browser-in-chat hiding, auto-memory, view toggles CSS, sidebar scroll
Some checks are pending
CI / build-check-test (push) Waiting to run

This commit is contained in:
JAE 2026-03-27 06:34:24 +00:00
parent ef135b6d94
commit 84671d57bf
4 changed files with 206 additions and 11 deletions

View file

@ -261,3 +261,66 @@ button:active, [role="button"]:active {
[class*="AgentInterface"] { [class*="AgentInterface"] {
background: transparent !important; 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);
}

View file

@ -40,21 +40,22 @@ export class KeyboardShortcuts extends LitElement {
override render() { override render() {
if (!this.open) return html``; if (!this.open) return html``;
return html` return html`
<div class="fixed inset-0 z-50 flex items-center justify-center" style="background:rgba(0,0,0,0.5)" @click=${(e: Event) => { if (e.target === e.currentTarget) this.hide(); }}> <div class="fixed inset-0 z-50 flex items-center justify-center" style="background:rgba(0,0,0,0.6);backdrop-filter:blur(4px)" @click=${(e: Event) => { if (e.target === e.currentTarget) this.hide(); }}>
<div class="jae-glass rounded-2xl shadow-2xl p-6 max-w-lg w-full mx-4 border border-border/60 jae-scale-in"> <div class="rounded-2xl shadow-2xl p-6 max-w-lg w-full mx-4 border border-white/10 jae-scale-in"
style="background:var(--color-card, hsl(240 6% 10%));color:var(--color-card-foreground, hsl(0 0% 95%))">
<div class="flex items-center justify-between mb-4"> <div class="flex items-center justify-between mb-4">
<h2 class="text-lg font-bold text-foreground">Keyboard Shortcuts</h2> <h2 class="text-lg font-bold" style="color:var(--color-card-foreground, hsl(0 0% 95%))"> Keyboard Shortcuts</h2>
<button @click=${() => this.hide()} class="text-muted-foreground hover:text-foreground transition-colors text-lg">\u2715</button> <button @click=${() => this.hide()} class="hover:opacity-70 transition-opacity text-lg" style="color:var(--color-muted-foreground, hsl(0 0% 64%))">&times;</button>
</div> </div>
<div class="space-y-4 max-h-[60vh] overflow-y-auto"> <div class="space-y-4 max-h-[60vh] overflow-y-auto pr-1">
${this.shortcuts.map(g => html` ${this.shortcuts.map(g => html`
<div> <div>
<h3 class="text-xs font-semibold text-muted-foreground uppercase tracking-widest mb-2">${g.group}</h3> <h3 class="text-xs font-semibold uppercase tracking-widest mb-2" style="color:var(--color-muted-foreground, hsl(0 0% 64%))">${g.group}</h3>
<div class="space-y-1.5"> <div class="space-y-1">
${g.items.map(s => html` ${g.items.map(s => html`
<div class="flex items-center justify-between py-1.5 px-2 rounded-lg hover:bg-secondary/30 transition-colors duration-150"> <div class="flex items-center justify-between py-1.5 px-3 rounded-lg transition-colors duration-150" style="color:var(--color-card-foreground, hsl(0 0% 95%))" @mouseenter=${(e: Event) => (e.currentTarget as HTMLElement).style.background = "rgba(255,255,255,0.06)"} @mouseleave=${(e: Event) => (e.currentTarget as HTMLElement).style.background = ""}>
<span class="text-sm text-foreground/90">${s.desc}</span> <span class="text-sm">${s.desc}</span>
<kbd class="px-2 py-0.5 rounded-md text-xs font-mono" style="background:rgba(42,42,62,0.8);color:var(--color-foreground);border:1px solid rgba(255,255,255,0.1)">${s.key}</kbd> <kbd class="px-2 py-0.5 rounded-md text-xs font-mono" style="background:rgba(255,255,255,0.08);color:var(--color-card-foreground, hsl(0 0% 90%));border:1px solid rgba(255,255,255,0.12)">${s.key}</kbd>
</div> </div>
`)} `)}
</div> </div>

View file

@ -112,6 +112,120 @@ RULES:
- Be concise and helpful. Use markdown formatting. - Be concise and helpful. Use markdown formatting.
- Never create files or run commands unless explicitly asked to do so.`; - 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<IDBDatabase> {
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<void> {
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<AutoMemory[]> {
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<AutoMemory[]> {
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<string> {
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 ===== // ===== AGENT CREATION =====
async function createAgent(initialState?: AgentState) { async function createAgent(initialState?: AgentState) {
const model = initialState?.model || getModel("venice", "llama-3.3-70b"); 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 // Update mood based on content
const mood = document.querySelector("jae-mood-indicator") as JaeMoodIndicator; const mood = document.querySelector("jae-mood-indicator") as JaeMoodIndicator;
if (mood && msg.role === "assistant" && typeof msg.content === "string") { 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.thinking) classes.push("hide-thinking");
if (!viewState.system) classes.push("hide-system"); if (!viewState.system) classes.push("hide-system");
if (!viewState.timestamps) classes.push("hide-timestamps"); 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(" "); return classes.join(" ");
} }

View file

@ -267,6 +267,10 @@ export class ModelSelector extends DialogBase {
const bIsCurrent = modelsAreEqual(this.currentModel, b.model); const bIsCurrent = modelsAreEqual(this.currentModel, b.model);
if (aIsCurrent && !bIsCurrent) return -1; if (aIsCurrent && !bIsCurrent) return -1;
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); return a.provider.localeCompare(b.provider);
}); });
} }
@ -299,7 +303,14 @@ export class ModelSelector extends DialogBase {
if (this.allowedProviders) { if (this.allowedProviders) {
return Array.from(seen).filter((p) => this.allowedProviders!.has(p)); 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() { private renderProviderTabs() {