refactor(types): comprehensive TypeScript type system improvements
Major type system refactoring and error fixes across the codebase: **Type System Improvements:** - Extended OpenFangStreamEvent with 'connected' and 'agents_updated' event types - Added GatewayPong interface for WebSocket pong responses - Added index signature to MemorySearchOptions for Record compatibility - Fixed RawApproval interface with hand_name, run_id properties **Gateway & Protocol Fixes:** - Fixed performHandshake nonce handling in gateway-client.ts - Fixed onAgentStream callback type definitions - Fixed HandRun runId mapping to handle undefined values - Fixed Approval mapping with proper default values **Memory System Fixes:** - Fixed MemoryEntry creation with required properties (lastAccessedAt, accessCount) - Replaced getByAgent with getAll method in vector-memory.ts - Fixed MemorySearchOptions type compatibility **Component Fixes:** - Fixed ReflectionLog property names (filePath→file, proposedContent→suggestedContent) - Fixed SkillMarket suggestSkills async call arguments - Fixed message-virtualization useRef generic type - Fixed session-persistence messageCount type conversion **Code Cleanup:** - Removed unused imports and variables across multiple files - Consolidated StoredError interface (removed duplicate) - Deleted obsolete test files (feedbackStore.test.ts, memory-index.test.ts) **New Features:** - Added browser automation module (Tauri backend) - Added Active Learning Panel component - Added Agent Onboarding Wizard - Added Memory Graph visualization - Added Personality Selector - Added Skill Market store and components Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
425
desktop/src/store/activeLearningStore.ts
Normal file
425
desktop/src/store/activeLearningStore.ts
Normal file
@@ -0,0 +1,425 @@
|
||||
/**
|
||||
* ActiveLearningStore - 主动学习状态管理
|
||||
*
|
||||
* 猡久学习事件和学习模式,学习建议的状态。
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import {
|
||||
type LearningEvent,
|
||||
type LearningPattern,
|
||||
type LearningSuggestion,
|
||||
type LearningEventType,
|
||||
type LearningConfig,
|
||||
} from '../types/active-learning';
|
||||
|
||||
// === Types ===
|
||||
|
||||
interface ActiveLearningState {
|
||||
events: LearningEvent[];
|
||||
patterns: LearningPattern[];
|
||||
suggestions: LearningSuggestion[];
|
||||
config: LearningConfig;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
interface ActiveLearningActions {
|
||||
recordEvent: (event: Omit<LearningEvent, 'id' | 'timestamp' | 'acknowledged'>) => Promise<LearningEvent>;
|
||||
recordFeedback: (agentId: string, messageId: string, feedback: string, context?: string) => Promise<LearningEvent | null>;
|
||||
acknowledgeEvent: (eventId: string) => void;
|
||||
getPatterns: (agentId: string) => LearningPattern[];
|
||||
getSuggestions: (agentId: string) => LearningSuggestion[];
|
||||
applySuggestion: (suggestionId: string) => void;
|
||||
dismissSuggestion: (suggestionId: string) => void;
|
||||
getStats: (agentId: string) => ActiveLearningStats;
|
||||
setConfig: (config: Partial<LearningConfig>) => void;
|
||||
clearEvents: (agentId: string) => void;
|
||||
exportLearningData: (agentId: string) => Promise<string>;
|
||||
importLearningData: (agentId: string, data: string) => Promise<void>;
|
||||
}
|
||||
|
||||
interface ActiveLearningStats {
|
||||
totalEvents: number;
|
||||
eventsByType: Record<LearningEventType, number>;
|
||||
totalPatterns: number;
|
||||
avgConfidence: number;
|
||||
}
|
||||
|
||||
export type ActiveLearningStore = ActiveLearningState & ActiveLearningActions;
|
||||
|
||||
const STORAGE_KEY = 'zclaw-active-learning';
|
||||
const MAX_EVENTS = 1000;
|
||||
|
||||
// === Helper Functions ===
|
||||
|
||||
function generateEventId(): string {
|
||||
return `le-${Date.now()}-${Math.random().toString(36).slice(2)}`;
|
||||
}
|
||||
|
||||
function analyzeSentiment(text: string): 'positive' | 'negative' | 'neutral' {
|
||||
const positive = ['好的', '很棒', '谢谢', '完美', 'excellent', '喜欢', '爱了', 'good', 'great', 'nice', '满意'];
|
||||
const negative = ['不好', '差', '糟糕', '错误', 'wrong', 'bad', '不喜欢', '讨厌', '问题', '失败', 'fail', 'error'];
|
||||
|
||||
const lowerText = text.toLowerCase();
|
||||
|
||||
if (positive.some(w => lowerText.includes(w))) return 'positive';
|
||||
if (negative.some(w => lowerText.includes(w))) return 'negative';
|
||||
return 'neutral';
|
||||
}
|
||||
|
||||
function analyzeEventType(text: string): LearningEventType {
|
||||
const lowerText = text.toLowerCase();
|
||||
|
||||
if (lowerText.includes('纠正') || lowerText.includes('不对') || lowerText.includes('修改')) {
|
||||
return 'correction';
|
||||
}
|
||||
if (lowerText.includes('喜欢') || lowerText.includes('偏好') || lowerText.includes('风格')) {
|
||||
return 'preference';
|
||||
}
|
||||
if (lowerText.includes('场景') || lowerText.includes('上下文') || lowerText.includes('情况')) {
|
||||
return 'context';
|
||||
}
|
||||
if (lowerText.includes('总是') || lowerText.includes('经常') || lowerText.includes('习惯')) {
|
||||
return 'behavior';
|
||||
}
|
||||
return 'feedback';
|
||||
}
|
||||
|
||||
function inferPreference(feedback: string, sentiment: string): string {
|
||||
if (sentiment === 'positive') {
|
||||
if (feedback.includes('简洁')) return '用户偏好简洁的回复';
|
||||
if (feedback.includes('详细')) return '用户偏好详细的回复';
|
||||
if (feedback.includes('快速')) return '用户偏好快速响应';
|
||||
return '用户对当前回复风格满意';
|
||||
}
|
||||
if (sentiment === 'negative') {
|
||||
if (feedback.includes('太长')) return '用户偏好更短的回复';
|
||||
if (feedback.includes('太短')) return '用户偏好更详细的回复';
|
||||
if (feedback.includes('不准确')) return '用户偏好更准确的信息';
|
||||
return '用户对当前回复风格不满意';
|
||||
}
|
||||
return '用户反馈中性';
|
||||
}
|
||||
|
||||
// === Store ===
|
||||
|
||||
export const useActiveLearningStore = create<ActiveLearningStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
events: [],
|
||||
patterns: [],
|
||||
suggestions: [],
|
||||
config: {
|
||||
enabled: true,
|
||||
minConfidence: 0.5,
|
||||
maxEvents: MAX_EVENTS,
|
||||
suggestionCooldown: 2,
|
||||
},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
|
||||
recordEvent: async (event) => {
|
||||
const { events, config } = get();
|
||||
if (!config.enabled) throw new Error('Learning is disabled');
|
||||
|
||||
// 检查重复事件
|
||||
const existing = events.find(e =>
|
||||
e.agentId === event.agentId &&
|
||||
e.messageId === event.messageId &&
|
||||
e.type === event.type
|
||||
);
|
||||
|
||||
if (existing) {
|
||||
// 更新现有事件
|
||||
const updated = events.map(e =>
|
||||
e.id === existing.id
|
||||
? {
|
||||
...e,
|
||||
observation: e.observation + ' | ' + event.observation,
|
||||
confidence: (e.confidence + event.confidence) / 2,
|
||||
appliedCount: e.appliedCount + 1,
|
||||
}
|
||||
: e
|
||||
);
|
||||
set({ events: updated });
|
||||
return existing;
|
||||
}
|
||||
|
||||
// 创建新事件
|
||||
const newEvent: LearningEvent = {
|
||||
...event,
|
||||
id: generateEventId(),
|
||||
timestamp: Date.now(),
|
||||
acknowledged: false,
|
||||
appliedCount: 0,
|
||||
};
|
||||
|
||||
// 提取模式
|
||||
const newPatterns = extractPatterns(newEvent, get().patterns);
|
||||
const newSuggestions = generateSuggestions(newEvent, newPatterns);
|
||||
|
||||
// 保持事件数量限制
|
||||
const updatedEvents = [newEvent, ...events].slice(0, config.maxEvents);
|
||||
|
||||
set({
|
||||
events: updatedEvents,
|
||||
patterns: [...get().patterns, ...newPatterns],
|
||||
suggestions: [...get().suggestions, ...newSuggestions],
|
||||
});
|
||||
|
||||
return newEvent;
|
||||
},
|
||||
|
||||
recordFeedback: async (agentId, messageId, feedback, context) => {
|
||||
const { config } = get();
|
||||
if (!config.enabled) return null;
|
||||
|
||||
const sentiment = analyzeSentiment(feedback);
|
||||
const type = analyzeEventType(feedback);
|
||||
|
||||
return get().recordEvent({
|
||||
type,
|
||||
agentId,
|
||||
messageId,
|
||||
trigger: context || 'User feedback',
|
||||
observation: feedback,
|
||||
context,
|
||||
inferredPreference: inferPreference(feedback, sentiment),
|
||||
confidence: sentiment === 'positive' ? 0.8 : sentiment === 'negative' ? 0.5 : 0.3,
|
||||
appliedCount: 0,
|
||||
});
|
||||
},
|
||||
|
||||
acknowledgeEvent: (eventId) => {
|
||||
const { events } = get();
|
||||
set({
|
||||
events: events.map(e =>
|
||||
e.id === eventId ? { ...e, acknowledged: true } : e
|
||||
),
|
||||
});
|
||||
},
|
||||
|
||||
getPatterns: (agentId) => {
|
||||
return get().patterns.filter(p => p.agentId === agentId);
|
||||
},
|
||||
|
||||
getSuggestions: (agentId) => {
|
||||
const now = Date.now();
|
||||
return get().suggestions.filter(s =>
|
||||
s.agentId === agentId &&
|
||||
!s.dismissed &&
|
||||
(!s.expiresAt || s.expiresAt.getTime() > now)
|
||||
);
|
||||
},
|
||||
|
||||
applySuggestion: (suggestionId) => {
|
||||
const { suggestions, patterns } = get();
|
||||
const suggestion = suggestions.find(s => s.id === suggestionId);
|
||||
|
||||
if (suggestion) {
|
||||
// 更新模式置信度
|
||||
const updatedPatterns = patterns.map(p =>
|
||||
p.pattern === suggestion.pattern
|
||||
? { ...p, confidence: Math.min(1, p.confidence + 0.1) }
|
||||
: p
|
||||
);
|
||||
|
||||
set({
|
||||
suggestions: suggestions.map(s =>
|
||||
s.id === suggestionId ? { ...s, dismissed: false } : s
|
||||
),
|
||||
patterns: updatedPatterns,
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
dismissSuggestion: (suggestionId) => {
|
||||
const { suggestions } = get();
|
||||
set({
|
||||
suggestions: suggestions.map(s =>
|
||||
s.id === suggestionId ? { ...s, dismissed: true } : s
|
||||
),
|
||||
});
|
||||
},
|
||||
|
||||
getStats: (agentId) => {
|
||||
const { events, patterns } = get();
|
||||
const agentEvents = events.filter(e => e.agentId === agentId);
|
||||
const agentPatterns = patterns.filter(p => p.agentId === agentId);
|
||||
|
||||
const eventsByType: Record<LearningEventType, number> = {
|
||||
preference: 0,
|
||||
correction: 0,
|
||||
context: 0,
|
||||
feedback: 0,
|
||||
behavior: 0,
|
||||
implicit: 0,
|
||||
};
|
||||
|
||||
for (const event of agentEvents) {
|
||||
eventsByType[event.type]++;
|
||||
}
|
||||
|
||||
return {
|
||||
totalEvents: agentEvents.length,
|
||||
eventsByType,
|
||||
totalPatterns: agentPatterns.length,
|
||||
avgConfidence: agentPatterns.length > 0
|
||||
? agentPatterns.reduce((sum, p) => sum + p.confidence, 0) / agentPatterns.length
|
||||
: 0,
|
||||
};
|
||||
},
|
||||
|
||||
setConfig: (config) => {
|
||||
set(state => ({
|
||||
config: { ...state.config, ...config },
|
||||
}));
|
||||
},
|
||||
|
||||
clearEvents: (agentId) => {
|
||||
const { events, patterns, suggestions } = get();
|
||||
set({
|
||||
events: events.filter(e => e.agentId !== agentId),
|
||||
patterns: patterns.filter(p => p.agentId !== agentId),
|
||||
suggestions: suggestions.filter(s => s.agentId !== agentId),
|
||||
});
|
||||
},
|
||||
|
||||
exportLearningData: async (agentId) => {
|
||||
const { events, patterns, config } = get();
|
||||
const data = {
|
||||
events: events.filter(e => e.agentId === agentId),
|
||||
patterns: patterns.filter(p => p.agentId === agentId),
|
||||
config,
|
||||
exportedAt: new Date().toISOString(),
|
||||
};
|
||||
return JSON.stringify(data, null, 2);
|
||||
},
|
||||
|
||||
importLearningData: async (agentId, data) => {
|
||||
try {
|
||||
const parsed = JSON.parse(data);
|
||||
const { events, patterns } = get();
|
||||
|
||||
// 合并导入的数据
|
||||
const mergedEvents = [
|
||||
...events,
|
||||
...parsed.events.map((e: LearningEvent) => ({
|
||||
...e,
|
||||
id: generateEventId(),
|
||||
agentId,
|
||||
})),
|
||||
].slice(0, MAX_EVENTS);
|
||||
|
||||
const mergedPatterns = [
|
||||
...patterns,
|
||||
...parsed.patterns.map((p: LearningPattern) => ({
|
||||
...p,
|
||||
agentId,
|
||||
})),
|
||||
];
|
||||
|
||||
set({
|
||||
events: mergedEvents,
|
||||
patterns: mergedPatterns,
|
||||
});
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to import learning data: ${err}`);
|
||||
}
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: STORAGE_KEY,
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
// === Pattern Extraction ===
|
||||
|
||||
function extractPatterns(
|
||||
event: LearningEvent,
|
||||
existingPatterns: LearningPattern[]
|
||||
): LearningPattern[] {
|
||||
const patterns: LearningPattern[] = [];
|
||||
|
||||
// 偏好模式
|
||||
if (event.observation.includes('谢谢') || event.observation.includes('好的')) {
|
||||
patterns.push({
|
||||
type: 'preference',
|
||||
pattern: 'positive_response_preference',
|
||||
description: '用户偏好正面回复风格',
|
||||
examples: [event.observation],
|
||||
confidence: 0.8,
|
||||
agentId: event.agentId,
|
||||
});
|
||||
}
|
||||
|
||||
// 精确性模式
|
||||
if (event.type === 'correction') {
|
||||
patterns.push({
|
||||
type: 'rule',
|
||||
pattern: 'precision_preference',
|
||||
description: '用户对精确性有更高要求',
|
||||
examples: [event.observation],
|
||||
confidence: 0.9,
|
||||
agentId: event.agentId,
|
||||
});
|
||||
}
|
||||
|
||||
// 上下文模式
|
||||
if (event.context) {
|
||||
patterns.push({
|
||||
type: 'context',
|
||||
pattern: 'context_aware',
|
||||
description: 'Agent 需要关注上下文',
|
||||
examples: [event.context],
|
||||
confidence: 0.6,
|
||||
agentId: event.agentId,
|
||||
});
|
||||
}
|
||||
|
||||
return patterns.filter(p =>
|
||||
!existingPatterns.some(ep => ep.pattern === p.pattern && ep.agentId === p.agentId)
|
||||
);
|
||||
}
|
||||
|
||||
// === Suggestion Generation ===
|
||||
|
||||
function generateSuggestions(
|
||||
event: LearningEvent,
|
||||
patterns: LearningPattern[]
|
||||
): LearningSuggestion[] {
|
||||
const suggestions: LearningSuggestion[] = [];
|
||||
const now = Date.now();
|
||||
|
||||
for (const pattern of patterns) {
|
||||
const template = SUGGESTION_TEMPLATES[pattern.pattern];
|
||||
|
||||
if (template) {
|
||||
suggestions.push({
|
||||
id: `sug-${Date.now()}-${Math.random().toString(36).slice(2)}`,
|
||||
agentId: event.agentId,
|
||||
type: pattern.type,
|
||||
pattern: pattern.pattern,
|
||||
suggestion: template,
|
||||
confidence: pattern.confidence,
|
||||
createdAt: now,
|
||||
expiresAt: new Date(now + 7 * 24 * 60 * 60 * 1000),
|
||||
dismissed: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return suggestions;
|
||||
}
|
||||
|
||||
const SUGGESTION_TEMPLATES: Record<string, string> = {
|
||||
positive_response_preference:
|
||||
'用户似乎偏好正面回复。建议在回复时保持积极和确认的语气。',
|
||||
precision_preference:
|
||||
'用户对精确性有更高要求。建议在提供信息时更加详细和准确。',
|
||||
context_aware:
|
||||
'Agent 需要关注上下文。建议在回复时考虑对话的背景和历史。',
|
||||
};
|
||||
Reference in New Issue
Block a user