diff --git a/crates/zclaw-skills/src/semantic_router.rs b/crates/zclaw-skills/src/semantic_router.rs index 729762c..bd4424c 100644 --- a/crates/zclaw-skills/src/semantic_router.rs +++ b/crates/zclaw-skills/src/semantic_router.rs @@ -5,9 +5,8 @@ //! 2. Optional embedding similarity (when an Embedder is configured) //! 3. Optional LLM fallback for ambiguous cases //! -//! **@reserved** — This module is fully implemented (719 lines, 200+ lines of tests) -//! but NOT yet wired into the message pipeline. It will be activated in Phase 3 -//! when the "butler mode" semantic auto-routing feature is enabled. Do NOT delete. +//! **Active** — Used by `ButlerRouterMiddleware` (zclaw-runtime) for keyword-based routing. +//! The full TF-IDF + embedding pipeline can be integrated via `with_router()` when needed. use std::collections::HashMap; use std::sync::Arc; diff --git a/desktop/src-tauri/src/lib.rs b/desktop/src-tauri/src/lib.rs index 828ecf8..0ec9173 100644 --- a/desktop/src-tauri/src/lib.rs +++ b/desktop/src-tauri/src/lib.rs @@ -155,6 +155,25 @@ pub fn run() { .expect("In-memory SQLite should never fail") }); app.manage(persistence); + + // Initialize pain point persistence (SQLite-backed). + // Must complete before event loop starts so that pain aggregator + // has a valid storage layer for dual-write operations. + { + let app_dir = app.path().app_data_dir().expect("Failed to get app data dir"); + let pain_db_dir = app_dir.join("intelligence"); + std::fs::create_dir_all(&pain_db_dir).expect("Failed to create intelligence dir"); + + let db_path = pain_db_dir.join("pain.db"); + let db_url = format!("sqlite:{}", db_path.display()); + let pool = rt.block_on(sqlx::SqlitePool::connect(&db_url)) + .expect("Failed to connect pain storage SQLite pool"); + + if let Err(e) = rt.block_on(intelligence::pain_aggregator::init_pain_storage(pool)) { + tracing::error!("[PainStorage] Init failed: {}, pain points will not persist", e); + } + } + Ok(()) }) .invoke_handler(tauri::generate_handler![ diff --git a/desktop/src/components/FirstConversationPrompt.tsx b/desktop/src/components/FirstConversationPrompt.tsx index f541b91..44a68be 100644 --- a/desktop/src/components/FirstConversationPrompt.tsx +++ b/desktop/src/components/FirstConversationPrompt.tsx @@ -7,6 +7,7 @@ * - Horizontal quick-action chips (colored pills) * - Clean, minimal aesthetic */ +import { useEffect } from 'react'; import { motion } from 'framer-motion'; import { Sparkles, @@ -21,6 +22,7 @@ import { generateWelcomeMessage, getScenarioById, } from '../lib/personality-presets'; +import { useColdStart } from '../lib/use-cold-start'; import type { Clone } from '../store/agentStore'; import { useChatStore } from '../store/chatStore'; import { useClassroomStore } from '../store/classroomStore'; @@ -56,6 +58,16 @@ export function FirstConversationPrompt({ onSelectSuggestion, }: FirstConversationPromptProps) { const chatMode = useChatStore((s) => s.chatMode); + const { isColdStart, phase, greetingSent, markGreetingSent, getGreetingMessage } = useColdStart(); + + // Cold start: auto-trigger greeting for first-time users + useEffect(() => { + if (isColdStart && phase === 'idle' && !greetingSent) { + const greeting = getGreetingMessage(clone.nickname || clone.name, clone.emoji); + onSelectSuggestion?.(greeting); + markGreetingSent(); + } + }, [isColdStart, phase, greetingSent, clone.nickname, clone.name, clone.emoji, onSelectSuggestion, markGreetingSent, getGreetingMessage]); const modeGreeting: Record = { flash: '快速回答,即时响应', diff --git a/desktop/src/components/Settings/General.tsx b/desktop/src/components/Settings/General.tsx index 2958e62..d8e6849 100644 --- a/desktop/src/components/Settings/General.tsx +++ b/desktop/src/components/Settings/General.tsx @@ -3,6 +3,7 @@ import { useConnectionStore } from '../../store/connectionStore'; import { useConfigStore } from '../../store/configStore'; import { useConversationStore } from '../../store/chat/conversationStore'; import { useSaaSStore } from '../../store/saasStore'; +import { useUIModeStore } from '../../store/uiModeStore'; import { getStoredGatewayToken, setStoredGatewayToken } from '../../lib/gateway-client'; import { silentErrorHandler } from '../../lib/error-utils'; @@ -20,6 +21,8 @@ export function General() { const [showToolCalls, setShowToolCalls] = useState(quickConfig.showToolCalls ?? false); const [gatewayToken, setGatewayToken] = useState(getStoredGatewayToken()); const [isSaving, setIsSaving] = useState(false); + const mode = useUIModeStore((s) => s.mode); + const setMode = useUIModeStore((s) => s.setMode); const connected = connectionState === 'connected'; const connecting = connectionState === 'connecting' || connectionState === 'reconnecting'; @@ -185,6 +188,35 @@ export function General() { + +
+
界面模式
+
切换简洁界面或完整专业界面。
+
+ + +
+
); diff --git a/desktop/src/components/TopBar.tsx b/desktop/src/components/TopBar.tsx index f0e8022..42da3a3 100644 --- a/desktop/src/components/TopBar.tsx +++ b/desktop/src/components/TopBar.tsx @@ -1,5 +1,6 @@ -import { ClipboardList } from 'lucide-react'; +import { ClipboardList, Minimize2 } from 'lucide-react'; import { Button } from './ui'; +import { useUIModeStore } from '../store/uiModeStore'; interface TopBarProps { title: string; @@ -32,6 +33,17 @@ export function TopBar({ {/* 右侧按钮 */}
+ {/* 切换简洁模式 */} + {/* 详情按钮 */} {showDetailButton && onOpenDetail && (