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` +
+ +
+ JAE Mascot +
+ + +

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` +
+ +
+ JAE +
+
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} +
`;