diff --git a/src/App.tsx b/src/App.tsx index 5b4d409..2e813b3 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,46 +1,24 @@ import * as React from "react" import { - Bot, - ChevronDown, - ChevronUp, - Check, - CircleStop, - Copy, Database, - Download, - Edit3, - Eye, - EyeOff, FileText, - GitFork, Info, - KeyRound, MessageSquare, - Moon, - Plus, - RefreshCw, - RotateCcw, - Save, - Search, - Send, Settings, SlidersHorizontal, - Sun, - Trash2, - Upload, - Wand2, - X, } from "lucide-react" -import { MarkdownMessage } from "@/components/markdown-message" +import { AboutView } from "@/components/app/about-view" +import { ChatView } from "@/components/app/chat-view" +import { DataView } from "@/components/app/data-view" +import { ModelsView } from "@/components/app/models-view" +import { PromptsView } from "@/components/app/prompts-view" +import { AppSidebar } from "@/components/app/sidebar" +import { SettingsView } from "@/components/app/settings-view" import { useTheme } from "@/components/theme-provider" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" -import { Input } from "@/components/ui/input" -import { Select } from "@/components/ui/select" -import { Textarea } from "@/components/ui/textarea" import { - APP_VERSION, createDefaultData, createId, createMessage, @@ -67,25 +45,21 @@ import { ASK_QUESTIONS_TOOL, ASK_QUESTIONS_TOOL_NAME, formatAskQuestionsResult, - parseAskQuestionsArguments, } from "@/lib/tools" import { cn } from "@/lib/utils" import type { AppData, AppSettings, AskQuestionsAnswer, - AskQuestionsPayload, ChatMessage, ChatSession, ChatToolCall, - ChatToolDefinition, ModelConfig, ModelParameters, PromptTemplate, RequestState, + RuntimeConfig, ServiceConfig, - ThemeMode, - ToolChoiceMode, ViewKey, } from "@/types" @@ -101,18 +75,6 @@ type ActiveRequest = { reason: "user" | null } -type RuntimeConfig = { - service?: ServiceConfig - model?: ModelConfig - prompt?: PromptTemplate - modelName: string - parameters: ModelParameters - systemPrompt: string - tools: ChatToolDefinition[] - toolChoice: ToolChoiceMode - errors: string[] -} - const VIEW_ITEMS: { key: ViewKey label: string @@ -126,15 +88,6 @@ const VIEW_ITEMS: { { key: "about", label: "关于", icon: Info }, ] -const AVAILABLE_TOOL_ITEMS = [ - { - id: ASK_QUESTIONS_TOOL_NAME, - label: "askQuestions", - description: - "向用户展示单选、多选和文本框,并把答案作为工具结果返回给模型。", - }, -] - const STATUS_TEXT: Record = { idle: "就绪", requesting: "请求中", @@ -153,67 +106,6 @@ const STATUS_TONE: Record = { done: "success", } -function formatDateTime(value: string) { - return new Intl.DateTimeFormat("zh-CN", { - month: "2-digit", - day: "2-digit", - hour: "2-digit", - minute: "2-digit", - }).format(new Date(value)) -} - -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") -} - function isBusy(status: RequestState) { return status === "requesting" || status === "generating" } @@ -581,20 +473,6 @@ function AppContent() { }) } - function clearCurrentSession() { - if (!window.confirm("确定清空当前会话吗?")) { - return - } - - updateCurrentSession((session) => ({ - ...session, - messages: [], - status: "idle", - error: undefined, - updatedAt: nowIso(), - })) - } - function clearAllSessions() { if (!window.confirm("确定清空所有会话吗?")) { return @@ -878,43 +756,6 @@ function AppContent() { request.controller.abort() } - async function regenerateLastResponse() { - if (!currentSession || activeRequestRef.current) { - return - } - - let lastAssistantIndex = -1 - - for ( - let index = currentSession.messages.length - 1; - index >= 0; - index -= 1 - ) { - if (currentSession.messages[index].role === "assistant") { - lastAssistantIndex = index - break - } - } - - if (lastAssistantIndex < 0) { - announce("还没有可重新生成的回复", "warning") - return - } - - const sourceMessages = currentSession.messages.slice(0, lastAssistantIndex) - - setData((previous) => - updateSessionInData(previous, currentSession.id, (session) => ({ - ...session, - messages: sourceMessages, - status: "idle", - error: undefined, - updatedAt: nowIso(), - })) - ) - await startAssistantResponse(currentSession.id, sourceMessages) - } - function deleteMessage(messageId: string) { if (!window.confirm("确定删除这条消息吗?")) { return @@ -1599,102 +1440,17 @@ function AppContent() { data.settings.compactMode && "app-shell-compact" )} > - + createNewSession()} + onClearSessions={clearAllSessions} + onSwitchSession={switchSession} + onRenameSession={renameSession} + onDeleteSession={deleteSession} + />
@@ -1763,12 +1519,12 @@ function AppContent() { editingMessageId={editingMessageId} editingContent={editingContent} showPromptEditor={showPromptEditor} + quickConfigOpen={data.settings.chatQuickConfigOpen} isBusy={isCurrentBusy} configIsValid={configIsValid} onInputChange={setChatInput} onSend={() => void handleSend()} onStop={stopGeneration} - onRegenerate={() => void regenerateLastResponse()} onCopy={copyText} onDeleteMessage={deleteMessage} onEditMessage={startEditMessage} @@ -1780,12 +1536,12 @@ function AppContent() { onEditingContentChange={setEditingContent} onSaveEditOnly={() => void saveEditedMessage(false)} onSaveEditAndSend={() => void saveEditedMessage(true)} - onClearSession={clearCurrentSession} - onNewSession={() => createNewSession()} - onTemporarySession={() => createNewSession(true)} onServiceChange={applySessionService} onModelChange={applySessionModel} onPromptChange={applySessionPrompt} + onQuickConfigOpenChange={(open) => + updateSettings({ chatQuickConfigOpen: open }) + } onPromptEditorToggle={() => setShowPromptEditor((value) => !value)} onSystemPromptChange={(value) => updateCurrentSession((session) => ({ @@ -1902,1922 +1658,6 @@ function AppContent() { ) } -type ChatViewProps = { - currentSession: ChatSession - runtimeConfig: RuntimeConfig - services: ServiceConfig[] - models: ModelConfig[] - prompts: PromptTemplate[] - chatInput: string - editingMessageId: string | null - editingContent: string - showPromptEditor: boolean - isBusy: boolean - configIsValid: boolean - onInputChange: (value: string) => void - onSend: () => void - onStop: () => void - onRegenerate: () => 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 - onClearSession: () => void - onNewSession: () => void - onTemporarySession: () => void - onServiceChange: (serviceId: string) => void - onModelChange: (modelId: string) => void - onPromptChange: (promptId: string) => void - onPromptEditorToggle: () => void - onSystemPromptChange: (value: string) => void - onOpenSettings: () => void -} - -function ChatView({ - currentSession, - runtimeConfig, - services, - models, - prompts, - chatInput, - editingMessageId, - editingContent, - showPromptEditor, - isBusy, - configIsValid, - onInputChange, - onSend, - onStop, - onRegenerate, - onCopy, - onDeleteMessage, - onEditMessage, - onForkMessage, - onSubmitAskQuestions, - onCancelEdit, - onEditingContentChange, - onSaveEditOnly, - onSaveEditAndSend, - onClearSession, - onNewSession, - onTemporarySession, - onServiceChange, - onModelChange, - onPromptChange, - onPromptEditorToggle, - onSystemPromptChange, - onOpenSettings, -}: ChatViewProps) { - const canSend = chatInput.trim().length > 0 && !isBusy - const [quickConfigOpen, setQuickConfigOpen] = React.useState(true) - - return ( -
-
-
-
-

- {currentSession.title} -

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