From aa84172ca4889a9953d63924387b49fd9aaa165a Mon Sep 17 00:00:00 2001 From: iven Date: Thu, 23 Apr 2026 14:51:47 +0800 Subject: [PATCH] =?UTF-8?q?refactor(panel):=20=E7=A7=BB=E9=99=A4=20Agent?= =?UTF-8?q?=20tab=20=E2=80=94=20=E8=B7=A8=E4=BC=9A=E8=AF=9D=E8=BA=AB?= =?UTF-8?q?=E4=BB=BD=E7=94=B1=20soul.md=20=E6=8E=A5=E7=AE=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Agent tab 展示的信息对用户无实际作用,身份记忆已通过 soul.md → pre_conversation_hook 实现跨会话。移除 Agent tab (简洁+专业模式),清理 ~280 行 dead code。 --- desktop/src/components/RightPanel.tsx | 545 +------------------------- wiki/log.md | 7 + wiki/memory.md | 1 + 3 files changed, 15 insertions(+), 538 deletions(-) diff --git a/desktop/src/components/RightPanel.tsx b/desktop/src/components/RightPanel.tsx index d43f49d..eb841ea 100644 --- a/desktop/src/components/RightPanel.tsx +++ b/desktop/src/components/RightPanel.tsx @@ -1,21 +1,17 @@ -import { ReactNode, useCallback, useEffect, useMemo, useRef, useState } from 'react'; +import { ReactNode, useEffect, useMemo, useRef, useState } from 'react'; import { useShallow } from 'zustand/react/shallow'; import { motion } from 'framer-motion'; import { getStoredGatewayUrl } from '../lib/gateway-client'; import { useConnectionStore } from '../store/connectionStore'; import { useAgentStore, type PluginStatus } from '../store/agentStore'; import { useConfigStore } from '../store/configStore'; -import { toChatAgent, useChatStore, type CodeBlock } from '../store/chatStore'; +import { useChatStore, type CodeBlock } from '../store/chatStore'; import { useConversationStore } from '../store/chat/conversationStore'; -import { intelligenceClient, type IdentitySnapshot } from '../lib/intelligence-client'; -import { invoke } from '@tauri-apps/api/core'; import { listen, type UnlistenFn } from '@tauri-apps/api/event'; -import type { AgentInfo } from '../lib/kernel-types'; import { Wifi, WifiOff, Bot, BarChart3, Plug, RefreshCw, - MessageSquare, Cpu, FileText, User, Activity, Brain, - Shield, Sparkles, List, Network, Dna, History, - ChevronDown, ChevronUp, RotateCcw, AlertCircle, Loader2, + MessageSquare, Cpu, FileText, Activity, Brain, + Shield, Sparkles, List, Network, Dna, ConciergeBell, } from 'lucide-react'; import { ButlerPanel } from './ButlerPanel'; @@ -86,7 +82,7 @@ import { IdentityChangeProposalPanel } from './IdentityChangeProposal'; import { CodeSnippetPanel, type CodeSnippet } from './CodeSnippetPanel'; import { cardHover, defaultTransition } from '../lib/animations'; import { Button, Badge } from './ui'; -import { getPersonalityById } from '../lib/personality-presets'; + import { silentErrorHandler } from '../lib/error-utils'; interface RightPanelProps { @@ -110,12 +106,10 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) { const updateClone = useAgentStore((s) => s.updateClone); // Config store - const workspaceInfo = useConfigStore((s) => s.workspaceInfo); const quickConfig = useConfigStore((s) => s.quickConfig); // Use shallow selector for message stats to avoid re-rendering during streaming. // Counts only change when messages are added/removed, not when content is appended. - const setCurrentAgent = useChatStore((s) => s.setCurrentAgent); const { messageCount, userMsgCount, assistantMsgCount, toolCallCount } = useChatStore( useShallow((s) => ({ messageCount: s.messages.length, @@ -133,36 +127,12 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) { const messages = stableMessagesRef.current; const currentModel = useConversationStore((s) => s.currentModel); const currentAgent = useConversationStore((s) => s.currentAgent); - const [activeTab, setActiveTab] = useState<'status' | 'files' | 'agent' | 'memory' | 'reflection' | 'autonomy' | 'evolution' | 'butler'>('status'); + const [activeTab, setActiveTab] = useState<'status' | 'files' | 'memory' | 'reflection' | 'autonomy' | 'evolution' | 'butler'>('status'); const [memoryViewMode, setMemoryViewMode] = useState<'list' | 'graph'>('list'); - const [isEditingAgent, setIsEditingAgent] = useState(false); - const [agentDraft, setAgentDraft] = useState(null); - - // Identity snapshot state - const [snapshots, setSnapshots] = useState([]); - const [snapshotsExpanded, setSnapshotsExpanded] = useState(false); - const [snapshotsLoading, setSnapshotsLoading] = useState(false); - const [snapshotsError, setSnapshotsError] = useState(null); - const [restoringSnapshotId, setRestoringSnapshotId] = useState(null); - const [confirmRestoreId, setConfirmRestoreId] = useState(null); - - // UserProfile from memory store (dynamic, learned from conversations) - const [userProfile, setUserProfile] = useState | null>(null); const connected = connectionState === 'connected'; - const selectedClone = useMemo( - () => clones.find((clone) => clone.id === currentAgent?.id), - [clones, currentAgent?.id] - ); - const focusAreas = selectedClone?.scenarios?.length ? selectedClone.scenarios : ['coding', 'writing', 'research', 'product', 'data']; - const bootstrapFiles = selectedClone?.bootstrapFiles || []; const gatewayUrl = quickConfig.gatewayUrl || getStoredGatewayUrl(); - useEffect(() => { - if (!selectedClone || isEditingAgent) return; - setAgentDraft(createAgentDraft(selectedClone, currentModel)); - }, [selectedClone, currentModel, isEditingAgent]); - // Load data when connected useEffect(() => { if (connected) { @@ -172,31 +142,6 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) { } }, [connected]); - // Fetch UserProfile from agent data (includes memory-learned profile) - useEffect(() => { - if (!currentAgent?.id) return; - invoke('agent_get', { agentId: currentAgent.id }) - .then(data => setUserProfile(data?.userProfile ?? null)) - .catch(() => setUserProfile(null)); - }, [currentAgent?.id]); - - // Listen for profile updates after conversations (fired after memory extraction completes) - // This single handler handles both userProfile refresh and clone name refresh - useEffect(() => { - const handler = async (e: Event) => { - const detail = (e as CustomEvent).detail; - if (detail?.agentId === currentAgent?.id && currentAgent?.id) { - invoke('agent_get', { agentId: currentAgent.id }) - .then(data => setUserProfile(data?.userProfile ?? null)) - .catch(() => {}); - // Refresh clones data so selectedClone (name, role, nickname, etc.) stays current - await loadClones(); - } - }; - window.addEventListener('zclaw:agent-profile-updated', handler); - return () => window.removeEventListener('zclaw:agent-profile-updated', handler); - }, [currentAgent?.id, clones]); - // Listen for Tauri identity update events (from Rust post_conversation_hook) // When agent name changes in soul.md, update AgentConfig.name and refresh panel useEffect(() => { @@ -218,84 +163,7 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) { connect().catch(silentErrorHandler('RightPanel')); }; - const handleStartEdit = () => { - if (!selectedClone) return; - setAgentDraft(createAgentDraft(selectedClone, currentModel)); - setIsEditingAgent(true); - }; - - const handleCancelEdit = () => { - if (selectedClone) { - setAgentDraft(createAgentDraft(selectedClone, currentModel)); - } - setIsEditingAgent(false); - }; - - const handleSaveAgent = async () => { - if (!selectedClone || !agentDraft || !agentDraft.name.trim()) return; - const updatedClone = await updateClone(selectedClone.id, { - name: agentDraft.name.trim(), - role: agentDraft.role.trim() || undefined, - nickname: agentDraft.nickname.trim() || undefined, - model: agentDraft.model.trim() || undefined, - scenarios: agentDraft.scenarios.split(',').map((item) => item.trim()).filter(Boolean), - workspaceDir: agentDraft.workspaceDir.trim() || undefined, - userName: agentDraft.userName.trim() || undefined, - userRole: agentDraft.userRole.trim() || undefined, - restrictFiles: agentDraft.restrictFiles, - privacyOptIn: agentDraft.privacyOptIn, - }); - if (updatedClone) { - setCurrentAgent(toChatAgent(updatedClone)); - setAgentDraft(createAgentDraft(updatedClone, updatedClone.model || currentModel)); - setIsEditingAgent(false); - } - }; - - const loadSnapshots = useCallback(async () => { - const agentId = currentAgent?.id; - if (!agentId) return; - setSnapshotsLoading(true); - setSnapshotsError(null); - try { - const result = await intelligenceClient.identity.getSnapshots(agentId, 20); - setSnapshots(result); - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - setSnapshotsError(`加载快照失败: ${msg}`); - } finally { - setSnapshotsLoading(false); - } - }, [currentAgent?.id]); - - const handleRestoreSnapshot = useCallback(async (snapshotId: string) => { - const agentId = currentAgent?.id; - if (!agentId) return; - setRestoringSnapshotId(snapshotId); - setSnapshotsError(null); - setConfirmRestoreId(null); - try { - await intelligenceClient.identity.restoreSnapshot(agentId, snapshotId); - await loadSnapshots(); - } catch (err) { - const msg = err instanceof Error ? err.message : String(err); - setSnapshotsError(`回滚失败: ${msg}`); - } finally { - setRestoringSnapshotId(null); - } - }, [currentAgent?.id, loadSnapshots]); - - // Load snapshots when agent tab is active and agent changes - useEffect(() => { - if (activeTab === 'agent' && currentAgent?.id) { - loadSnapshots(); - } - }, [activeTab, currentAgent?.id, loadSnapshots]); - const runtimeSummary = connected ? '已连接' : connectionState === 'connecting' ? '连接中...' : connectionState === 'reconnecting' ? '重连中...' : '未连接'; - const userNameDisplay = selectedClone?.userName || quickConfig.userName || 'User'; - const userAddressing = selectedClone?.userName || quickConfig.userName || 'User'; - const localTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone || '系统时区'; // Extract code blocks from all messages (both from codeBlocks property and content parsing) const codeSnippets = useMemo((): CodeSnippet[] => { @@ -339,7 +207,7 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) { {/* 顶部工具栏 - Tab 栏 */}
{simpleMode ? ( - /* 简洁模式: 仅 状态 / Agent / 管家 */ + /* 简洁模式: 仅 状态 / 管家 */
} label="状态" /> - setActiveTab('agent')} - icon={} - label="Agent" - /> setActiveTab('butler')} @@ -370,12 +232,6 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) { icon={} label="状态" /> - setActiveTab('agent')} - icon={} - label="Agent" - /> setActiveTab('files')} @@ -491,289 +347,6 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) { ) : activeTab === 'butler' ? ( - ) : activeTab === 'agent'? ( -
- -
-
-
- {selectedClone?.emoji ? ( - {selectedClone.emoji} - ) : ( - 🦞 - )} -
-
-
- {selectedClone?.name || currentAgent?.name || '全能助手'} - {selectedClone?.personality ? ( - - {getPersonalityById(selectedClone.personality)?.label || selectedClone.personality} - - ) : ( - - 友好亲切 - - )} -
-
{selectedClone?.role || '全能型 AI 助手'}
-
-
- {selectedClone ? ( - isEditingAgent ? ( -
- - -
- ) : ( - - ) - ) : null} -
-
- - -
关于我
- {isEditingAgent && agentDraft ? ( -
- setAgentDraft({ ...agentDraft, name: value })} /> - setAgentDraft({ ...agentDraft, role: value })} /> - setAgentDraft({ ...agentDraft, nickname: value })} /> - setAgentDraft({ ...agentDraft, model: value })} /> -
- ) : ( -
- - - - -
- )} -
- - -
我眼中的你
- {isEditingAgent && agentDraft ? ( -
- setAgentDraft({ ...agentDraft, userName: value })} /> - setAgentDraft({ ...agentDraft, userRole: value })} /> - setAgentDraft({ ...agentDraft, scenarios: value })} placeholder="编程, 研究" /> - setAgentDraft({ ...agentDraft, workspaceDir: value })} /> - setAgentDraft({ ...agentDraft, restrictFiles: value })} /> - setAgentDraft({ ...agentDraft, privacyOptIn: value })} /> -
- ) : ( -
- - - -
-
专注
-
- {focusAreas.map((item) => ( - {item} - ))} -
-
- - - - - {/* Dynamic: UserProfile data (from conversation learning) */} - {userProfile && ( -
-
对话中了解到的
- {userProfile.industry ? ( - - ) : null} - {userProfile.role ? ( - - ) : null} - {userProfile.communicationStyle ? ( - - ) : null} - {Array.isArray(userProfile.recentTopics) && (userProfile.recentTopics as string[]).length > 0 ? ( - - ) : null} -
- )} -
- )} -
- - -
-
引导文件
- - {selectedClone?.bootstrapReady ? '已生成' : '未生成'} - -
-
- {bootstrapFiles.length > 0 ? bootstrapFiles.map((file) => ( -
-
- {file.name} - - {file.exists ? '已存在' : '缺失'} - -
-
{file.path}
-
- )) : ( -

该 Agent 尚未生成引导文件。

- )} -
-
- - {/* 历史快照 */} - - - - {snapshotsExpanded && ( -
- {snapshotsError && ( -
- - {snapshotsError} -
- )} - - {snapshotsLoading ? ( -
- - 加载中... -
- ) : snapshots.length === 0 ? ( -
- 暂无快照记录 -
- ) : ( - snapshots.map((snap) => { - const isRestoring = restoringSnapshotId === snap.id; - const isConfirming = confirmRestoreId === snap.id; - const timeLabel = formatSnapshotTime(snap.timestamp); - - return ( -
-
- -
-
-
- {timeLabel} - {isConfirming ? ( -
- - -
- ) : ( - - )} -
-

- {snap.reason || '自动快照'} -

-
-
- ); - }) - )} -
- )} -
-
) : activeTab === 'files' ? (
@@ -997,107 +570,3 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) { ); } -function AgentRow({ label, value }: { label: string; value: string }) { - return ( -
-
{label}
-
{value}
-
- ); -} - -type AgentDraft = { - name: string; - role: string; - nickname: string; - model: string; - scenarios: string; - workspaceDir: string; - userName: string; - userRole: string; - restrictFiles: boolean; - privacyOptIn: boolean; -}; - -function createAgentDraft( - clone: { - name: string; - role?: string; - nickname?: string; - model?: string; - scenarios?: string[]; - workspaceDir?: string; - userName?: string; - userRole?: string; - restrictFiles?: boolean; - privacyOptIn?: boolean; - }, - currentModel: string -): AgentDraft { - return { - name: clone.name || '', - role: clone.role || '', - nickname: clone.nickname || '', - model: clone.model || currentModel, - scenarios: clone.scenarios?.join(', ') || '', - workspaceDir: clone.workspaceDir || '~/.zclaw/zclaw-workspace', - userName: clone.userName || '', - userRole: clone.userRole || '', - restrictFiles: clone.restrictFiles ?? true, - privacyOptIn: clone.privacyOptIn ?? false, - }; -} - -function AgentInput({ - label, - value, - onChange, - placeholder, -}: { - label: string; - value: string; - onChange: (value: string) => void; - placeholder?: string; -}) { - return ( - - ); -} - -function AgentToggle({ - label, - checked, - onChange, -}: { - label: string; - checked: boolean; - onChange: (value: boolean) => void; -}) { - return ( - - ); -} - -function formatSnapshotTime(timestamp: string): string { - const now = Date.now(); - const then = new Date(timestamp).getTime(); - const diff = now - then; - - if (diff < 60000) return '刚刚'; - if (diff < 3600000) return `${Math.floor(diff / 60000)} 分钟前`; - if (diff < 86400000) return `${Math.floor(diff / 3600000)} 小时前`; - if (diff < 604800000) return `${Math.floor(diff / 86400000)} 天前`; - return new Date(timestamp).toLocaleDateString('zh-CN'); -} diff --git a/wiki/log.md b/wiki/log.md index b6bbb24..d455148 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -9,6 +9,13 @@ tags: [log, history] > Append-only 操作记录。格式: `## [日期] 类型 | 描述` +## [2026-04-23] fix | Agent 命名检测重构+跨会话记忆修复+Agent tab 移除 +- **fix(desktop)**: `detectAgentNameSuggestion` 从 6 个固定正则改为 trigger+extract 两步法 (10 个 trigger) +- **fix(desktop)**: 名字检测从 memory extraction 解耦 — 502 不再阻断面板刷新 +- **fix(src-tauri)**: `agent_update` 同步写入 soul.md — config.name → system prompt 断链修复 +- **refactor(desktop)**: 移除 Agent tab (简洁模式/专业模式),清理 dead code (~280 行) +- **验证**: cargo check 0 error, tsc --noEmit 0 error + ## [2026-04-23] fix | 身份信号提取与持久化 — 对话中起名跨会话记忆+面板刷新 - **fix(zclaw-growth)**: ProfileSignals 增加 agent_name/user_name 字段 + 提取提示词扩展 + 解析器+回退逻辑 - **fix(zclaw-runtime)**: 身份信号存入 VikingStorage (importance=8) diff --git a/wiki/memory.md b/wiki/memory.md index 78557fd..de12ad5 100644 --- a/wiki/memory.md +++ b/wiki/memory.md @@ -133,6 +133,7 @@ tags: [module, memory, fts5, growth] | 日期 | 变更 | 关联 | |------|------|------| +| 2026-04-23 | agent_update 同步写 soul.md + 命名检测解耦 memory extraction + Agent tab 移除 | commit 394cb66+0bb5265+1c00290 | | 2026-04-23 | 身份信号提取: ProfileSignals+agent_name/user_name + VikingStorage identity 存储 + soul.md 写回 | commit 08812e5+e64a3ea | | 2026-04-22 | 跨会话记忆断裂修复: profile_store 连接 + 双数据库统一 + 诊断日志 | commit adf0251 | | 2026-04-22 | Wiki 5-section 重构: 363→~190 行,详细逻辑归档 | wiki/ |