feat: better tool parsing ui

This commit is contained in:
Brooklyn Nicholson
2026-05-04 16:08:44 -05:00
parent d1d0ed4016
commit 5f334e86fd
24 changed files with 1865 additions and 244 deletions
+16 -13
View File
@@ -39,6 +39,7 @@ import {
import type { ModelOptionsResponse } from '@/types/hermes'
import { routeSessionId } from '../routes'
import type { SetStatusbarItemGroup } from '../shell/statusbar-controls'
import { titlebarHeaderBaseClass, titlebarHeaderShadowClass } from '../shell/titlebar'
import type { SetTitlebarToolGroup } from '../shell/titlebar-controls'
@@ -72,10 +73,13 @@ interface ChatViewProps extends Omit<React.ComponentProps<'div'>, 'onSubmit'> {
onSetFastMode: (enabled: boolean) => void
onSetReasoningEffort: (effort: string) => void
onSelectPersonality: (name: string) => void
onOpenCommandCenterSystem: () => void
onOpenSkills: () => void
onThreadMessagesChange: (messages: readonly ThreadMessage[]) => void
onEdit: (message: AppendMessage) => Promise<void>
onReload: (parentId: string | null) => Promise<void>
onTranscribeAudio?: (audio: Blob) => Promise<string>
setStatusbarItemGroup?: SetStatusbarItemGroup
setTitlebarToolGroup?: SetTitlebarToolGroup
}
@@ -121,17 +125,20 @@ export function ChatView({
onPickImages,
onRemoveAttachment,
onSubmit,
onChangeCwd,
onBrowseCwd,
onOpenModelPicker,
onChangeCwd: _onChangeCwd,
onBrowseCwd: _onBrowseCwd,
onOpenModelPicker: _onOpenModelPicker,
onRestartPreviewServer,
onSetFastMode,
onSetReasoningEffort,
onSelectPersonality,
onSetFastMode: _onSetFastMode,
onSetReasoningEffort: _onSetReasoningEffort,
onSelectPersonality: _onSelectPersonality,
onOpenCommandCenterSystem,
onOpenSkills,
onThreadMessagesChange,
onEdit,
onReload,
onTranscribeAudio,
setStatusbarItemGroup: _setStatusbarItemGroup,
setTitlebarToolGroup
}: ChatViewProps) {
const location = useLocation()
@@ -266,7 +273,7 @@ export function ChatView({
<>
<div
className={cn(
'relative col-start-2 col-end-3 row-start-1 flex h-[calc(100vh-0.375rem)] min-w-0 flex-col overflow-hidden rounded-[0.9375rem] bg-transparent',
'relative col-start-2 col-end-3 row-start-1 flex h-full min-w-0 flex-col overflow-hidden rounded-[0.9375rem] bg-transparent',
className
)}
>
@@ -337,12 +344,8 @@ export function ChatView({
<ChatPreviewRail onRestartServer={onRestartPreviewServer} setTitlebarToolGroup={setTitlebarToolGroup} />
<ChatRightRail
onBrowseCwd={onBrowseCwd}
onChangeCwd={onChangeCwd}
onOpenModelPicker={onOpenModelPicker}
onSelectPersonality={onSelectPersonality}
onSetFastMode={onSetFastMode}
onSetReasoningEffort={onSetReasoningEffort}
onOpenCommandCenterSystem={onOpenCommandCenterSystem}
onOpenSkills={onOpenSkills}
/>
</>
)
+88 -65
View File
@@ -1,57 +1,44 @@
import { useStore } from '@nanostores/react'
import type * as React from 'react'
import { type ReactNode, useMemo } from 'react'
import type { SetTitlebarToolGroup } from '@/app/shell/titlebar-controls'
import { SESSION_INSPECTOR_WIDTH, SessionInspector } from '@/components/session-inspector'
import { Button } from '@/components/ui/button'
import { AlertCircle, Loader2, Sparkles } from '@/lib/icons'
import { cn } from '@/lib/utils'
import { $desktopActionTasks, buildRailTasks, type RailTask, type RailTaskStatus } from '@/store/activity'
import { $inspectorOpen } from '@/store/layout'
import { $previewReloadRequest, $previewTarget } from '@/store/preview'
import {
$availablePersonalities,
$busy,
$currentBranch,
$currentCwd,
$currentFastMode,
$currentModel,
$currentPersonality,
$currentProvider,
$currentReasoningEffort,
$currentServiceTier,
$gatewayState
} from '@/store/session'
import { $previewReloadRequest, $previewServerRestart, $previewTarget } from '@/store/preview'
import { $sessions, $workingSessionIds } from '@/store/session'
import { PreviewPane } from './preview-pane'
interface ChatRightRailProps extends Pick<
React.ComponentProps<typeof SessionInspector>,
'onBrowseCwd' | 'onChangeCwd'
> {
onOpenModelPicker: () => void
onSetFastMode: (enabled: boolean) => void
onSetReasoningEffort: (effort: string) => void
onSelectPersonality: (name: string) => void
export const SESSION_INSPECTOR_WIDTH = 'clamp(13.5rem, 21vw, 20rem)'
export const PREVIEW_RAIL_WIDTH = 'clamp(18rem, 36vw, 38rem)'
const RAIL_TASK_LIMIT = 6
const TASK_ICONS: Record<RailTaskStatus, ReactNode> = {
error: <AlertCircle className="size-3 text-destructive" />,
running: <Loader2 className="size-3 animate-spin text-muted-foreground" />,
success: <Sparkles className="size-3 text-emerald-500" />
}
export function ChatRightRail({
onBrowseCwd,
onChangeCwd,
onOpenModelPicker,
onSetFastMode,
onSetReasoningEffort,
onSelectPersonality
}: ChatRightRailProps) {
interface ChatRightRailProps {
onOpenCommandCenterSystem: () => void
onOpenSkills: () => void
}
export function ChatRightRail({ onOpenCommandCenterSystem, onOpenSkills }: ChatRightRailProps) {
const inspectorOpen = useStore($inspectorOpen)
const gatewayOpen = useStore($gatewayState) === 'open'
const busy = useStore($busy)
const cwd = useStore($currentCwd)
const branch = useStore($currentBranch)
const model = useStore($currentModel)
const provider = useStore($currentProvider)
const reasoningEffort = useStore($currentReasoningEffort)
const serviceTier = useStore($currentServiceTier)
const fastMode = useStore($currentFastMode)
const personality = useStore($currentPersonality)
const personalities = useStore($availablePersonalities)
const sessions = useStore($sessions)
const workingSessionIds = useStore($workingSessionIds)
const previewRestart = useStore($previewServerRestart)
const desktopActionTasks = useStore($desktopActionTasks)
const tasks = useMemo(
() => buildRailTasks(workingSessionIds, sessions, previewRestart, desktopActionTasks),
[desktopActionTasks, previewRestart, sessions, workingSessionIds]
)
return (
<div
@@ -60,26 +47,21 @@ export function ChatRightRail({
inspectorOpen && 'border-l border-border/60'
)}
>
<SessionInspector
branch={branch}
busy={busy}
cwd={cwd}
fastMode={fastMode}
modelLabel={model ? model.split('/').pop() || model : ''}
modelTitle={provider ? `${provider}: ${model || ''}` : model}
onBrowseCwd={onBrowseCwd}
onChangeCwd={onChangeCwd}
onOpenModelPicker={gatewayOpen ? onOpenModelPicker : undefined}
onSelectPersonality={gatewayOpen ? onSelectPersonality : undefined}
onSetFastMode={gatewayOpen ? onSetFastMode : undefined}
onSetReasoningEffort={gatewayOpen ? onSetReasoningEffort : undefined}
open={inspectorOpen}
personalities={personalities}
personality={personality}
providerName={provider}
reasoningEffort={reasoningEffort}
serviceTier={serviceTier}
/>
<aside
aria-hidden={!inspectorOpen}
className={cn(
'relative flex h-full w-full min-w-0 flex-col overflow-hidden bg-transparent pb-2 pl-2 pr-3 pt-[calc(var(--titlebar-height)+0.25rem)] text-muted-foreground transition-none',
inspectorOpen ? 'opacity-100' : 'pointer-events-none opacity-0'
)}
>
<RailHeader onOpenAll={onOpenCommandCenterSystem} />
<div className="flex min-h-0 flex-1 flex-col gap-1.5 overflow-y-auto px-1.5">
{tasks.length === 0 ? <EmptyRail /> : tasks.slice(0, RAIL_TASK_LIMIT).map(task => <RailRow key={task.id} task={task} />)}
</div>
<RailFooter onOpenSkills={onOpenSkills} onOpenSystem={onOpenCommandCenterSystem} />
</aside>
</div>
)
}
@@ -110,5 +92,46 @@ export function ChatPreviewRail({
)
}
export { SESSION_INSPECTOR_WIDTH }
export const PREVIEW_RAIL_WIDTH = 'clamp(18rem, 36vw, 38rem)'
function RailHeader({ onOpenAll }: { onOpenAll: () => void }) {
return (
<div className="mb-2 flex items-center justify-between gap-1 px-1.5">
<span className="text-[0.68rem] font-semibold uppercase tracking-[0.07em] text-muted-foreground/85">Background</span>
<Button className="h-6 px-2 text-[0.68rem]" onClick={onOpenAll} size="sm" variant="ghost">
View all
</Button>
</div>
)
}
function RailFooter({ onOpenSkills, onOpenSystem }: { onOpenSkills: () => void; onOpenSystem: () => void }) {
return (
<div className="mt-2 flex items-center gap-1 px-1.5">
<Button className="h-6 flex-1 justify-start px-2 text-[0.68rem]" onClick={onOpenSkills} size="sm" variant="ghost">
Agents
</Button>
<Button className="h-6 flex-1 justify-start px-2 text-[0.68rem]" onClick={onOpenSystem} size="sm" variant="ghost">
System
</Button>
</div>
)
}
function EmptyRail() {
return (
<div className="rounded-md border border-border/45 bg-background/55 px-2.5 py-2 text-[0.68rem] text-muted-foreground/80">
No background activity.
</div>
)
}
function RailRow({ task }: { task: RailTask }) {
return (
<div className="rounded-md border border-border/45 bg-background/58 px-2 py-1.5">
<div className="flex items-center gap-1.5">
{TASK_ICONS[task.status]}
<span className="truncate text-[0.72rem] font-medium text-foreground/90">{task.label}</span>
</div>
<div className="mt-0.5 truncate pl-4.5 text-[0.66rem] text-muted-foreground/80">{task.detail}</div>
</div>
)
}
@@ -769,7 +769,7 @@ export function PreviewPane({ onRestartServer, reloadRequest = 0, setTitlebarToo
}, [appendConsoleEntry, target.url])
return (
<aside className="relative flex h-screen w-full min-w-0 flex-col overflow-hidden border-l border-border/60 bg-background text-muted-foreground">
<aside className="relative flex h-full w-full min-w-0 flex-col overflow-hidden border-l border-border/60 bg-background text-muted-foreground">
<div className="flex min-h-0 flex-1 flex-col overflow-hidden">
<div className="pointer-events-none flex min-h-(--titlebar-height) items-center gap-1.5 border-b border-border/60 bg-background px-2 py-1">
<div className="min-w-0 flex-1">
+1 -1
View File
@@ -100,7 +100,7 @@ export function ChatSidebar({
return (
<Sidebar
className={cn(
'relative h-screen min-w-0 overflow-hidden border-r border-t-0 border-b-0 border-l-0 text-foreground transition-none [backdrop-filter:blur(1.5rem)_saturate(1.08)]',
'relative h-full min-w-0 overflow-hidden border-r border-t-0 border-b-0 border-l-0 text-foreground transition-none [backdrop-filter:blur(1.5rem)_saturate(1.08)]',
sidebarOpen
? 'border-(--sidebar-edge-border) bg-[color-mix(in_srgb,var(--dt-sidebar-bg)_97%,transparent)] opacity-100'
: 'pointer-events-none border-transparent bg-transparent opacity-0'