From bd731660ac4eff6a9717e89bc60635d506d35cd7 Mon Sep 17 00:00:00 2001 From: qixinbo Date: Sat, 28 Mar 2026 14:46:50 +0800 Subject: [PATCH] fix: model arguments fixed --- backend/app/core/llm_provider.py | 5 +- backend/app/core/nanobot.py | 30 +++- .../core/patched_openai_compat_provider.py | 43 +++++ backend/main.py | 33 +++- backend/tests/test_chat_project_id.py | 45 ++++++ .../test_patched_openai_compat_provider.py | 62 +++++++ frontend/src/components/ChatInterface.tsx | 152 ++++++++++++++++-- frontend/src/components/Sidebar.tsx | 8 - frontend/src/i18n/locales/en.json | 4 + frontend/src/i18n/locales/zh.json | 4 + frontend/src/pages/Dashboard.tsx | 2 +- frontend/src/pages/Login.tsx | 8 +- 12 files changed, 361 insertions(+), 35 deletions(-) create mode 100644 backend/app/core/patched_openai_compat_provider.py create mode 100644 backend/tests/test_patched_openai_compat_provider.py diff --git a/backend/app/core/llm_provider.py b/backend/app/core/llm_provider.py index ee3b952..9ab343c 100644 --- a/backend/app/core/llm_provider.py +++ b/backend/app/core/llm_provider.py @@ -2,9 +2,10 @@ from typing import Optional, Dict from nanobot.providers.azure_openai_provider import AzureOpenAIProvider from nanobot.providers.openai_codex_provider import OpenAICodexProvider -from nanobot.providers.openai_compat_provider import OpenAICompatProvider from nanobot.providers.registry import find_by_name +from app.core.patched_openai_compat_provider import PatchedOpenAICompatProvider + def normalize_provider_name(provider: Optional[str]) -> Optional[str]: if not provider: @@ -51,7 +52,7 @@ def build_llm_provider( extra_headers=extra_headers, ) - return OpenAICompatProvider( + return PatchedOpenAICompatProvider( api_key=api_key, api_base=api_base, default_model=model, diff --git a/backend/app/core/nanobot.py b/backend/app/core/nanobot.py index b784ba1..6a9827d 100644 --- a/backend/app/core/nanobot.py +++ b/backend/app/core/nanobot.py @@ -19,7 +19,6 @@ from nanobot.bus.events import OutboundMessage from nanobot.bus.queue import MessageBus from nanobot.config.loader import load_config from nanobot.cron.service import CronService -from nanobot.providers.openai_compat_provider import OpenAICompatProvider from nanobot.providers.openai_codex_provider import OpenAICodexProvider from nanobot.providers.azure_openai_provider import AzureOpenAIProvider from nanobot.providers.base import GenerationSettings @@ -32,6 +31,7 @@ from nanobot.config.schema import Config # or just import here if we are confident. # Given the structure, importing here should be fine as long as skills.py doesn't import nanobot.py. from app.api.skills import load_skills +from app.core.patched_openai_compat_provider import PatchedOpenAICompatProvider from app.services.llm_cache import get_llm_configs, get_active_llm_config from app.core.data_root import get_workspace_root @@ -45,6 +45,7 @@ class NanobotIntegration: self._started = False self._model_agent_cache: Dict[tuple[str | None, int | None], AgentLoop] = {} self._model_agent_lock = asyncio.Lock() + self._last_usage_by_session: Dict[str, Dict[str, Any]] = {} @staticmethod def _normalize_config_value(value: Any) -> Any: @@ -80,6 +81,28 @@ class NanobotIntegration: return content return str(response) + @staticmethod + def _normalize_usage(usage: Any) -> Dict[str, int] | None: + if not isinstance(usage, dict): + return None + normalized: Dict[str, int] = {} + prompt = int(usage.get("prompt_tokens", 0) or 0) + completion = int(usage.get("completion_tokens", 0) or 0) + total = int(usage.get("total_tokens", 0) or 0) + + # If total_tokens is missing or zero, calculate it + if total == 0: + total = prompt + completion + + normalized["prompt_tokens"] = prompt + normalized["completion_tokens"] = completion + normalized["total_tokens"] = total + return normalized if (prompt > 0 or completion > 0) else None + + def get_last_usage(self, session_id: str) -> Dict[str, int] | None: + usage = self._last_usage_by_session.get(session_id) + return dict(usage) if usage else None + def _need_custom_agent_for_target(self, target_config: Dict[str, Any]) -> bool: if not self.agent: return False @@ -220,7 +243,7 @@ class NanobotIntegration: extra_headers=extra_headers, ) - return OpenAICompatProvider( + return PatchedOpenAICompatProvider( api_key=api_key, api_base=api_base, default_model=model, @@ -407,6 +430,9 @@ class NanobotIntegration: on_progress=on_progress, on_stream=on_stream, ) + usage = self._normalize_usage(getattr(agent_to_use, "_last_usage", None)) + if usage: + self._last_usage_by_session[session_id] = usage return self._extract_response_text(response) def _normalize_session_messages(self, messages: List[Any]) -> List[dict[str, Any]]: diff --git a/backend/app/core/patched_openai_compat_provider.py b/backend/app/core/patched_openai_compat_provider.py new file mode 100644 index 0000000..e2b5bb5 --- /dev/null +++ b/backend/app/core/patched_openai_compat_provider.py @@ -0,0 +1,43 @@ +from __future__ import annotations + +from typing import Any + +from nanobot.providers.openai_compat_provider import OpenAICompatProvider + + +class PatchedOpenAICompatProvider(OpenAICompatProvider): + _MAX_COMPLETION_TOKEN_MODELS = ("gpt-5", "o1", "o3", "o4") + + def _build_kwargs( + self, + messages: list[dict[str, Any]], + tools: list[dict[str, Any]] | None, + model: str | None, + max_tokens: int, + temperature: float, + reasoning_effort: str | None, + tool_choice: str | dict[str, Any] | None, + ) -> dict[str, Any]: + kwargs = super()._build_kwargs( + messages=messages, + tools=tools, + model=model, + max_tokens=max_tokens, + temperature=temperature, + reasoning_effort=reasoning_effort, + tool_choice=tool_choice, + ) + + model_name = (model or self.default_model or "").lower() + spec = self._spec + supports_max_completion_tokens = bool( + spec and getattr(spec, "supports_max_completion_tokens", False) + ) + should_use_max_completion_tokens = supports_max_completion_tokens or any( + token in model_name for token in self._MAX_COMPLETION_TOKEN_MODELS + ) + + if should_use_max_completion_tokens and "max_tokens" in kwargs: + kwargs["max_completion_tokens"] = kwargs.pop("max_tokens") + + return kwargs diff --git a/backend/main.py b/backend/main.py index 65dd2c2..fdc82db 100644 --- a/backend/main.py +++ b/backend/main.py @@ -268,6 +268,7 @@ def _persist_assistant_enrichment( session_id: str, viz_payload: Optional[Dict[str, Any]] = None, artifacts: Optional[List[Dict[str, Any]]] = None, + usage: Optional[Dict[str, Any]] = None, ) -> None: if not nanobot_service.agent: return @@ -281,9 +282,25 @@ def _persist_assistant_enrichment( if artifacts: session.messages[-1]["artifacts"] = artifacts changed = True + if usage: + session.messages[-1]["usage"] = usage + changed = True if changed: nanobot_service.agent.sessions.save(session) + +def _extract_reasoning_content(session_messages: List[Dict[str, Any]]) -> str: + for message in reversed(session_messages): + if not isinstance(message, dict): + continue + if message.get("role") != "assistant": + continue + reasoning_content = message.get("reasoning_content") + if isinstance(reasoning_content, str) and reasoning_content.strip(): + return reasoning_content + break + return "" + @app.post("/nanobot/chat") async def nanobot_chat(request: ChatRequest): try: @@ -321,10 +338,12 @@ async def nanobot_chat(request: ChatRequest): artifacts = extract_artifacts(text, session_messages) viz_payload = current_viz_data.get() + usage = nanobot_service.get_last_usage(request.session_id) _persist_assistant_enrichment( session_id=request.session_id, viz_payload=viz_payload if isinstance(viz_payload, dict) else None, artifacts=artifacts, + usage=usage, ) payload = { @@ -334,6 +353,8 @@ async def nanobot_chat(request: ChatRequest): } if artifacts: payload["artifacts"] = artifacts + if usage: + payload["usage"] = usage return payload except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @@ -356,7 +377,9 @@ async def nanobot_chat_stream(request: ChatRequest): async def _on_progress(content: str, **kwargs: Any) -> None: if content: - await progress_queue.put(content) + payload: Dict[str, Any] = {"type": "progress", "content": content} + payload.update(kwargs) + await progress_queue.put(payload) async def _on_stream(delta: str) -> None: if delta: @@ -427,9 +450,10 @@ async def nanobot_chat_stream(request: ChatRequest): session = nanobot_service.agent.sessions.get_or_create(request.session_id) session_messages = session.messages artifacts = extract_artifacts(text, session_messages) + reasoning_content = _extract_reasoning_content(session_messages) - # Check again for viz payload after task completes if not sent yet viz_payload = current_viz_data.get() + usage = nanobot_service.get_last_usage(request.session_id) if viz_payload: try: current_hash = hash(( @@ -447,11 +471,16 @@ async def nanobot_chat_stream(request: ChatRequest): session_id=request.session_id, viz_payload=viz_payload if isinstance(viz_payload, dict) else None, artifacts=artifacts, + usage=usage, ) final_payload = {"type": "final", "content": text} + if reasoning_content: + final_payload["reasoning_content"] = reasoning_content if artifacts: final_payload["artifacts"] = artifacts + if usage: + final_payload["usage"] = usage yield f"data: {json.dumps(final_payload, ensure_ascii=False)}\n\n" yield f"data: {json.dumps({'type': 'done'}, ensure_ascii=False)}\n\n" except asyncio.CancelledError: diff --git a/backend/tests/test_chat_project_id.py b/backend/tests/test_chat_project_id.py index 26e470d..9c4c88c 100644 --- a/backend/tests/test_chat_project_id.py +++ b/backend/tests/test_chat_project_id.py @@ -98,3 +98,48 @@ def test_nanobot_chat_stream_syncs_project_id(monkeypatch) -> None: assert "stream-complete" in content assert calls == [{"session_key": "api:test-3", "project_id": 202}] assert process_kwargs and process_kwargs[0]["project_id"] == 202 + + +def test_nanobot_chat_stream_emits_reasoning_flags_and_final_reasoning(monkeypatch) -> None: + async def fake_process_message(*args, **kwargs): + on_progress = kwargs.get("on_progress") + on_stream = kwargs.get("on_stream") + if on_progress: + await on_progress("模型正在拆解问题", is_reasoning=True) + await on_progress("开始执行工具", tool_hint=True) + if on_stream: + await on_stream("answer-token") + return "final-answer" + + class _DummySession: + def __init__(self): + self.metadata = {} + self.messages = [ + {"role": "assistant", "content": "final-answer", "reasoning_content": "完整思考过程"} + ] + + class _DummySessions: + def get_or_create(self, _key): + return _DummySession() + + class _DummyAgent: + def __init__(self): + self.sessions = _DummySessions() + + async def collect_stream_chunks(response) -> list[str]: + chunks: list[str] = [] + async for chunk in response.body_iterator: + chunks.append(chunk.decode("utf-8") if isinstance(chunk, bytes) else chunk) + return chunks + + monkeypatch.setattr(main.nanobot_service, "process_message", fake_process_message) + monkeypatch.setattr(main.nanobot_service, "agent", _DummyAgent()) + + request = main.ChatRequest(message="hello", session_id="api:test-4", project_id=303) + response = asyncio.run(main.nanobot_chat_stream(request)) + chunks = asyncio.run(collect_stream_chunks(response)) + content = "".join(chunks) + + assert '"type": "progress", "content": "模型正在拆解问题", "is_reasoning": true' in content + assert '"type": "progress", "content": "开始执行工具", "tool_hint": true' in content + assert '"type": "final", "content": "final-answer", "reasoning_content": "完整思考过程"' in content diff --git a/backend/tests/test_patched_openai_compat_provider.py b/backend/tests/test_patched_openai_compat_provider.py new file mode 100644 index 0000000..e3cd3b4 --- /dev/null +++ b/backend/tests/test_patched_openai_compat_provider.py @@ -0,0 +1,62 @@ +import sys +from pathlib import Path + +BACKEND_ROOT = Path(__file__).resolve().parents[1] +REPO_ROOT = BACKEND_ROOT.parent +NANOBOT_ROOT = REPO_ROOT / "nanobot" +if str(BACKEND_ROOT) not in sys.path: + sys.path.insert(0, str(BACKEND_ROOT)) +if str(NANOBOT_ROOT) not in sys.path: + sys.path.insert(0, str(NANOBOT_ROOT)) + +from app.core.llm_provider import build_llm_provider +from app.core.nanobot import NanobotIntegration +from app.core.patched_openai_compat_provider import PatchedOpenAICompatProvider + + +def test_build_llm_provider_uses_max_completion_tokens_for_gpt5() -> None: + provider = build_llm_provider( + model="gpt-5.4-nano", + provider="openai", + api_key="test-key", + api_base="https://example.com/v1", + ) + + assert isinstance(provider, PatchedOpenAICompatProvider) + kwargs = provider._build_kwargs( + messages=[{"role": "user", "content": "hello"}], + tools=None, + model="gpt-5.4-nano", + max_tokens=5, + temperature=0, + reasoning_effort=None, + tool_choice=None, + ) + + assert kwargs["max_completion_tokens"] == 5 + assert "max_tokens" not in kwargs + + +def test_nanobot_provider_keeps_max_tokens_for_legacy_models() -> None: + integration = NanobotIntegration() + provider = integration._build_provider( + model="gpt-4o-mini", + provider_name="openai", + api_key="test-key", + api_base="https://example.com/v1", + extra_headers=None, + ) + + assert isinstance(provider, PatchedOpenAICompatProvider) + kwargs = provider._build_kwargs( + messages=[{"role": "user", "content": "hello"}], + tools=None, + model="gpt-4o-mini", + max_tokens=5, + temperature=0, + reasoning_effort=None, + tool_choice=None, + ) + + assert kwargs["max_tokens"] == 5 + assert "max_completion_tokens" not in kwargs diff --git a/frontend/src/components/ChatInterface.tsx b/frontend/src/components/ChatInterface.tsx index 6c293a6..25c73cb 100644 --- a/frontend/src/components/ChatInterface.tsx +++ b/frontend/src/components/ChatInterface.tsx @@ -1,6 +1,6 @@ import { useState, useRef, useEffect } from "react"; import { ScrollArea } from "@/components/ui/scroll-area"; -import { User, Loader2, ArrowUp, ChevronDown, Check, Square, Plus, Database, Wand2, Zap, CheckCircle2, Table, XCircle, Settings, ExternalLink, FileText, Download, Eye } from "lucide-react"; +import { User, Loader2, ArrowUp, ChevronDown, Check, Square, Plus, Database, Wand2, Zap, CheckCircle2, Table, XCircle, Settings, ExternalLink, FileText, Download, Eye, Copy } from "lucide-react"; import { api } from "@/lib/api"; import { type ChartSpec } from "@/store/visualizationStore"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; @@ -25,6 +25,11 @@ interface Message { progressLogs?: string[]; routeInfo?: string; reasoningContent?: string; + usage?: { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + }; artifacts?: MessageArtifact[]; } @@ -183,6 +188,8 @@ export function ChatInterface() { const [selectedSkillIds, setSelectedSkillIds] = useState([]); const [isMenuOpen, setIsMenuOpen] = useState(false); const [artifactPreview, setArtifactPreview] = useState(null); + const [collapsedThinkingByMessage, setCollapsedThinkingByMessage] = useState>({}); + const [thinkingCopiedByMessage, setThinkingCopiedByMessage] = useState>({}); const scrollRef = useRef(null); const location = useLocation(); const { currentProject } = useProjectStore(); @@ -372,6 +379,8 @@ export function ChatInterface() { role: m.role as 'user' | 'assistant', content: cleanContent, viz: m.viz ? buildMessageViz(m.viz) : undefined, + reasoningContent: typeof m.reasoning_content === "string" ? m.reasoning_content : undefined, + usage: m.usage, artifacts: normalizeArtifacts(m.artifacts), }; }); @@ -489,6 +498,22 @@ export function ChatInterface() { const selectedDataSourceName = availableDataSources.find(ds => ds.id === selectedDataSource)?.name || ""; const selectedSkills = availableSkills.filter(skill => selectedSkillIds.includes(skill.id)); + const isThinkingCollapsed = (messageId: string) => collapsedThinkingByMessage[messageId] ?? true; + const toggleThinkingCollapsed = (messageId: string) => { + setCollapsedThinkingByMessage((prev) => ({ ...prev, [messageId]: !(prev[messageId] ?? true) })); + }; + const copyThinkingContent = async (messageId: string, content: string) => { + if (!content.trim()) return; + try { + await navigator.clipboard.writeText(content); + setThinkingCopiedByMessage((prev) => ({ ...prev, [messageId]: true })); + window.setTimeout(() => { + setThinkingCopiedByMessage((prev) => ({ ...prev, [messageId]: false })); + }, 1200); + } catch (e) { + console.error("Failed to copy thinking content", e); + } + }; const renderActiveSelections = () => { if (!selectedDataSource && selectedSkills.length === 0) return null; @@ -613,9 +638,7 @@ export function ChatInterface() { if (msg.id !== assistantId) return msg; if (isReasoningToken) { - // 对于流式推理内容,拼接而不是创建新条目 - const currentReasoning = msg.reasoningContent || ""; - return { ...msg, reasoningContent: currentReasoning + text }; + return msg; } else { // 对于普通的阶段性日志,取消 8 条限制,允许滚动查看所有历史 const current = msg.progressLogs || []; @@ -673,6 +696,35 @@ export function ChatInterface() { let hasDonePayload = false; let rafPending = false; let renderedText = ""; + let reasoningBuffer = ""; + let reasoningRafPending = false; + + const flushReasoning = (force = false) => { + if (!reasoningBuffer) return; + if (force) { + const content = reasoningBuffer; + reasoningBuffer = ""; + setMessagesForSession(targetSessionKey, (prev) => + prev.map((msg) => + msg.id === assistantId ? { ...msg, reasoningContent: (msg.reasoningContent || "") + content } : msg + ) + ); + return; + } + if (reasoningRafPending) return; + reasoningRafPending = true; + requestAnimationFrame(() => { + reasoningRafPending = false; + if (!reasoningBuffer) return; + const content = reasoningBuffer; + reasoningBuffer = ""; + setMessagesForSession(targetSessionKey, (prev) => + prev.map((msg) => + msg.id === assistantId ? { ...msg, reasoningContent: (msg.reasoningContent || "") + content } : msg + ) + ); + }); + }; const flushAssistant = (force = false) => { if (streamedText === renderedText && !force) return; @@ -717,6 +769,7 @@ export function ChatInterface() { type: string; content?: string; is_reasoning?: boolean; + tool_hint?: boolean; sql?: string; result?: unknown; error?: string; @@ -724,6 +777,12 @@ export function ChatInterface() { reason?: string; chart?: { chart_spec?: ChartSpec | null; reasoning?: string; can_visualize?: boolean; chart_type?: string } | null; artifacts?: unknown; + reasoning_content?: string; + usage?: { + prompt_tokens: number; + completion_tokens: number; + total_tokens: number; + }; }; if (payload.type === "delta" && payload.content) { @@ -743,9 +802,13 @@ export function ChatInterface() { } if (payload.type === "progress" && payload.content) { - // 如果 progress 内容带有空格或者换行,并且不是典型的系统提示词,很可能这是 reasoning_content - // 为了安全起见,我们在后端应该加上 is_reasoning 标记,这里我们通过启发式或者统一拼接 - pushProgressLog(payload.content, payload.is_reasoning || false); + if (payload.is_reasoning || payload.tool_hint) { + const nextLine = payload.content.endsWith("\n") ? payload.content : `${payload.content}\n`; + reasoningBuffer += nextLine; + flushReasoning(false); + } else { + pushProgressLog(payload.content, false); + } } if (payload.type === "final") { @@ -753,12 +816,22 @@ export function ChatInterface() { if (typeof payload.content === "string") { streamedText = payload.content; } + if (typeof payload.reasoning_content === "string") { + reasoningBuffer = ""; + setMessagesForSession(targetSessionKey, (prev) => + prev.map((msg) => + msg.id === assistantId ? { ...msg, reasoningContent: payload.reasoning_content } : msg + ) + ); + } else { + flushReasoning(true); + } flushAssistant(true); pushProgressLog(t('answerGenerationCompleted')); const messageArtifacts = normalizeArtifacts(payload.artifacts); setMessagesForSession(targetSessionKey, (prev) => prev.map((msg) => - msg.id === assistantId ? { ...msg, content: typeof payload.content === "string" ? payload.content : msg.content || "", awaitingFirstToken: false, viz: streamedViz ?? msg.viz, artifacts: messageArtifacts.length > 0 ? messageArtifacts : msg.artifacts } : msg + msg.id === assistantId ? { ...msg, content: typeof payload.content === "string" ? payload.content : msg.content || "", awaitingFirstToken: false, viz: streamedViz ?? msg.viz, usage: payload.usage, artifacts: messageArtifacts.length > 0 ? messageArtifacts : msg.artifacts } : msg ) ); } @@ -783,6 +856,7 @@ export function ChatInterface() { } } + flushReasoning(true); flushAssistant(true); if (!streamedText && (hasFinalPayload || hasDonePayload)) { setMessagesForSession(targetSessionKey, (prev) => @@ -1046,6 +1120,14 @@ export function ChatInterface() { const isMessageGenerating = isLoading && msgIdx === messages.length - 1; const { markdown, reportHtml } = splitReportHtml(msg.content); const externalReportUrl = extractExternalReport(msg.content); + const fallbackThinkingLines = Array.from(new Set( + (msg.progressLogs || []).filter((log) => + log && + log !== t('requestSubmittedRouting') && + log !== t('answerGenerationCompleted') + ) + )); + const displayedThinkingContent = (msg.reasoningContent || "").trim() || fallbackThinkingLines.join("\n"); return (
{msg.role === "assistant" ? ( <> - {msg.reasoningContent && ( -
-
- - {t('thinkingProcess')} -
- {msg.reasoningContent} + {displayedThinkingContent && ( +
+ + {!isThinkingCollapsed(msg.id) && ( +
+ {displayedThinkingContent} +
+ )}
)} {msg.progressLogs && msg.progressLogs.length > 0 ? ( diff --git a/frontend/src/components/Sidebar.tsx b/frontend/src/components/Sidebar.tsx index 92963b4..cb94b47 100644 --- a/frontend/src/components/Sidebar.tsx +++ b/frontend/src/components/Sidebar.tsx @@ -140,10 +140,8 @@ function Section({ onRename, onTogglePinned, onToggleArchived, - onBatchDelete, activeKey, isSelectionMode, - setIsSelectionMode, selectedKeys, setSelectedKeys }: { @@ -153,10 +151,8 @@ function Section({ onRename: (key: string, currentTitle: string) => void; onTogglePinned: (key: string, pinned: boolean) => void; onToggleArchived: (key: string, archived: boolean) => void; - onBatchDelete: (keys: string[]) => void; activeKey: string | null; isSelectionMode: boolean; - setIsSelectionMode: (val: boolean) => void; selectedKeys: string[]; setSelectedKeys: (val: string[] | ((prev: string[]) => string[])) => void; }) { @@ -684,13 +680,11 @@ function SidebarBody() { items={activeSessions} onSelect={handleSelectSession} onDelete={handleDeleteSession} - onBatchDelete={handleBatchDelete} onRename={openRenameDialog} onTogglePinned={handleTogglePinned} onToggleArchived={handleToggleArchived} activeKey={activeSessionKey} isSelectionMode={activeSelectionMode} - setIsSelectionMode={setActiveSelectionMode} selectedKeys={activeSelectedKeys} setSelectedKeys={setActiveSelectedKeys} /> @@ -714,13 +708,11 @@ function SidebarBody() { items={archivedSessions} onSelect={handleSelectSession} onDelete={handleDeleteSession} - onBatchDelete={handleBatchDelete} onRename={openRenameDialog} onTogglePinned={handleTogglePinned} onToggleArchived={handleToggleArchived} activeKey={activeSessionKey} isSelectionMode={archivedSelectionMode} - setIsSelectionMode={setArchivedSelectionMode} selectedKeys={archivedSelectedKeys} setSelectedKeys={setArchivedSelectedKeys} /> diff --git a/frontend/src/i18n/locales/en.json b/frontend/src/i18n/locales/en.json index 65d1ba0..96a58fd 100644 --- a/frontend/src/i18n/locales/en.json +++ b/frontend/src/i18n/locales/en.json @@ -36,6 +36,10 @@ "processing": "Processing...", "processCompleted": "Completed", "thinkingProcess": "Thinking Process", + "thinkingTokens": "{{count}} tokens", + "thinkingCharCount": "{{count}} chars", + "expandThinking": "Expand", + "collapseThinking": "Collapse", "modelThinking": "Model is thinking, please wait...", "openReportInNewTab": "Open report in new tab", "artifactPreview": "File Preview", diff --git a/frontend/src/i18n/locales/zh.json b/frontend/src/i18n/locales/zh.json index b7af86a..c17629c 100644 --- a/frontend/src/i18n/locales/zh.json +++ b/frontend/src/i18n/locales/zh.json @@ -49,6 +49,10 @@ "processing": "正在处理中", "processCompleted": "处理完成", "thinkingProcess": "思考过程", + "thinkingTokens": "{{count}} tokens", + "thinkingCharCount": "{{count}} 字", + "expandThinking": "展开", + "collapseThinking": "收起", "modelThinking": "模型思考中,请稍候...", "openReportInNewTab": "在新标签页中打开分析报告", "artifactPreview": "文件预览", diff --git a/frontend/src/pages/Dashboard.tsx b/frontend/src/pages/Dashboard.tsx index c6101b1..a6169bc 100644 --- a/frontend/src/pages/Dashboard.tsx +++ b/frontend/src/pages/Dashboard.tsx @@ -9,7 +9,7 @@ import { ScrollArea } from "@/components/ui/scroll-area"; import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from "@/components/ui/table"; import { Popover, PopoverContent, PopoverTrigger } from "@/components/ui/popover"; import { Input } from "@/components/ui/input"; -import { X, Type, AlignLeft, AlignCenter, AlignRight, Bold, Italic, Underline } from "lucide-react"; +import { X, Type, Bold, Italic, Underline } from "lucide-react"; import { BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, LineChart, Line } from 'recharts'; import { VegaChart } from "@/components/VegaChart"; import 'react-grid-layout/css/styles.css'; diff --git a/frontend/src/pages/Login.tsx b/frontend/src/pages/Login.tsx index 02c757b..861e478 100644 --- a/frontend/src/pages/Login.tsx +++ b/frontend/src/pages/Login.tsx @@ -87,10 +87,8 @@ export function Login() {
- - + + i18n.changeLanguage('zh')} className={i18n.language === 'zh' ? 'bg-zinc-100' : ''}> @@ -186,4 +184,4 @@ export function Login() {
); -} \ No newline at end of file +}