refactor: 将 A_Memorix 重构为主线长期记忆子系统并重建管理界面

- 将 A_Memorix 从旧 submodule / 插件形态迁入主线源码,主体落到 src/A_memorix
- 调整主程序接入方式,使 A_Memorix 作为源码内长期记忆子系统运行
- 回收父项目插件体系中针对 A_Memorix 的特判,减少对 plugin 通用层的侵入
- 将长期记忆配置、运行时、自检、导入、调优等能力收口到 memory 路由与主线服务层
- 重做长期记忆控制台与图谱页面,按 MaiBot 现有 dashboard 风格接入
- 补充实体关系图与证据视图双视图能力,支持查看节点、关系、段落及其证据链路
- 新增长期记忆配置编辑器与 memory-api,支持主线内配置管理
- 补齐删除管理能力:删除预览、混合删除、来源批量删除、删除操作恢复
- 优化删除预览与删除操作详情的前端展示,支持分页、检索,并以实体名/关系内容/段落摘要替代单纯 hash 展示
- 修复图谱与控制台相关前端问题,包括证据视图切换、查询触发时机、删除弹层空值保护等
- 新增或更新 A_Memorix 相关测试、WebUI 路由测试、前端 vitest 测试与辅助验证脚本
- 移除旧 plugins/A_memorix、.gitmodules 及相关历史维护文档
This commit is contained in:
A-Dawn
2026-04-03 08:08:24 +08:00
parent bf5eb45709
commit 15d436b3a1
136 changed files with 52533 additions and 629 deletions

View File

@@ -0,0 +1,509 @@
import type { PluginConfigSchema } from '@/lib/plugin-api'
import { getApiBaseUrl } from './api-base'
import { isElectron } from './runtime'
async function getMemoryApiBase(): Promise<string> {
if (isElectron()) {
const base = await getApiBaseUrl()
return base ? `${base}/api/webui/memory` : '/api/webui/memory'
}
return import.meta.env.VITE_API_BASE_URL
? `${import.meta.env.VITE_API_BASE_URL}/memory`
: '/api/webui/memory'
}
async function requestJson<T>(path: string, init?: RequestInit): Promise<T> {
const response = await fetch(`${await getMemoryApiBase()}${path}`, init)
if (!response.ok) {
let detail = `${response.status}`
try {
const payload = await response.json()
detail = String(payload?.detail ?? payload?.error ?? detail)
} catch {
// ignore json parsing fallback
}
throw new Error(detail)
}
return response.json() as Promise<T>
}
export interface MemoryGraphNodePayload {
id: string
name: string
attributes?: Record<string, unknown>
}
export interface MemoryGraphEdgePayload {
source: string
target: string
weight: number
relation_hashes?: string[]
predicates?: string[]
relation_count?: number
evidence_count?: number
label?: string
}
export interface MemoryGraphPayload {
success: boolean
nodes: MemoryGraphNodePayload[]
edges: MemoryGraphEdgePayload[]
total_nodes: number
total_edges: number
}
export interface MemoryGraphRelationDetailPayload {
hash: string
subject: string
predicate: string
object: string
text: string
confidence: number
paragraph_count: number
paragraph_hashes: string[]
source_paragraph: string
}
export interface MemoryGraphParagraphDetailPayload {
hash: string
content: string
preview: string
source: string
created_at?: number | null
updated_at?: number | null
entity_count: number
relation_count: number
entities: string[]
relations: string[]
}
export interface MemoryEvidenceGraphNodePayload {
id: string
type: 'entity' | 'relation' | 'paragraph'
content: string
metadata?: MemoryEvidenceGraphNodeMetadata
}
export interface MemoryEvidenceGraphEdgePayload {
source: string
target: string
kind: 'mentions' | 'supports' | 'subject' | 'object'
label: string
weight: number
}
export interface MemoryEvidenceGraphPayload {
nodes: MemoryEvidenceGraphNodePayload[]
edges: MemoryEvidenceGraphEdgePayload[]
focus_entities: string[]
}
export interface MemoryEvidenceEntityNodeMetadata extends Record<string, unknown> {
entity_name?: string
}
export interface MemoryEvidenceRelationNodeMetadata extends Record<string, unknown> {
hash?: string
subject?: string
predicate?: string
object?: string
confidence?: number
paragraph_count?: number
paragraph_hashes?: string[]
text?: string
}
export interface MemoryEvidenceParagraphNodeMetadata extends Record<string, unknown> {
hash?: string
source?: string
updated_at?: number | null
entity_count?: number
relation_count?: number
preview?: string
}
export type MemoryEvidenceGraphNodeMetadata =
| MemoryEvidenceEntityNodeMetadata
| MemoryEvidenceRelationNodeMetadata
| MemoryEvidenceParagraphNodeMetadata
| Record<string, unknown>
export interface MemoryGraphNodeDetailPayload {
success: boolean
node: {
id: string
type: 'entity'
content: string
hash?: string
appearance_count?: number
}
relations: MemoryGraphRelationDetailPayload[]
paragraphs: MemoryGraphParagraphDetailPayload[]
evidence_graph: MemoryEvidenceGraphPayload
}
export interface MemoryGraphEdgeDetailPayload {
success: boolean
edge: MemoryGraphEdgePayload
relations: MemoryGraphRelationDetailPayload[]
paragraphs: MemoryGraphParagraphDetailPayload[]
evidence_graph: MemoryEvidenceGraphPayload
}
export interface MemoryRuntimeConfigPayload {
success: boolean
config: Record<string, unknown>
data_dir: string
embedding_dimension: number
auto_save: boolean
relation_vectors_enabled: boolean
runtime_ready: boolean
embedding_degraded: boolean
embedding_degraded_reason: string
embedding_degraded_since?: number | null
embedding_last_check?: number | null
paragraph_vector_backfill_pending: number
paragraph_vector_backfill_running: number
paragraph_vector_backfill_failed: number
paragraph_vector_backfill_done: number
}
export interface MemoryRuntimeSelfCheckPayload {
success: boolean
report?: Record<string, unknown>
error?: string
}
export interface MemoryConfigPayload {
success: boolean
config: Record<string, unknown>
path: string
}
export interface MemoryRawConfigPayload {
success: boolean
config: string
path: string
}
export interface MemoryConfigSchemaPayload {
success: boolean
schema: PluginConfigSchema
path: string
}
export interface MemoryImportGuidePayload {
success: boolean
content: string
source?: string
path?: string
settings?: Record<string, unknown>
}
export interface MemoryTaskPayload {
task_id?: string
status?: string
mode?: string
created_at?: number
updated_at?: number
[key: string]: unknown
}
export interface MemoryTaskListPayload {
success: boolean
items: MemoryTaskPayload[]
count?: number
settings?: Record<string, unknown>
}
export interface MemoryTuningProfilePayload {
success: boolean
profile?: Record<string, unknown>
settings?: Record<string, unknown>
toml?: string
}
export interface MemoryDeleteCountsPayload {
relations?: number
paragraphs?: number
entities?: number
sources?: number
requested_sources?: number
matched_sources?: number
[key: string]: number | undefined
}
export interface MemoryDeletePreviewItemPayload {
item_type: string
item_hash: string
item_key?: string
label?: string
preview?: string
source?: string
}
export interface MemoryDeleteRequestPayload {
mode: string
selector: Record<string, unknown> | string
reason?: string
requested_by?: string
}
export interface MemoryDeletePreviewPayload {
success: boolean
mode: string
selector: Record<string, unknown> | string
counts: MemoryDeleteCountsPayload
sources: string[]
items: MemoryDeletePreviewItemPayload[]
item_count: number
dry_run?: boolean
requested_source_count?: number
matched_source_count?: number
vector_ids?: string[]
error?: string
}
export interface MemoryDeleteExecutePayload {
success: boolean
mode: string
operation_id: string
counts: MemoryDeleteCountsPayload
sources: string[]
deleted_count: number
deleted_entity_count: number
deleted_relation_count: number
deleted_paragraph_count: number
deleted_source_count: number
deleted_vector_count?: number
requested_source_count?: number
matched_source_count?: number
error?: string
deleted?: boolean | number
}
export interface MemoryDeleteOperationItemPayload {
item_type: string
item_hash: string
item_key?: string
payload?: Record<string, unknown>
created_at?: number
}
export interface MemoryDeleteOperationPayload {
operation_id: string
mode: string
selector?: Record<string, unknown> | string
reason?: string | null
requested_by?: string | null
status?: string
created_at?: number
restored_at?: number | null
summary?: Record<string, unknown>
items?: MemoryDeleteOperationItemPayload[]
}
export interface MemoryDeleteOperationListPayload {
success: boolean
items: MemoryDeleteOperationPayload[]
count?: number
error?: string
}
export interface MemoryDeleteOperationDetailPayload {
success: boolean
operation?: MemoryDeleteOperationPayload | null
error?: string
}
export interface MemorySourceItemPayload {
source: string
paragraph_count?: number
relation_count?: number
episode_rebuild_blocked?: boolean
[key: string]: unknown
}
export interface MemorySourceListPayload {
success: boolean
items: MemorySourceItemPayload[]
count: number
}
export async function getMemoryGraph(limit: number = 120): Promise<MemoryGraphPayload> {
return requestJson<MemoryGraphPayload>(`/graph?limit=${limit}`)
}
export async function getMemoryGraphNodeDetail(
nodeId: string,
options?: {
relationLimit?: number
paragraphLimit?: number
evidenceNodeLimit?: number
},
): Promise<MemoryGraphNodeDetailPayload> {
const params = new URLSearchParams({
node_id: nodeId,
relation_limit: String(options?.relationLimit ?? 20),
paragraph_limit: String(options?.paragraphLimit ?? 20),
evidence_node_limit: String(options?.evidenceNodeLimit ?? 80),
})
return requestJson<MemoryGraphNodeDetailPayload>(`/graph/node-detail?${params.toString()}`)
}
export async function getMemoryGraphEdgeDetail(
source: string,
target: string,
options?: {
paragraphLimit?: number
evidenceNodeLimit?: number
},
): Promise<MemoryGraphEdgeDetailPayload> {
const params = new URLSearchParams({
source,
target,
paragraph_limit: String(options?.paragraphLimit ?? 20),
evidence_node_limit: String(options?.evidenceNodeLimit ?? 80),
})
return requestJson<MemoryGraphEdgeDetailPayload>(`/graph/edge-detail?${params.toString()}`)
}
export async function previewMemoryDelete(
payload: MemoryDeleteRequestPayload,
): Promise<MemoryDeletePreviewPayload> {
return requestJson<MemoryDeletePreviewPayload>('/delete/preview', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function executeMemoryDelete(
payload: MemoryDeleteRequestPayload,
): Promise<MemoryDeleteExecutePayload> {
return requestJson<MemoryDeleteExecutePayload>('/delete/execute', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function restoreMemoryDelete(payload: {
operation_id: string
mode?: string
selector?: Record<string, unknown> | string
reason?: string
requested_by?: string
}): Promise<Record<string, unknown>> {
return requestJson('/delete/restore', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function getMemoryDeleteOperations(
limit: number = 20,
mode: string = '',
): Promise<MemoryDeleteOperationListPayload> {
const params = new URLSearchParams({ limit: String(limit) })
if (mode.trim()) {
params.set('mode', mode)
}
return requestJson<MemoryDeleteOperationListPayload>(`/delete/operations?${params.toString()}`)
}
export async function getMemoryDeleteOperation(
operationId: string,
): Promise<MemoryDeleteOperationDetailPayload> {
return requestJson<MemoryDeleteOperationDetailPayload>(`/delete/operations/${encodeURIComponent(operationId)}`)
}
export async function getMemorySources(): Promise<MemorySourceListPayload> {
return requestJson<MemorySourceListPayload>('/sources')
}
export async function getMemoryRuntimeConfig(): Promise<MemoryRuntimeConfigPayload> {
return requestJson<MemoryRuntimeConfigPayload>('/runtime/config')
}
export async function refreshMemoryRuntimeSelfCheck(): Promise<MemoryRuntimeSelfCheckPayload> {
return requestJson<MemoryRuntimeSelfCheckPayload>('/runtime/self-check/refresh', {
method: 'POST',
})
}
export async function getMemoryConfigSchema(): Promise<MemoryConfigSchemaPayload> {
return requestJson<MemoryConfigSchemaPayload>('/config/schema')
}
export async function getMemoryConfig(): Promise<MemoryConfigPayload> {
return requestJson<MemoryConfigPayload>('/config')
}
export async function updateMemoryConfig(config: Record<string, unknown>): Promise<{ success: boolean; message?: string }> {
return requestJson('/config', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ config }),
})
}
export async function getMemoryConfigRaw(): Promise<MemoryRawConfigPayload> {
return requestJson<MemoryRawConfigPayload>('/config/raw')
}
export async function updateMemoryConfigRaw(config: string): Promise<{ success: boolean; message?: string }> {
return requestJson('/config/raw', {
method: 'PUT',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ config }),
})
}
export async function getMemoryImportGuide(): Promise<MemoryImportGuidePayload> {
return requestJson<MemoryImportGuidePayload>('/import/guide')
}
export async function getMemoryImportSettings(): Promise<Record<string, unknown>> {
return requestJson('/import/settings')
}
export async function getMemoryImportTasks(limit: number = 20): Promise<MemoryTaskListPayload> {
return requestJson<MemoryTaskListPayload>(`/import/tasks?limit=${limit}`)
}
export async function createMemoryPasteImport(payload: Record<string, unknown>): Promise<{ success: boolean; task?: MemoryTaskPayload }> {
return requestJson('/import/paste', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function getMemoryTuningProfile(): Promise<MemoryTuningProfilePayload> {
return requestJson<MemoryTuningProfilePayload>('/retrieval_tuning/profile')
}
export async function getMemoryTuningTasks(limit: number = 20): Promise<MemoryTaskListPayload> {
return requestJson<MemoryTaskListPayload>(`/retrieval_tuning/tasks?limit=${limit}`)
}
export async function createMemoryTuningTask(payload: Record<string, unknown>): Promise<{ success: boolean; task?: MemoryTaskPayload }> {
return requestJson('/retrieval_tuning/tasks', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(payload),
})
}
export async function applyBestMemoryTuningProfile(taskId: string): Promise<{ success: boolean; error?: string }> {
return requestJson(`/retrieval_tuning/tasks/${encodeURIComponent(taskId)}/apply-best`, {
method: 'POST',
})
}
export async function getMemoryTuningReport(taskId: string, format: 'md' | 'json' = 'md'): Promise<{ success: boolean; content: string; path: string; error?: string }> {
return requestJson(`/retrieval_tuning/tasks/${encodeURIComponent(taskId)}/report?format=${format}`)
}