Skip to content

Commit a85f2b3

Browse files
committed
feat: add input history, JSON detection, message metadata, and pinned messages functionality
- Implemented InputHistory class for managing user input history with navigation support. - Added JSON detection utility to auto-detect and format JSON messages. - Created MessageMeta extraction for tracking message tokens, cost, and model usage. - Developed PinnedMessages class for managing pinned messages with localStorage persistence. - Introduced session cache for efficient retrieval of recent session histories. - Implemented silent message filtering to exclude NO_REPLY messages from display. - Added slash command definitions and parser for user commands. - Created slash command executor to handle various commands and interactions. - Implemented browser-native speech recognition and synthesis functionalities. - Added unit tests for new features including input history, JSON detection, pinned messages, and silent filtering.
1 parent 33af542 commit a85f2b3

38 files changed

Lines changed: 3112 additions & 376 deletions

CHANGELOG.md

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,76 @@
44

55
## [Unreleased]
66

7+
### Added
8+
9+
- **Slash Commands** — type `/` to open autocomplete menu with 18 commands; keyboard navigation (↑↓ Tab Enter Esc); arg picker for `/think`, `/fast`, `/verbose`; categorized by Session, Model, Tools, Agents (`lib/slash-commands.ts`, `components/slash-menu.tsx`, `hooks/use-slash-menu.ts`)
10+
- **Slash Command Executor** — 15 client-side commands executed via Gateway RPC without sending to agent: `/model`, `/think`, `/compact`, `/fast`, `/verbose`, `/usage`, `/agents`, `/kill`, `/help`, `/new`, `/reset`, `/stop`, `/clear`, `/export`, `/focus` (`lib/slash-executor.ts`)
11+
- **Input History** — Arrow Up/Down navigates last 50 sent messages, deduplicates consecutive identical entries (`lib/input-history.ts`)
12+
- **Chat Export**`/export` generates a Markdown file and downloads it (`lib/chat-export.ts`)
13+
- **Message Search**`Ctrl+F` / `Cmd+F` opens search bar; filters messages by text content; shows match count (`components/search-bar.tsx`)
14+
- **Per-Message Metadata** — assistant messages display input/output tokens, cache read/write, cost, context %, and model name on hover (`lib/message-meta.ts`, `components/message-meta.tsx`)
15+
- **JSON Auto-Detection** — messages containing pure JSON render as collapsible blocks with syntax badge and pretty-print (`lib/json-detect.ts`, `components/json-block.tsx`)
16+
- **Welcome State** — empty chat shows agent avatar, name, `/` hint, and 4 quick-start suggestion buttons (`components/welcome-state.tsx`)
17+
- **Token Estimate** — input area shows approximate token count (`~N tokens`) when message exceeds 100 characters
18+
- **NO_REPLY Filter** — assistant messages matching `NO_REPLY` are silently hidden from display (`lib/silent-filter.ts`)
19+
- **Speech-to-Text (STT)** — microphone button records voice and inserts transcribed text; uses Web Speech API with interim + final transcript support (`lib/speech.ts`)
20+
- **Text-to-Speech (TTS)** — volume button on assistant messages reads content aloud; strips markdown before speaking; stop/cancel support (`lib/speech.ts`)
21+
- **Pinned Messages** — bookmark button on any message; pinned bar above chat with expand/collapse; persisted to localStorage per session (`lib/pinned-messages.ts`, `components/pinned-bar.tsx`)
22+
- **Deleted Messages** — trash button hides messages client-side; persisted to localStorage per session (`lib/deleted-messages.ts`)
23+
- **Focus Mode** — toggle in chat settings hides sidebar for distraction-free chatting
24+
- **Tool Output Sidebar** — resizable split panel; click "View full output" on any tool call to see formatted output; drag handle for width adjustment (`components/tool-sidebar.tsx`)
25+
- **Session Cache** — LRU cache (max 20) for pinned/deleted message state across session switches (`lib/session-cache.ts`)
26+
27+
#### Chat — Input Redesign
28+
- **Professional chat input** — redesigned to match OpenClaw UI: card-style container with accent focus glow, subtle toolbar separator, compact icon buttons (`h-7 rounded-md`), accent-colored send button with hover shadow
29+
- **Model Selector** — inline model picker in chat input toolbar; lazy-loads model catalog on open; search/filter models; shows current model with provider; switches via `sessions.patch` RPC (`components/model-selector.tsx`)
30+
- **Slash Command Button**`/` icon button in toolbar opens slash menu; active state highlighted; toggle on/off
31+
- **New Session in toolbar** — moved `+` button from header to chat input toolbar-right; hidden during streaming (replaced by Stop button); matches OpenClaw layout pattern
32+
- **Full-width input** — removed `max-w-3xl` constraint; input stretches to screen width
33+
- **Dynamic placeholder** — shows `"Message {AgentName} (Enter to send)"` instead of static text; switches to `"Listening…"` during STT recording
34+
35+
#### Chat — Session Sidebar Redesign
36+
- **Collapsible agent groups** — click agent header to toggle sessions visibility; chevron indicator (``/``); collapsed groups excluded from virtualization
37+
- **Collapse/Expand all** — icon button next to search filter; toggles all agent groups; appears only with 2+ agents; tooltip hint
38+
- **Enhanced session items** — two-line layout (label + preview/subject); relative time (`just now`, `5m ago`); token count right-aligned; selected state with subtle shadow
39+
- **Improved spacing** — consistent `px-2.5` padding on virtual list items and container; no more edge-clipping
40+
41+
#### Chat — Toolbar Actions
42+
- **Export Button** — download icon in chat input toolbar; triggers `exportChatMarkdown()` to save conversation as Markdown file; hidden during streaming
43+
- **Search Button** — magnifying glass icon in chat input toolbar; opens search bar overlay; synced with `Ctrl+F` / `Cmd+F` keyboard shortcut
44+
45+
#### Chat — Session Rename
46+
- **Inline session rename** — double-click session label in chat header to edit; Enter to save, Escape to cancel; calls `sessions.patch` RPC with `label` field; toast feedback on success/failure
47+
48+
#### Chat — Auto-scroll Toggle
49+
- **Scroll lock** — lock/unlock toggle near scroll area; when locked, `ChatContainerScrollAnchor` is unmounted to pause auto-scroll during streaming; visual indicator shows lock state
50+
51+
#### Chat — Quick Actions on Hover
52+
- **Session hover actions** — hovering a session item in sidebar reveals compact and delete action buttons overlaying the time/token area; uses `group-hover` Tailwind pattern; compact calls `sessions.compact` RPC with toast feedback
53+
54+
#### Chat — Header Cleanup
55+
- **Removed duplicate Stop button** — single Stop button in chat input toolbar only; header shows `"Generating…"` shimmer text indicator
56+
- **Removed Reset button** — reset available via `/reset` slash command; reduces header clutter
57+
58+
#### Chat — Stable Message Keys
59+
- **`messageKey()`** — stable key generation for chat messages using `id > messageId > toolCallId > timestamp+role+index`; mirrors OpenClaw UI's `messageKey()` (`ui/src/ui/views/chat.ts:1467`); used by pin and delete features for correctness across history reloads
60+
61+
#### Tests
62+
- **Chat lib unit tests** — 59 new tests across 7 files: `input-history` (9), `json-detect` (9), `silent-filter` (6), `pinned-messages` (6), `deleted-messages` (6), `slash-commands` (15), `messageKey` (8); total test count 342 → 401
63+
- **Coverage config** — added 6 chat lib files to `vitest.config.ts` coverage include
64+
65+
### Changed
66+
67+
#### Chat — Code Quality
68+
- **Shiki highlighter** — replaced `Promise<any>` + `as any` with proper `Awaited<ReturnType<>>` typing; zero `as any` casts remaining in codebase (`lib/shiki.ts`)
69+
- **SlashMenu memo stability** — destructured stable `useCallback` refs from `useSlashMenu()` hook into individual deps; `SlashMenu` memo now works correctly instead of re-rendering every keystroke
70+
- **Clipboard error handling** — added rejection handler to `navigator.clipboard.writeText()`; prevents unhandled promise rejection in non-HTTPS contexts
71+
- **Input ref timing** — captured `rawInput` from ref before `setInputValue('')` to eliminate dependency on React batching internals
72+
- **Copy timeout cleanup**`setCopied(false)` timeout tracked via ref and cleared on unmount; prevents setState-after-unmount warning
73+
- **Drag resize optimization** — tool sidebar `handleMouseDown` uses `widthRef` instead of `width` in closure; callback stable with `[]` deps during drag
74+
- **STT listener cleanup** — speech recognition event listeners tracked and explicitly removed in `stopStt()`
75+
- **Agent load error toasts** — failed identity, config, workspace, and model list loads now show `toast.error()` instead of silent `log.warn`
76+
777
### Changed
878

979
- **`GatewaySessionRow`** — added `spawnedBy?: string` field; tracks which session spawned a sub-agent session

src/app/agents/components/agent-overview.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
Zap,
1414
} from 'lucide-react'
1515
import { useCallback, useEffect, useMemo, useState } from 'react'
16+
import { toast } from 'sonner'
1617
import { Badge } from '@/components/ui/badge'
1718
import { Button } from '@/components/ui/button'
1819
import { Input } from '@/components/ui/input'
@@ -194,7 +195,10 @@ export function AgentOverview({
194195
client
195196
.request<{ models: ModelChoice[] }>('models.list', {})
196197
.then((r) => setAvailableModels(r.models))
197-
.catch((err) => log.warn('Models list failed', err))
198+
.catch((err) => {
199+
log.warn('Models list failed', err)
200+
toast.error('Failed to load available models')
201+
})
198202
}, [client])
199203

200204
const isDirty = useMemo(() => {

src/app/agents/index.tsx

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
Zap,
2121
} from 'lucide-react'
2222
import { useEffect, useMemo, useRef, useState } from 'react'
23+
import { toast } from 'sonner'
2324
import { AgentActivity } from '@/app/agents/components/agent-activity'
2425
import { AgentBindings } from '@/app/agents/components/agent-bindings'
2526
import { AgentCard } from '@/app/agents/components/agent-card'
@@ -123,7 +124,10 @@ export default function AgentsPage() {
123124
client
124125
.request<AgentIdentityResult>('agent.identity.get', { agentId: agent.id })
125126
.then((r) => setIdentities((prev) => ({ ...prev, [agent.id]: r })))
126-
.catch((err) => log.warn(`Identity fetch failed for ${agent.id}`, err))
127+
.catch((err) => {
128+
log.warn(`Identity fetch failed for ${agent.id}`, err)
129+
toast.error(`Failed to load identity for ${agent.name || agent.id}`)
130+
})
127131
}
128132
}, [client, agents])
129133

@@ -132,7 +136,10 @@ export default function AgentsPage() {
132136
client
133137
.request<ConfigSnapshot>('config.get', {})
134138
.then((r) => useGatewayStore.getState().setConfig(r))
135-
.catch((err) => log.warn('Config fetch failed', err))
139+
.catch((err) => {
140+
log.warn('Config fetch failed', err)
141+
toast.error('Failed to load configuration')
142+
})
136143
}, [client, config])
137144

138145
useEffect(() => {
@@ -141,7 +148,10 @@ export default function AgentsPage() {
141148
client
142149
.request<{ agentId: string; workspace: string; files: unknown[] }>('agents.files.list', { agentId: selectedId })
143150
.then((r) => setWorkspace(r.workspace))
144-
.catch((err) => log.warn('Workspace fetch failed', err))
151+
.catch((err) => {
152+
log.warn('Workspace fetch failed', err)
153+
toast.error('Failed to load workspace info')
154+
})
145155
}, [client, selectedId])
146156

147157
const refresh = async () => {

0 commit comments

Comments
 (0)