import * as React from "react" import { ChevronDown, ChevronUp, CircleStop, Copy, Edit3, FileText, GitFork, MessageSquare, Save, Send, Settings, SlidersHorizontal, Trash2, X, } from "lucide-react" import { MarkdownMessage } from "@/components/markdown-message" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Select } from "@/components/ui/select" import { Textarea } from "@/components/ui/textarea" import { LabelledField } from "@/components/app/shared" import { ASK_QUESTIONS_TOOL_NAME, parseAskQuestionsArguments, } from "@/lib/tools" import { cn } from "@/lib/utils" import type { AskQuestionsAnswer, AskQuestionsPayload, ChatMessage, ChatSession, ChatToolCall, ModelConfig, PromptTemplate, RequestState, RuntimeConfig, ServiceConfig, } from "@/types" const STATUS_TEXT: Record = { idle: "就绪", requesting: "请求中", generating: "生成中", failed: "失败", stopped: "已停止", done: "完成", } function formatMessageTime(value: string) { return new Intl.DateTimeFormat("zh-CN", { hour: "2-digit", minute: "2-digit", }).format(new Date(value)) } function formatDuration(ms?: number) { if (!ms) { return "" } if (ms < 1000) { return `${Math.round(ms)}ms` } return `${(ms / 1000).toFixed(1)}s` } function formatUsage(message: ChatMessage) { if (!message.usage) { return "" } const parts = [ message.usage.promptTokens !== undefined ? `输入 ${message.usage.promptTokens}` : "", message.usage.completionTokens !== undefined ? `输出 ${message.usage.completionTokens}` : "", message.usage.totalTokens !== undefined ? `总计 ${message.usage.totalTokens}` : "", ].filter(Boolean) return parts.length ? parts.join(" / ") : "" } function getMessageCopyContent(message: ChatMessage) { if (message.role !== "assistant" || !message.reasoningContent?.trim()) { return message.content } return [ `reasoning_content:\n${message.reasoningContent.trim()}`, message.content.trim() ? `content:\n${message.content.trim()}` : "", ] .filter(Boolean) .join("\n\n") } type ChatViewProps = { currentSession: ChatSession runtimeConfig: RuntimeConfig services: ServiceConfig[] models: ModelConfig[] prompts: PromptTemplate[] chatInput: string editingMessageId: string | null editingContent: string showPromptEditor: boolean quickConfigOpen: boolean isBusy: boolean configIsValid: boolean onInputChange: (value: string) => void onSend: () => void onStop: () => void onCopy: (text: string, label?: string) => void onDeleteMessage: (messageId: string) => void onEditMessage: (message: ChatMessage) => void onForkMessage: (messageId: string) => void onSubmitAskQuestions: ( messageId: string, toolCall: ChatToolCall, answers: AskQuestionsAnswer[] ) => void onCancelEdit: () => void onEditingContentChange: (value: string) => void onSaveEditOnly: () => void onSaveEditAndSend: () => void onServiceChange: (serviceId: string) => void onModelChange: (modelId: string) => void onPromptChange: (promptId: string) => void onQuickConfigOpenChange: (open: boolean) => void onPromptEditorToggle: () => void onSystemPromptChange: (value: string) => void onOpenSettings: () => void } export function ChatView({ currentSession, runtimeConfig, services, models, prompts, chatInput, editingMessageId, editingContent, showPromptEditor, quickConfigOpen, isBusy, configIsValid, onInputChange, onSend, onStop, onCopy, onDeleteMessage, onEditMessage, onForkMessage, onSubmitAskQuestions, onCancelEdit, onEditingContentChange, onSaveEditOnly, onSaveEditAndSend, onServiceChange, onModelChange, onPromptChange, onQuickConfigOpenChange, onPromptEditorToggle, onSystemPromptChange, onOpenSettings, }: ChatViewProps) { const canSend = chatInput.trim().length > 0 && !isBusy return (

{currentSession.title}

{currentSession.temporary ? 临时 : null}
{runtimeConfig.service?.name || "未选择服务"} {runtimeConfig.modelName || "未选择模型"} {currentSession.messages.length} 条消息
{quickConfigOpen ? (
{!configIsValid ? ( ) : null}
{showPromptEditor ? (