fix(butler): wire verification gaps — pain storage init, cold start, UI mode switches
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

- Call init_pain_storage() in Tauri .setup() so pain persistence activates on boot
- Integrate useColdStart hook into FirstConversationPrompt for auto-greeting
- Add UI mode toggle section to Settings/General (already had imports)
- Add "简洁" mode switch-back button to TopBar in professional layout
- Update SemanticSkillRouter @reserved annotation to reflect active status
This commit is contained in:
iven
2026-04-09 10:38:49 +08:00
parent e6937e1e5f
commit 646d8c21af
5 changed files with 78 additions and 4 deletions

View File

@@ -5,9 +5,8 @@
//! 2. Optional embedding similarity (when an Embedder is configured) //! 2. Optional embedding similarity (when an Embedder is configured)
//! 3. Optional LLM fallback for ambiguous cases //! 3. Optional LLM fallback for ambiguous cases
//! //!
//! **@reserved** — This module is fully implemented (719 lines, 200+ lines of tests) //! **Active** — Used by `ButlerRouterMiddleware` (zclaw-runtime) for keyword-based routing.
//! but NOT yet wired into the message pipeline. It will be activated in Phase 3 //! The full TF-IDF + embedding pipeline can be integrated via `with_router()` when needed.
//! when the "butler mode" semantic auto-routing feature is enabled. Do NOT delete.
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::Arc;

View File

@@ -155,6 +155,25 @@ pub fn run() {
.expect("In-memory SQLite should never fail") .expect("In-memory SQLite should never fail")
}); });
app.manage(persistence); 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(()) Ok(())
}) })
.invoke_handler(tauri::generate_handler![ .invoke_handler(tauri::generate_handler![

View File

@@ -7,6 +7,7 @@
* - Horizontal quick-action chips (colored pills) * - Horizontal quick-action chips (colored pills)
* - Clean, minimal aesthetic * - Clean, minimal aesthetic
*/ */
import { useEffect } from 'react';
import { motion } from 'framer-motion'; import { motion } from 'framer-motion';
import { import {
Sparkles, Sparkles,
@@ -21,6 +22,7 @@ import {
generateWelcomeMessage, generateWelcomeMessage,
getScenarioById, getScenarioById,
} from '../lib/personality-presets'; } from '../lib/personality-presets';
import { useColdStart } from '../lib/use-cold-start';
import type { Clone } from '../store/agentStore'; import type { Clone } from '../store/agentStore';
import { useChatStore } from '../store/chatStore'; import { useChatStore } from '../store/chatStore';
import { useClassroomStore } from '../store/classroomStore'; import { useClassroomStore } from '../store/classroomStore';
@@ -56,6 +58,16 @@ export function FirstConversationPrompt({
onSelectSuggestion, onSelectSuggestion,
}: FirstConversationPromptProps) { }: FirstConversationPromptProps) {
const chatMode = useChatStore((s) => s.chatMode); 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<string, string> = { const modeGreeting: Record<string, string> = {
flash: '快速回答,即时响应', flash: '快速回答,即时响应',

View File

@@ -3,6 +3,7 @@ import { useConnectionStore } from '../../store/connectionStore';
import { useConfigStore } from '../../store/configStore'; import { useConfigStore } from '../../store/configStore';
import { useConversationStore } from '../../store/chat/conversationStore'; import { useConversationStore } from '../../store/chat/conversationStore';
import { useSaaSStore } from '../../store/saasStore'; import { useSaaSStore } from '../../store/saasStore';
import { useUIModeStore } from '../../store/uiModeStore';
import { getStoredGatewayToken, setStoredGatewayToken } from '../../lib/gateway-client'; import { getStoredGatewayToken, setStoredGatewayToken } from '../../lib/gateway-client';
import { silentErrorHandler } from '../../lib/error-utils'; import { silentErrorHandler } from '../../lib/error-utils';
@@ -20,6 +21,8 @@ export function General() {
const [showToolCalls, setShowToolCalls] = useState(quickConfig.showToolCalls ?? false); const [showToolCalls, setShowToolCalls] = useState(quickConfig.showToolCalls ?? false);
const [gatewayToken, setGatewayToken] = useState(getStoredGatewayToken()); const [gatewayToken, setGatewayToken] = useState(getStoredGatewayToken());
const [isSaving, setIsSaving] = useState(false); const [isSaving, setIsSaving] = useState(false);
const mode = useUIModeStore((s) => s.mode);
const setMode = useUIModeStore((s) => s.setMode);
const connected = connectionState === 'connected'; const connected = connectionState === 'connected';
const connecting = connectionState === 'connecting' || connectionState === 'reconnecting'; const connecting = connectionState === 'connecting' || connectionState === 'reconnecting';
@@ -185,6 +188,35 @@ export function General() {
</div> </div>
<Toggle checked={showToolCalls} onChange={handleShowToolCallsChange} disabled={isSaving} /> <Toggle checked={showToolCalls} onChange={handleShowToolCallsChange} disabled={isSaving} />
</div> </div>
<div>
<div className="text-sm font-medium text-gray-900"></div>
<div className="text-xs text-gray-500 mt-0.5 mb-2"></div>
<div className="flex gap-3">
<button
onClick={() => setMode('simple')}
className={`flex-1 p-3 rounded-lg border text-sm text-center transition-colors ${
mode === 'simple'
? 'border-orange-500 bg-orange-50 text-orange-700 dark:bg-orange-900/20 dark:text-orange-300'
: 'border-gray-200 hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800'
}`}
>
<p className="text-xs text-gray-500 mt-1">使</p>
</button>
<button
onClick={() => setMode('professional')}
className={`flex-1 p-3 rounded-lg border text-sm text-center transition-colors ${
mode === 'professional'
? 'border-orange-500 bg-orange-50 text-orange-700 dark:bg-orange-900/20 dark:text-orange-300'
: 'border-gray-200 hover:bg-gray-50 dark:border-gray-700 dark:hover:bg-gray-800'
}`}
>
<p className="text-xs text-gray-500 mt-1"></p>
</button>
</div>
</div>
</div> </div>
</div> </div>
); );

View File

@@ -1,5 +1,6 @@
import { ClipboardList } from 'lucide-react'; import { ClipboardList, Minimize2 } from 'lucide-react';
import { Button } from './ui'; import { Button } from './ui';
import { useUIModeStore } from '../store/uiModeStore';
interface TopBarProps { interface TopBarProps {
title: string; title: string;
@@ -32,6 +33,17 @@ export function TopBar({
{/* 右侧按钮 */} {/* 右侧按钮 */}
<div className="flex items-center gap-1"> <div className="flex items-center gap-1">
{/* 切换简洁模式 */}
<Button
variant="ghost"
size="sm"
onClick={() => useUIModeStore.getState().setMode('simple')}
className="flex items-center gap-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100"
title="切换到简洁模式"
>
<Minimize2 className="w-4 h-4" />
<span className="text-sm"></span>
</Button>
{/* 详情按钮 */} {/* 详情按钮 */}
{showDetailButton && onOpenDetail && ( {showDetailButton && onOpenDetail && (
<Button <Button