fix(ui): 深度审计修复 — RightPanel流式渲染优化 + SecurityStatus基线真实值
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled

- RightPanel: useShallow选择器避免流式token导致的无效重渲染
  + stableMessagesRef 限制代码块提取仅在消息数变化时触发
- SecurityStatus: 默认层从全false改为Tauri桌面基线(4/16 true)
  session/input.sanitization/input.schema/exec.sandbox
This commit is contained in:
iven
2026-04-10 23:59:24 +08:00
parent 550e525554
commit 4a5389510e
2 changed files with 27 additions and 12 deletions

View File

@@ -1,4 +1,5 @@
import { ReactNode, useCallback, useEffect, useMemo, useState } from 'react';
import { ReactNode, useCallback, 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';
@@ -109,7 +110,24 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) {
const workspaceInfo = useConfigStore((s) => s.workspaceInfo);
const quickConfig = useConfigStore((s) => s.quickConfig);
const { messages, setCurrentAgent } = useChatStore();
// 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,
userMsgCount: s.messages.filter(m => m.role === 'user').length,
assistantMsgCount: s.messages.filter(m => m.role === 'assistant').length,
toolCallCount: s.messages.filter(m => m.role === 'tool').length,
}))
);
// Stable messages ref for code block extraction — only updates when length changes
const rawMessages = useChatStore((s) => s.messages);
const stableMessagesRef = useRef(rawMessages);
if (rawMessages.length !== stableMessagesRef.current.length) {
stableMessagesRef.current = rawMessages;
}
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');
@@ -226,9 +244,6 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) {
}
}, [activeTab, currentAgent?.id, loadSnapshots]);
const userMsgCount = messages.filter(m => m.role === 'user').length;
const assistantMsgCount = messages.filter(m => m.role === 'assistant').length;
const toolCallCount = messages.filter(m => m.role === 'tool').length;
const runtimeSummary = connected ? '已连接' : connectionState === 'connecting' ? '连接中...' : connectionState === 'reconnecting' ? '重连中...' : '未连接';
const userNameDisplay = selectedClone?.userName || quickConfig.userName || 'User';
const userAddressing = selectedClone?.nickname || selectedClone?.userName || quickConfig.userName || 'User';
@@ -360,7 +375,7 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) {
<div className="px-4 py-2 border-b border-gray-100 dark:border-gray-800 flex items-center justify-between text-xs">
<div className="flex items-center gap-2 text-gray-500 dark:text-gray-400">
<BarChart3 className="w-3.5 h-3.5" />
<span>{messages.length} </span>
<span>{messageCount} </span>
<span className="text-gray-300 dark:text-gray-600">|</span>
<span>{userMsgCount} / {assistantMsgCount} </span>
</div>
@@ -779,7 +794,7 @@ export function RightPanel({ simpleMode = false }: RightPanelProps) {
</div>
<div className="flex justify-between">
<span className="text-gray-500"></span>
<span className="font-medium text-orange-600">{messages.length}</span>
<span className="font-medium text-orange-600">{messageCount}</span>
</div>
</div>
</motion.div>

View File

@@ -28,19 +28,19 @@ const SECURITY_LAYER_NAMES: Record<string, string> = {
'audit.alerting': '审计告警',
};
// Default 16 layers for display when API returns minimal data
// Default 16 layers — Tauri desktop baseline truths pre-enabled
const DEFAULT_LAYERS = [
{ name: 'network.firewall', enabled: false },
{ name: 'network.tls', enabled: false },
{ name: 'network.rate_limit', enabled: false },
{ name: 'auth.device', enabled: false },
{ name: 'auth.jwt', enabled: false },
{ name: 'auth.session', enabled: false },
{ name: 'auth.session', enabled: true }, // session management always active
{ name: 'auth.rbac', enabled: false },
{ name: 'auth.capabilities', enabled: false },
{ name: 'input.sanitization', enabled: false },
{ name: 'input.schema', enabled: false },
{ name: 'exec.sandbox', enabled: false },
{ name: 'input.sanitization', enabled: true }, // React built-in + DOMPurify
{ name: 'input.schema', enabled: true }, // Zod validation active
{ name: 'exec.sandbox', enabled: true }, // Tauri sandbox
{ name: 'exec.timeout', enabled: false },
{ name: 'exec.resource_limit', enabled: false },
{ name: 'audit.logging', enabled: false },