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:
316
desktop/src/store/memoryGraphStore.ts
Normal file
316
desktop/src/store/memoryGraphStore.ts
Normal file
@@ -0,0 +1,316 @@
|
||||
/**
|
||||
* MemoryGraphStore - 记忆图谱状态管理
|
||||
*
|
||||
* 管理记忆图谱可视化的状态,包括节点、边、布局和交互。
|
||||
*/
|
||||
|
||||
import { create } from 'zustand';
|
||||
import { persist } from 'zustand/middleware';
|
||||
import { getMemoryManager, type MemoryEntry, type MemoryType } from '../lib/agent-memory';
|
||||
|
||||
export type { MemoryType };
|
||||
|
||||
// === Types ===
|
||||
|
||||
export interface GraphNode {
|
||||
id: string;
|
||||
type: MemoryType;
|
||||
label: string;
|
||||
x: number;
|
||||
y: number;
|
||||
vx: number;
|
||||
vy: number;
|
||||
importance: number;
|
||||
accessCount: number;
|
||||
createdAt: string;
|
||||
isHighlighted: boolean;
|
||||
isSelected: boolean;
|
||||
}
|
||||
|
||||
export interface GraphEdge {
|
||||
id: string;
|
||||
source: string;
|
||||
target: string;
|
||||
type: 'reference' | 'related' | 'derived';
|
||||
strength: number;
|
||||
}
|
||||
|
||||
export interface GraphFilter {
|
||||
types: MemoryType[];
|
||||
minImportance: number;
|
||||
dateRange: {
|
||||
start?: string;
|
||||
end?: string;
|
||||
};
|
||||
searchQuery: string;
|
||||
}
|
||||
|
||||
export interface GraphLayout {
|
||||
width: number;
|
||||
height: number;
|
||||
zoom: number;
|
||||
offsetX: number;
|
||||
offsetY: number;
|
||||
}
|
||||
|
||||
interface MemoryGraphState {
|
||||
nodes: GraphNode[];
|
||||
edges: GraphEdge[];
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
filter: GraphFilter;
|
||||
layout: GraphLayout;
|
||||
selectedNodeId: string | null;
|
||||
hoveredNodeId: string | null;
|
||||
showLabels: boolean;
|
||||
simulationRunning: boolean;
|
||||
}
|
||||
|
||||
interface MemoryGraphActions {
|
||||
loadGraph: (agentId: string) => Promise<void>;
|
||||
setFilter: (filter: Partial<GraphFilter>) => void;
|
||||
resetFilter: () => void;
|
||||
setLayout: (layout: Partial<GraphLayout>) => void;
|
||||
selectNode: (nodeId: string | null) => void;
|
||||
hoverNode: (nodeId: string | null) => void;
|
||||
toggleLabels: () => void;
|
||||
startSimulation: () => void;
|
||||
stopSimulation: () => void;
|
||||
updateNodePositions: (updates: Array<{ id: string; x: number; y: number }>) => void;
|
||||
highlightSearch: (query: string) => void;
|
||||
clearHighlight: () => void;
|
||||
exportAsImage: () => Promise<Blob | null>;
|
||||
getFilteredNodes: () => GraphNode[];
|
||||
getFilteredEdges: () => GraphEdge[];
|
||||
}
|
||||
|
||||
const DEFAULT_FILTER: GraphFilter = {
|
||||
types: ['fact', 'preference', 'lesson', 'context', 'task'],
|
||||
minImportance: 0,
|
||||
dateRange: {},
|
||||
searchQuery: '',
|
||||
};
|
||||
|
||||
const DEFAULT_LAYOUT: GraphLayout = {
|
||||
width: 800,
|
||||
height: 600,
|
||||
zoom: 1,
|
||||
offsetX: 0,
|
||||
offsetY: 0,
|
||||
};
|
||||
|
||||
export type MemoryGraphStore = MemoryGraphState & MemoryGraphActions;
|
||||
|
||||
// === Helper Functions ===
|
||||
|
||||
function memoryToNode(memory: MemoryEntry, index: number, total: number): GraphNode {
|
||||
// 使用圆形布局初始位置
|
||||
const angle = (index / total) * 2 * Math.PI;
|
||||
const radius = 200;
|
||||
|
||||
return {
|
||||
id: memory.id,
|
||||
type: memory.type,
|
||||
label: memory.content.slice(0, 50) + (memory.content.length > 50 ? '...' : ''),
|
||||
x: 400 + radius * Math.cos(angle),
|
||||
y: 300 + radius * Math.sin(angle),
|
||||
vx: 0,
|
||||
vy: 0,
|
||||
importance: memory.importance,
|
||||
accessCount: memory.accessCount,
|
||||
createdAt: memory.createdAt,
|
||||
isHighlighted: false,
|
||||
isSelected: false,
|
||||
};
|
||||
}
|
||||
|
||||
function findRelatedMemories(memories: MemoryEntry[]): GraphEdge[] {
|
||||
const edges: GraphEdge[] = [];
|
||||
|
||||
// 简单的关联算法:基于共同标签和关键词
|
||||
for (let i = 0; i < memories.length; i++) {
|
||||
for (let j = i + 1; j < memories.length; j++) {
|
||||
const m1 = memories[i];
|
||||
const m2 = memories[j];
|
||||
|
||||
// 检查共同标签
|
||||
const commonTags = m1.tags.filter(t => m2.tags.includes(t));
|
||||
if (commonTags.length > 0) {
|
||||
edges.push({
|
||||
id: `edge-${m1.id}-${m2.id}`,
|
||||
source: m1.id,
|
||||
target: m2.id,
|
||||
type: 'related',
|
||||
strength: commonTags.length * 0.3,
|
||||
});
|
||||
}
|
||||
|
||||
// 同类型记忆关联
|
||||
if (m1.type === m2.type) {
|
||||
const existingEdge = edges.find(
|
||||
e => e.source === m1.id && e.target === m2.id
|
||||
);
|
||||
if (!existingEdge) {
|
||||
edges.push({
|
||||
id: `edge-${m1.id}-${m2.id}-type`,
|
||||
source: m1.id,
|
||||
target: m2.id,
|
||||
type: 'derived',
|
||||
strength: 0.1,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return edges;
|
||||
}
|
||||
|
||||
export const useMemoryGraphStore = create<MemoryGraphStore>()(
|
||||
persist(
|
||||
(set, get) => ({
|
||||
nodes: [],
|
||||
edges: [],
|
||||
isLoading: false,
|
||||
error: null,
|
||||
filter: DEFAULT_FILTER,
|
||||
layout: DEFAULT_LAYOUT,
|
||||
selectedNodeId: null,
|
||||
hoveredNodeId: null,
|
||||
showLabels: true,
|
||||
simulationRunning: false,
|
||||
|
||||
loadGraph: async (agentId: string) => {
|
||||
set({ isLoading: true, error: null });
|
||||
|
||||
try {
|
||||
const mgr = getMemoryManager();
|
||||
const memories = await mgr.getAll(agentId, { limit: 200 });
|
||||
|
||||
const nodes = memories.map((m, i) => memoryToNode(m, i, memories.length));
|
||||
const edges = findRelatedMemories(memories);
|
||||
|
||||
set({
|
||||
nodes,
|
||||
edges,
|
||||
isLoading: false,
|
||||
});
|
||||
} catch (err) {
|
||||
set({
|
||||
isLoading: false,
|
||||
error: err instanceof Error ? err.message : '加载图谱失败',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
setFilter: (filter) => {
|
||||
set(state => ({
|
||||
filter: { ...state.filter, ...filter },
|
||||
}));
|
||||
},
|
||||
|
||||
resetFilter: () => {
|
||||
set({ filter: DEFAULT_FILTER });
|
||||
},
|
||||
|
||||
setLayout: (layout) => {
|
||||
set(state => ({
|
||||
layout: { ...state.layout, ...layout },
|
||||
}));
|
||||
},
|
||||
|
||||
selectNode: (nodeId) => {
|
||||
set(state => ({
|
||||
selectedNodeId: nodeId,
|
||||
nodes: state.nodes.map(n => ({
|
||||
...n,
|
||||
isSelected: n.id === nodeId,
|
||||
})),
|
||||
}));
|
||||
},
|
||||
|
||||
hoverNode: (nodeId) => {
|
||||
set(state => ({
|
||||
hoveredNodeId: nodeId,
|
||||
nodes: state.nodes.map(n => ({
|
||||
...n,
|
||||
isHighlighted: nodeId ? n.id === nodeId : n.isHighlighted,
|
||||
})),
|
||||
}));
|
||||
},
|
||||
|
||||
toggleLabels: () => {
|
||||
set(state => ({ showLabels: !state.showLabels }));
|
||||
},
|
||||
|
||||
startSimulation: () => {
|
||||
set({ simulationRunning: true });
|
||||
},
|
||||
|
||||
stopSimulation: () => {
|
||||
set({ simulationRunning: false });
|
||||
},
|
||||
|
||||
updateNodePositions: (updates) => {
|
||||
set(state => ({
|
||||
nodes: state.nodes.map(node => {
|
||||
const update = updates.find(u => u.id === node.id);
|
||||
return update ? { ...node, x: update.x, y: update.y } : node;
|
||||
}),
|
||||
}));
|
||||
},
|
||||
|
||||
highlightSearch: (query) => {
|
||||
const lowerQuery = query.toLowerCase();
|
||||
set(state => ({
|
||||
filter: { ...state.filter, searchQuery: query },
|
||||
nodes: state.nodes.map(n => ({
|
||||
...n,
|
||||
isHighlighted: query ? n.label.toLowerCase().includes(lowerQuery) : false,
|
||||
})),
|
||||
}));
|
||||
},
|
||||
|
||||
clearHighlight: () => {
|
||||
set(state => ({
|
||||
nodes: state.nodes.map(n => ({ ...n, isHighlighted: false })),
|
||||
}));
|
||||
},
|
||||
|
||||
exportAsImage: async () => {
|
||||
// SVG 导出逻辑在组件中实现
|
||||
return null;
|
||||
},
|
||||
|
||||
getFilteredNodes: () => {
|
||||
const { nodes, filter } = get();
|
||||
return nodes.filter(n => {
|
||||
if (!filter.types.includes(n.type)) return false;
|
||||
if (n.importance < filter.minImportance) return false;
|
||||
if (filter.dateRange.start && n.createdAt < filter.dateRange.start) return false;
|
||||
if (filter.dateRange.end && n.createdAt > filter.dateRange.end) return false;
|
||||
if (filter.searchQuery) {
|
||||
return n.label.toLowerCase().includes(filter.searchQuery.toLowerCase());
|
||||
}
|
||||
return true;
|
||||
});
|
||||
},
|
||||
|
||||
getFilteredEdges: () => {
|
||||
const { edges } = get();
|
||||
const filteredNodes = get().getFilteredNodes();
|
||||
const nodeIds = new Set(filteredNodes.map(n => n.id));
|
||||
|
||||
return edges.filter(e => nodeIds.has(e.source) && nodeIds.has(e.target));
|
||||
},
|
||||
}),
|
||||
{
|
||||
name: 'zclaw-memory-graph',
|
||||
partialize: (state) => ({
|
||||
filter: state.filter,
|
||||
layout: state.layout,
|
||||
showLabels: state.showLabels,
|
||||
}),
|
||||
}
|
||||
)
|
||||
);
|
||||
Reference in New Issue
Block a user