【上下文压缩机制修复】 - engine.ts 新增 lastRealInputTokens 记录 LLM 返回的真实输入 token,压缩判断取 max(估算值, 真实值),避免估算偏低导致不压缩但 API 413 - 修复 effectiveContextWindow 缺少默认值导致 compressionThreshold 变 NaN、压缩永不触发的 bug(添加 ?? 128_000 兜底) - compressMessages 保留区从固定 10 条改为按 token 预算动态截断(50% 上下文窗口) - 二次截断 charsPerToken 从 2 调整为 1.0,与 CJK_TOKEN_RATIO 一致 - 压缩后重置 lastRealInputTokens,避免跨迭代污染 - 每个 run 开始时重置 lastRealInputTokens 【前端 Token 显示修复】 - 区分"累计消耗"和"上下文占用"语义——之前 totalTokens(累计) / contextWindow(单次窗口) 得出无意义百分比 - TokenUsage.tsx 上下文占用改用 lastInputTokens,新增压缩节省行(绿色,仅当 > 0 时显示) - agent-store.ts TokenUsage 接口新增 lastInputTokens 和 lastCompressedSaved 字段,4 处初始值统一更新 - useAgentStream.ts usage 事件 lastInputTokens 替换不累加,compressed 事件通过 streamEvent 接收 savedTokens - handlers.ts onCompressed 同时发 toast + streamEvent,解决"压缩触发但前端 token 显示不降"缺陷 - 旧数据兼容使用 ?? 0,保证历史会话加载不崩溃 【MCP 工具就绪竞态修复】 - 修复输入框永久显示"工具加载中"的竞态条件:MCP initialize 几乎立即 resolve(connectServer 不 await),tools:ready 事件在前端监听器注册前已发出 - main.ts 维护 toolsReady 标志 + 注册 tools:isReady IPC handler 查询当前状态 - preload.ts 暴露 tools.isReady() 方法 - App.tsx 注册 onReady 监听器后立即查询 isReady(),无论事件是否错过都能恢复正确状态 【记忆系统加固】 - consolidator.ts 新增 runningPromise + waitForCompletion(35s),before-quit 等待固化完成,防止退出时异步 consolidate 数据丢失 - 固化到 MEMORY.md 的同时写入 semantic_memories 表,解决双轨存储无交叉验证问题 - manager.ts tokenize 按中英文标点切分子句后再做 bigram,优化中文分词 - 清理正则冗余括号 【SOUL.md 降级处理】 - context-builder.ts SOUL.md 为空或不存在时降级到默认身份,向前端发 toast 提示用户 - 新增 fallbackRoleNotified 去重标志,仅首次降级通知,避免每次发消息都弹 toast - SOUL.md 恢复内容时重置标志 【Token 估算调整】 - token-estimator.ts CJK_TOKEN_RATIO 从 1.5 调整为 1.0 【MCP 异步初始化】 - main.ts MCP 完成后广播 tools:ready 事件,前端 UI 据以控制输入框可用性 - preload.ts + global.d.ts 暴露 tools.onReady() 监听器 - App.tsx + ChatInput.tsx toolsReady 状态控制输入框 【版本号】 - package.json + package-lock.json 从 0.3.16 升级到 0.3.18
114 lines
5.1 KiB
TypeScript
114 lines
5.1 KiB
TypeScript
/**
|
|
* MetonaAI Desktop — 根组件
|
|
*
|
|
* 三栏布局:Header + [Sidebar | Chat | DetailPanel] + StatusBar
|
|
* + ToastContainer + SettingsModal + OnboardingWizard
|
|
*
|
|
* 连接所有 Store,注册快捷键,初始化主题。
|
|
*/
|
|
|
|
import { ThemeProvider, CssBaseline } from '@mui/material';
|
|
import { useEffect } from 'react';
|
|
import { Header } from '@renderer/components/layout/Header';
|
|
import { Sidebar } from '@renderer/components/layout/Sidebar';
|
|
import { DetailPanel } from '@renderer/components/layout/DetailPanel';
|
|
import { StatusBar } from '@renderer/components/layout/StatusBar';
|
|
import { ChatPanel } from '@renderer/components/chat/ChatPanel';
|
|
import { SettingsModal } from '@renderer/components/settings/SettingsModal';
|
|
import { OnboardingWizard } from '@renderer/components/onboarding/OnboardingWizard';
|
|
import { CommandPalette } from '@renderer/components/CommandPalette';
|
|
import { ToastContainer } from '@renderer/components/ToastContainer';
|
|
import { ConfirmationDialog } from '@renderer/components/ConfirmationDialog';
|
|
import { ContextMenuDialogHost } from '@renderer/components/ContextMenu';
|
|
import { ErrorBoundary } from '@renderer/components/common/ErrorBoundary';
|
|
import { useTheme } from '@renderer/hooks/useTheme';
|
|
import { useKeyboardShortcuts } from '@renderer/hooks/useKeyboardShortcuts';
|
|
import { useAgentStream } from '@renderer/hooks/useAgentStream';
|
|
import { useUIStore } from '@renderer/stores/ui-store';
|
|
import { useAgentStore } from '@renderer/stores/agent-store';
|
|
import { metonaDarkTheme, metonaLightTheme } from '@renderer/lib/theme';
|
|
|
|
export default function App(): React.JSX.Element {
|
|
useTheme();
|
|
useKeyboardShortcuts();
|
|
useAgentStream();
|
|
|
|
const resolvedTheme = useUIStore((s) => s.resolvedTheme);
|
|
const muiTheme = resolvedTheme === 'dark' ? metonaDarkTheme : metonaLightTheme;
|
|
|
|
// v0.3.0: 面板可见性(支持快捷键和专注模式)
|
|
const sidebarVisible = useUIStore((s) => s.sidebarVisible);
|
|
const detailVisible = useUIStore((s) => s.detailVisible);
|
|
|
|
// 启动时从数据库恢复状态
|
|
useEffect(() => {
|
|
if (window.metona?.config?.get) {
|
|
window.metona.config.get('onboarding.completed').then((v) => {
|
|
if (v === true) useUIStore.getState().setOnboardingCompleted(true);
|
|
}).catch((err) => { console.error('[App]', err); });
|
|
// M-25 修复: 改用 Promise.allSettled,单个配置读取失败不影响另一个
|
|
Promise.allSettled([
|
|
window.metona.config.get('llm.provider'),
|
|
window.metona.config.get('llm.model'),
|
|
]).then(([providerResult, modelResult]) => {
|
|
const provider = providerResult.status === 'fulfilled' ? (providerResult.value as string) || '' : '';
|
|
const model = modelResult.status === 'fulfilled' ? (modelResult.value as string) || '' : '';
|
|
if (provider || model) useAgentStore.getState().setProvider(provider, model);
|
|
// P1-6 修复: 配置加载完成,启用发送按钮
|
|
useAgentStore.getState().setConfigLoaded(true);
|
|
});
|
|
} else {
|
|
// 无 IPC 可用时也标记为已加载(避免永久禁用)
|
|
useAgentStore.getState().setConfigLoaded(true);
|
|
}
|
|
|
|
// v0.3.18 修复: 监听工具就绪事件(MCP 初始化完成后触发)
|
|
// 未就绪前 sendMessage 会被阻止,避免用户在工具不全时发送消息
|
|
// v0.3.18 修复: 注册监听器后立即查询一次,解决 tools:ready 事件在监听器注册前发出的竞态
|
|
if (window.metona?.tools?.onReady) {
|
|
const unsubscribe = window.metona.tools.onReady((data) => {
|
|
console.log('[App] Tools ready:', data.toolCount, 'tools');
|
|
useAgentStore.getState().setToolsReady(true);
|
|
});
|
|
// 竞态保护: 事件可能在监听器注册前已发出,主动查询当前状态
|
|
window.metona.tools.isReady?.().then((status) => {
|
|
if (status.ready) {
|
|
console.log('[App] Tools already ready (polled):', status.toolCount, 'tools');
|
|
useAgentStore.getState().setToolsReady(true);
|
|
}
|
|
}).catch((err) => { console.error('[App] tools.isReady query failed:', err); });
|
|
return unsubscribe;
|
|
} else {
|
|
// 无 IPC 可用时直接标记为就绪(避免永久禁用)
|
|
useAgentStore.getState().setToolsReady(true);
|
|
}
|
|
}, []);
|
|
|
|
return (
|
|
<ThemeProvider theme={muiTheme}>
|
|
<CssBaseline />
|
|
<div
|
|
className="h-screen w-screen flex flex-col overflow-hidden select-none"
|
|
style={{ background: muiTheme.palette.background.default, color: muiTheme.palette.text.primary }}
|
|
>
|
|
{/* H-7 修复: 添加 Header Bar — 规范要求布局为 Header (全宽) + [Sidebar|ChatPanel|DetailPanel] + StatusBar */}
|
|
<Header />
|
|
<div className="flex-1 flex overflow-hidden min-h-0">
|
|
{sidebarVisible && <Sidebar />}
|
|
<ChatPanel />
|
|
{detailVisible && <DetailPanel />}
|
|
</div>
|
|
<StatusBar />
|
|
<SettingsModal />
|
|
<OnboardingWizard />
|
|
<CommandPalette />
|
|
<ToastContainer />
|
|
<ErrorBoundary fallbackTitle="工具确认弹框渲染失败">
|
|
<ConfirmationDialog />
|
|
</ErrorBoundary>
|
|
<ContextMenuDialogHost />
|
|
</div>
|
|
</ThemeProvider>
|
|
);
|
|
}
|