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
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:
@@ -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;
|
||||||
|
|||||||
@@ -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![
|
||||||
|
|||||||
@@ -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: '快速回答,即时响应',
|
||||||
|
|||||||
@@ -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>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user