import { useMemo, useState } from 'react'; import { Archive, CircleDot, GitFork, LoaderCircle, PanelRight, Pencil, RotateCcw, Settings2, Square, Unplug, } from 'lucide-react'; import { Button } from '@/components/ui/button'; import { Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, } from '@/components/ui/dialog'; import { Input } from '@/components/ui/input'; import { Select } from '@/components/ui/select'; import { abortCodingConversation, compactCodingConversation, setCodingConversationModel, setCodingConversationThinking, } from '@/lib/coding-conversations'; import { buildCodingModelOptions, codingModelKey, parseCodingModelKey, } from '@/lib/coding-model-options'; import { useProviderStore } from '@/stores/providers'; import type { ConversationSnapshot, ConversationThinkingLevel, } from '@/types/coding-conversation'; import type { CodingConversationMetadata } from '@/types/coding-project'; const THINKING_OPTIONS: Array<{ value: ConversationThinkingLevel; label: string }> = [ { value: 'off', label: '关闭思考' }, { value: 'minimal', label: '极简思考' }, { value: 'low', label: '低思考' }, { value: 'medium', label: '中等思考' }, { value: 'high', label: '高思考' }, ]; function runLabel(snapshot: ConversationSnapshot | null): string { if (!snapshot) return '正在准备'; const { run } = snapshot; if (run.status === 'idle') return run.terminalReason === 'aborted' ? '已中止' : '空闲'; if (run.status === 'preparing') return '正在准备'; if (run.status === 'queued') return '排队中'; if (run.status === 'running') return '生成中'; if (run.status === 'retrying') return `重试中${run.retry ? ` · 第 ${run.retry.attempt} 次` : ''}`; if (run.status === 'compacting') return '整理上下文'; if (run.status === 'aborting') return '正在中止'; return '需要处理'; } export function CodingConversationHeader({ conversation, snapshot, connectionState, onRename, onArchive, onToggleUnread, onFork, onRefresh, onRecover, onOpenInspector, onOpenSettings, }: { conversation: CodingConversationMetadata | null; snapshot: ConversationSnapshot | null; connectionState: string; onRename(title: string): Promise; onArchive(): Promise; onToggleUnread(): Promise; onFork(): Promise; onRefresh(): Promise; onRecover(): Promise; onOpenInspector(): void; onOpenSettings?(): void; }) { const accounts = useProviderStore((state) => state.accounts); const vendors = useProviderStore((state) => state.vendors); const options = useMemo(() => buildCodingModelOptions(accounts, vendors), [accounts, vendors]); const [busyAction, setBusyAction] = useState(null); const [actionError, setActionError] = useState(null); const [renameOpen, setRenameOpen] = useState(false); const [titleDraft, setTitleDraft] = useState(conversation?.title ?? ''); const model = snapshot?.conversation.model.model ?? conversation?.model ?? null; const modelValue = model ? codingModelKey(model) : ''; const hasCurrentOption = options.some((option) => option.key === modelValue); const thinkingLevel = model?.thinkingLevel ?? 'off'; const runStatus = snapshot?.run.status ?? 'preparing'; const running = ['queued', 'running', 'retrying', 'compacting', 'aborting'].includes(runStatus); const queueCount = snapshot?.queue.items.length ?? 0; const context = snapshot?.context; const usedPercent = context?.contextWindow ? Math.min(100, Math.round((context.usedTokens / context.contextWindow) * 100)) : 0; const recoverable = Boolean(snapshot?.run.error?.recoverable || snapshot?.worker.error?.recoverable); const perform = (key: string, action: () => Promise) => { if (busyAction || !conversation) return; setBusyAction(key); setActionError(null); void action() .catch((error) => setActionError(error instanceof Error ? error.message : String(error))) .finally(() => setBusyAction((current) => current === key ? null : current)); }; return (

{runLabel(snapshot)} {queueCount > 0 ? ` · 队列 ${queueCount}` : ''} {connectionState === 'reconnecting' ? ' · 正在重连' : ''}

{running && runStatus !== 'aborting' && ( )}
{conversation && (
上下文 {context?.usedTokens ?? 0}/{context?.contextWindow ?? 0} {context?.recalculating && }
{recoverable && ( )}
)} {actionError &&

{actionError}

} 修改对话标题 标题只影响当前 Conversation,不会改动伙伴名称。 setTitleDraft(event.target.value)} />
); }