feat: better tool parsing ui
This commit is contained in:
@@ -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}
|
||||
/>
|
||||
</>
|
||||
)
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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'
|
||||
|
||||
Reference in New Issue
Block a user