From 769bfdf5d63e18e2b5a6c543f6f32fc0d192290a Mon Sep 17 00:00:00 2001 From: iven Date: Sat, 4 Apr 2026 01:31:28 +0800 Subject: [PATCH] =?UTF-8?q?fix(desktop):=20=E4=BF=AE=E5=A4=8D=2010=20?= =?UTF-8?q?=E4=B8=AA=20agent=20=E5=AF=B9=E8=AF=9D=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E5=8F=91=E7=8E=B0=E7=9A=84=E7=BC=BA=E9=99=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - BUG-001+002 P0: 模型选择器合并 SaaS 可用模型列表 - BUG-003 P1: 修复 relay 错误消息重复显示 - BUG-005 P1: 设置页面显示实际连接模式和地址 - BUG-006 P2: 统一 UI 语言为中文 - BUG-009 P3: 错误时隐藏建议按钮 - BUG-010 P3: 密码切换按钮添加 aria-label Co-Authored-By: Claude Opus 4.6 --- desktop/src/components/ChatArea.tsx | 23 +++++++++++++++++---- desktop/src/components/LoginPage.tsx | 1 + desktop/src/components/Settings/General.tsx | 6 +++++- desktop/src/components/ui/EmptyState.tsx | 8 +++---- desktop/src/store/chat/streamStore.ts | 2 +- 5 files changed, 30 insertions(+), 10 deletions(-) diff --git a/desktop/src/components/ChatArea.tsx b/desktop/src/components/ChatArea.tsx index a182b84..df9d6ae 100644 --- a/desktop/src/components/ChatArea.tsx +++ b/desktop/src/components/ChatArea.tsx @@ -7,6 +7,7 @@ import { useArtifactStore } from '../store/chat/artifactStore'; import { useConnectionStore } from '../store/connectionStore'; import { useAgentStore } from '../store/agentStore'; import { useConfigStore } from '../store/configStore'; +import { useSaaSStore } from '../store/saasStore'; import { type UnlistenFn } from '@tauri-apps/api/event'; import { safeListenEvent } from '../lib/safe-tauri'; import { Paperclip, SquarePen, ArrowUp, MessageSquare, Download, X, FileText, Image as ImageIcon, Search } from 'lucide-react'; @@ -63,7 +64,21 @@ export function ChatArea() { const connectionState = useConnectionStore((s) => s.connectionState); const { activeClassroom, classroomOpen, closeClassroom, generating, progressPercent, progressActivity, error: classroomError, clearError: clearClassroomError } = useClassroomStore(); const clones = useAgentStore((s) => s.clones); - const models = useConfigStore((s) => s.models); + const configModels = useConfigStore((s) => s.models); + const saasModels = useSaaSStore((s) => s.availableModels); + const isLoggedIn = useSaaSStore((s) => s.isLoggedIn); + + // Merge models: SaaS available models take priority when logged in + const models = useMemo(() => { + if (isLoggedIn && saasModels.length > 0) { + return saasModels.map(m => ({ + id: m.alias || m.id, + name: m.alias || m.id, + provider: m.provider_id, + })); + } + return configModels; + }, [isLoggedIn, saasModels, configModels]); const [input, setInput] = useState(''); const [pendingFiles, setPendingFiles] = useState([]); @@ -418,8 +433,8 @@ export function ChatArea() { ) : ( } - title="Welcome to ZCLAW" - description={connected ? 'Send a message to start the conversation.' : 'Please connect to Gateway first in Settings.'} + title="欢迎使用 ZCLAW" + description={connected ? '发送消息开始对话。' : '请先在设置中连接 Gateway。'} /> )} @@ -457,7 +472,7 @@ export function ChatArea() {
{/* Suggestion chips */} - {!isStreaming && suggestions.length > 0 && ( + {!isStreaming && suggestions.length > 0 && !messages.some(m => m.error) && ( { setInput(text); textareaRef.current?.focus(); }} diff --git a/desktop/src/components/LoginPage.tsx b/desktop/src/components/LoginPage.tsx index 413ffb3..40c65a4 100644 --- a/desktop/src/components/LoginPage.tsx +++ b/desktop/src/components/LoginPage.tsx @@ -440,6 +440,7 @@ export function LoginPage() { onClick={() => setShowPassword(!showPassword)} className="absolute right-3 top-1/2 -translate-y-1/2 text-white/30 hover:text-white/60 cursor-pointer" tabIndex={-1} + aria-label={showPassword ? '隐藏密码' : '显示密码'} > {showPassword ? : } diff --git a/desktop/src/components/Settings/General.tsx b/desktop/src/components/Settings/General.tsx index 053e71c..2958e62 100644 --- a/desktop/src/components/Settings/General.tsx +++ b/desktop/src/components/Settings/General.tsx @@ -2,6 +2,7 @@ import { useState, useEffect } from 'react'; import { useConnectionStore } from '../../store/connectionStore'; import { useConfigStore } from '../../store/configStore'; import { useConversationStore } from '../../store/chat/conversationStore'; +import { useSaaSStore } from '../../store/saasStore'; import { getStoredGatewayToken, setStoredGatewayToken } from '../../lib/gateway-client'; import { silentErrorHandler } from '../../lib/error-utils'; @@ -22,6 +23,7 @@ export function General() { const connected = connectionState === 'connected'; const connecting = connectionState === 'connecting' || connectionState === 'reconnecting'; + const saasUrl = useSaaSStore((s) => s.saasUrl); // 同步主题设置 useEffect(() => { @@ -97,7 +99,9 @@ export function General() {
地址 - ws://127.0.0.1:50051 + + {gatewayVersion === 'saas-relay' && saasUrl ? saasUrl : 'ws://127.0.0.1:50051'} +
Token diff --git a/desktop/src/components/ui/EmptyState.tsx b/desktop/src/components/ui/EmptyState.tsx index 24d689d..fb9e40f 100644 --- a/desktop/src/components/ui/EmptyState.tsx +++ b/desktop/src/components/ui/EmptyState.tsx @@ -100,8 +100,8 @@ export function EmptyConversations({ action, className, size }: PrebuiltEmptySta return ( } - title="No conversations" - description="Your conversation history will appear here." + title="暂无对话" + description="你的对话历史将显示在这里。" action={action} className={className} size={size} @@ -177,8 +177,8 @@ export function EmptyAgents({ action, className, size }: PrebuiltEmptyStateProps * Empty state for welcome screen. */ export function WelcomeEmptyState({ - title = "Welcome to ZCLAW", - description = "Send a message to start the conversation.", + title = "欢迎使用 ZCLAW", + description = "发送消息开始对话。", connected = true, action, className, diff --git a/desktop/src/store/chat/streamStore.ts b/desktop/src/store/chat/streamStore.ts index 7e79a96..5794c52 100644 --- a/desktop/src/store/chat/streamStore.ts +++ b/desktop/src/store/chat/streamStore.ts @@ -410,7 +410,7 @@ export const useStreamStore = create()( _chat?.updateMessages(msgs => msgs.map(m => m.id === assistantId - ? { ...m, content: `⚠️ ${error}`, streaming: false, error } + ? { ...m, content: '', streaming: false, error } : m.role === 'user' && m.optimistic && m.timestamp.getTime() >= streamStartTime ? { ...m, optimistic: false } : m