diff --git a/packages/web-ui/example/public/mascot/jae-camo.png b/packages/web-ui/example/public/mascot/jae-camo.png
new file mode 100644
index 0000000..d8cb7b4
Binary files /dev/null and b/packages/web-ui/example/public/mascot/jae-camo.png differ
diff --git a/packages/web-ui/example/public/mascot/jae-default.png b/packages/web-ui/example/public/mascot/jae-default.png
new file mode 100644
index 0000000..deeadba
Binary files /dev/null and b/packages/web-ui/example/public/mascot/jae-default.png differ
diff --git a/packages/web-ui/example/public/mascot/jae-fire.png b/packages/web-ui/example/public/mascot/jae-fire.png
new file mode 100644
index 0000000..89f3f81
Binary files /dev/null and b/packages/web-ui/example/public/mascot/jae-fire.png differ
diff --git a/packages/web-ui/example/public/mascot/jae-point-self.png b/packages/web-ui/example/public/mascot/jae-point-self.png
new file mode 100644
index 0000000..a5746a7
Binary files /dev/null and b/packages/web-ui/example/public/mascot/jae-point-self.png differ
diff --git a/packages/web-ui/example/public/mascot/jae-point-up.png b/packages/web-ui/example/public/mascot/jae-point-up.png
new file mode 100644
index 0000000..c84da13
Binary files /dev/null and b/packages/web-ui/example/public/mascot/jae-point-up.png differ
diff --git a/packages/web-ui/example/src/app.css b/packages/web-ui/example/src/app.css
index 695386b..7535511 100644
--- a/packages/web-ui/example/src/app.css
+++ b/packages/web-ui/example/src/app.css
@@ -1 +1,60 @@
@import "../../dist/app.css";
+
+
+/* ============================================================
+ Utility message visibility toggles
+ ============================================================ */
+#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,
+#chat-wrapper.hide-tool-calls .tool-result-renderer {
+ display: none !important;
+}
+
+#chat-wrapper.hide-thinking [data-message-type="thinking"],
+#chat-wrapper.hide-thinking .thinking-block {
+ display: none !important;
+}
+
+#chat-wrapper.hide-system-msgs [data-message-type="system"],
+#chat-wrapper.hide-system-msgs [data-message-type="system-notification"],
+#chat-wrapper.hide-system-msgs .system-notification {
+ display: none !important;
+}
+
+#chat-wrapper.hide-timestamps .message-timestamp,
+#chat-wrapper.hide-timestamps [data-timestamp] {
+ display: none !important;
+}
+
+/* ============================================================
+ Empty state mascot floating animation
+ ============================================================ */
+@keyframes jae-float {
+ 0%, 100% { transform: translateY(0px) rotate(0deg); }
+ 33% { transform: translateY(-10px) rotate(-1deg); }
+ 66% { transform: translateY(-5px) rotate(1deg); }
+}
+
+jae-empty-state img {
+ animation: jae-float 3.5s ease-in-out infinite;
+ filter: drop-shadow(0 12px 32px rgba(255, 100, 0, 0.25));
+}
+
+/* Suggestion chips hover glow */
+jae-empty-state button:hover {
+ box-shadow: 0 0 0 1px rgba(255, 100, 0, 0.3), 0 4px 16px rgba(255, 100, 0, 0.1);
+}
+
+/* ============================================================
+ Header mascot wobble on hover
+ ============================================================ */
+@keyframes jae-wobble {
+ 0%, 100% { transform: rotate(0deg); }
+ 25% { transform: rotate(-8deg); }
+ 75% { transform: rotate(8deg); }
+}
+
+.header-logo:hover {
+ animation: jae-wobble 0.4s ease-in-out;
+}
diff --git a/packages/web-ui/example/src/components/empty-state.ts b/packages/web-ui/example/src/components/empty-state.ts
new file mode 100644
index 0000000..76c5bf8
--- /dev/null
+++ b/packages/web-ui/example/src/components/empty-state.ts
@@ -0,0 +1,53 @@
+import { LitElement, html } from "lit";
+import { customElement, property } from "lit/decorators.js";
+
+@customElement("jae-empty-state")
+export class JaeEmptyState extends LitElement {
+ @property({ type: Boolean }) visible = true;
+
+ protected override createRenderRoot() { return this; }
+
+ private _suggestions = [
+ { icon: "💻", text: "Write me a TypeScript function that debounces API calls" },
+ { icon: "🔍", text: "Search the web for the latest news on AI coding agents" },
+ { icon: "🖼️", text: "Generate an image of a black dragon breathing fire" },
+ { icon: "📝", text: "Explain how async/await works in JavaScript" },
+ { icon: "🔧", text: "Help me debug this code and explain the issue" },
+ { icon: "📊", text: "Create a Mermaid diagram of a REST API flow" },
+ ];
+
+ override render() {
+ if (!this.visible) return html``;
+ return html`
+
+
+
+

+
+
+
+
Hey, I'm JAE
+
+ Your AI coding agent. I can write code, search the web, generate images, and a whole lot more.
+
+
+
+
+ ${this._suggestions.map(s => html`
+
+ `)}
+
+
+ `;
+ }
+}
diff --git a/packages/web-ui/example/src/components/utility-toggle.ts b/packages/web-ui/example/src/components/utility-toggle.ts
new file mode 100644
index 0000000..809f363
--- /dev/null
+++ b/packages/web-ui/example/src/components/utility-toggle.ts
@@ -0,0 +1,105 @@
+import { LitElement, html } from "lit";
+import { customElement, property } from "lit/decorators.js";
+
+export interface UtilityVisibility {
+ showToolCalls: boolean;
+ showThinking: boolean;
+ showSystemMessages: boolean;
+ showTimestamps: boolean;
+}
+
+@customElement("jae-utility-toggle")
+export class JaeUtilityToggle extends LitElement {
+ @property({ type: Object }) visibility: UtilityVisibility = {
+ showToolCalls: true,
+ showThinking: false,
+ showSystemMessages: false,
+ showTimestamps: true,
+ };
+
+ @property({ type: Boolean }) open = false;
+
+ protected override createRenderRoot() { return this; }
+
+ private _toggle(key: keyof UtilityVisibility) {
+ this.visibility = { ...this.visibility, [key]: !this.visibility[key] };
+ this.dispatchEvent(new CustomEvent("visibility-change", {
+ detail: this.visibility,
+ bubbles: true,
+ composed: true,
+ }));
+ }
+
+ private _items: { key: keyof UtilityVisibility; label: string; icon: string; desc: string }[] = [
+ { key: "showToolCalls", label: "Tool Calls", icon: "🔧", desc: "Show web search, image gen & other tool results" },
+ { key: "showThinking", label: "Thinking", icon: "🧠", desc: "Show model reasoning / thinking blocks" },
+ { key: "showSystemMessages", label: "System Messages", icon: "⚙️", desc: "Show system notifications and prompts" },
+ { key: "showTimestamps", label: "Timestamps", icon: "🕐", desc: "Show message timestamps" },
+ ];
+
+ override render() {
+ const activeCount = Object.values(this.visibility).filter(Boolean).length;
+ return html`
+
+
+
+
+
+ ${this.open ? html`
+
+
+
+

+
+
Message Filters
+
Control what JAE shows you
+
+
+
+
+
+
+ ${this._items.map(item => html`
+
+ `)}
+
+
+
+
+ Settings auto-save per session
+
+
+
+
{ this.open = false; this.requestUpdate(); }}>
+ ` : html``}
+
+ `;
+ }
+}
diff --git a/packages/web-ui/example/src/main.ts b/packages/web-ui/example/src/main.ts
index 4b61f91..e5e0e94 100644
--- a/packages/web-ui/example/src/main.ts
+++ b/packages/web-ui/example/src/main.ts
@@ -36,6 +36,10 @@ import "./components/command-palette.js";
import "./components/keyboard-shortcuts.js";
import "./components/memory-manager.js";
import "./components/cost-tracker.js";
+import { JaeEmptyState } from "./components/empty-state.js";
+import { JaeUtilityToggle, type UtilityVisibility } from "./components/utility-toggle.js";
+import "./components/empty-state.js";
+import "./components/utility-toggle.js";
// Register custom message renderers
registerCustomMessageRenderers();
@@ -80,6 +84,17 @@ const commandPalette = document.createElement("command-palette") as CommandPalet
const keyboardShortcuts = document.createElement("keyboard-shortcuts") as KeyboardShortcuts;
const memoryManager = document.createElement("memory-manager") as MemoryManager;
const costTracker = document.createElement("cost-tracker") as CostTracker;
+const utilityToggle = document.createElement("jae-utility-toggle") as JaeUtilityToggle;
+utilityToggle.addEventListener("visibility-change", (e: Event) => {
+ const vis = (e as CustomEvent).detail;
+ const chatEl = document.getElementById("chat-wrapper");
+ if (chatEl) {
+ chatEl.classList.toggle("hide-tool-calls", !vis.showToolCalls);
+ chatEl.classList.toggle("hide-thinking", !vis.showThinking);
+ chatEl.classList.toggle("hide-system-msgs", !vis.showSystemMessages);
+ chatEl.classList.toggle("hide-timestamps", !vis.showTimestamps);
+ }
+});
document.body.appendChild(commandPalette);
document.body.appendChild(keyboardShortcuts);
document.body.appendChild(memoryManager);
@@ -275,6 +290,17 @@ const newSession = () => {
window.location.href = url.toString();
};
+// Handle suggestion chip clicks from empty state
+const handleSuggestion = (e: Event) => {
+ const ce = e as CustomEvent;
+ const textarea = document.querySelector('textarea') as HTMLTextAreaElement;
+ if (textarea) {
+ textarea.value = ce.detail;
+ textarea.dispatchEvent(new Event('input', { bubbles: true }));
+ textarea.focus();
+ }
+};
+
// ============================================================================
// RENDER
// ============================================================================
@@ -328,7 +354,7 @@ const renderApp = () => {
});
}}
title="Click to edit title">${currentTitle}`
- : html`JAE Web UI`
+ : html`JAE
`
}
@@ -349,15 +375,26 @@ const renderApp = () => {
${Button({ variant: "ghost", size: "sm",
children: html`⌘K`,
onClick: () => commandPalette.show(), title: "Command Palette (Ctrl+K)" })}
-
+ ${utilityToggle}
+
${Button({ variant: "ghost", size: "sm", children: icon(Settings, "sm"),
onClick: () => SettingsDialog.open([new ProvidersModelsTab(), new ProxyTab()]),
title: "Settings" })}
+
+ ${agent && agent.state.messages.length === 0 ? html`
+
+
+
+ ` : html``}
+
- ${chatPanel}
+
+ ${chatPanel}
+
`;