import type { PluginConfigSchema } from '@/lib/plugin-api' import { getApiBaseUrl } from './api-base' import { isElectron } from './runtime' async function getMemoryApiBase(): Promise { 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(path: string, init?: RequestInit): Promise { 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 } export interface MemoryGraphNodePayload { id: string name: string attributes?: Record } 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 MemoryGraphSearchItem { type: 'entity' | 'relation' title: string matched_field: string matched_value: string entity_name?: string entity_hash?: string appearance_count?: number subject?: string predicate?: string object?: string relation_hash?: string confidence?: number created_at?: number } export interface MemoryGraphSearchPayload { success: boolean query: string limit: number count: number items: MemoryGraphSearchItem[] error?: string } 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 { entity_name?: string } export interface MemoryEvidenceRelationNodeMetadata extends Record { hash?: string subject?: string predicate?: string object?: string confidence?: number paragraph_count?: number paragraph_hashes?: string[] text?: string } export interface MemoryEvidenceParagraphNodeMetadata extends Record { hash?: string source?: string updated_at?: number | null entity_count?: number relation_count?: number preview?: string } export type MemoryEvidenceGraphNodeMetadata = | MemoryEvidenceEntityNodeMetadata | MemoryEvidenceRelationNodeMetadata | MemoryEvidenceParagraphNodeMetadata | Record 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 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 error?: string } export interface MemoryConfigPayload { success: boolean config: Record path: string } export interface MemoryRawConfigPayload { success: boolean config: string path: string exists?: boolean using_default?: boolean } export interface MemoryConfigSchemaPayload { success: boolean schema: PluginConfigSchema path: string } export interface MemoryImportGuidePayload { success: boolean content: string source?: string path?: string settings?: MemoryImportSettings } 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 } export type MemoryImportInputMode = 'text' | 'json' export type MemoryImportTaskKind = | 'upload' | 'paste' | 'raw_scan' | 'lpmm_openie' | 'lpmm_convert' | 'temporal_backfill' | 'maibot_migration' export interface MemoryImportSettings { max_queue_size?: number max_files_per_task?: number max_file_size_mb?: number max_paste_chars?: number default_file_concurrency?: number default_chunk_concurrency?: number max_file_concurrency?: number max_chunk_concurrency?: number poll_interval_ms?: number maibot_source_db_default?: string maibot_target_data_dir?: string path_aliases?: Record llm_retry?: Record convert_enable_staging_switch?: boolean convert_keep_backup_count?: number } export interface MemoryImportSettingsPayload { success: boolean settings: MemoryImportSettings } export interface MemoryImportPathAliasesPayload { success: boolean path_aliases: Record } export interface MemoryImportResolvePathPayload { success?: boolean alias: string relative_path: string resolved_path: string exists: boolean is_file: boolean is_dir: boolean error?: string } export interface MemoryImportChunkPayload { chunk_id: string index: number chunk_type: string status: string step: string failed_at: string retryable: boolean error: string progress: number content_preview: string updated_at: number } export interface MemoryImportFilePayload { file_id: string name: string source_kind: string input_mode: MemoryImportInputMode status: string current_step: string detected_strategy_type: string total_chunks: number done_chunks: number failed_chunks: number cancelled_chunks: number progress: number error: string created_at: number updated_at: number source_path?: string content_hash?: string retry_chunk_indexes?: number[] retry_mode?: string chunks?: MemoryImportChunkPayload[] } export interface MemoryImportRetrySummary { chunk_retry_files?: number chunk_retry_chunks?: number file_fallback_files?: number skipped_files?: number parent_task_id?: string skipped_details?: Array> } export interface MemoryImportTaskPayload extends MemoryTaskPayload { task_id: string source: string status: string current_step: string total_chunks: number done_chunks: number failed_chunks: number cancelled_chunks: number progress: number error: string file_count: number created_at: number started_at?: number | null finished_at?: number | null updated_at: number task_kind?: MemoryImportTaskKind | string schema_detected?: string artifact_paths?: Record rollback_info?: Record retry_parent_task_id?: string retry_summary?: MemoryImportRetrySummary params?: Record files?: MemoryImportFilePayload[] } export interface MemoryImportTaskListPayload { success: boolean items: MemoryImportTaskPayload[] count?: number settings?: MemoryImportSettings } export interface MemoryImportTaskDetailPayload { success: boolean task?: MemoryImportTaskPayload error?: string } export interface MemoryImportChunkListPayload { success: boolean task_id?: string file_id?: string offset?: number limit?: number total?: number items?: MemoryImportChunkPayload[] error?: string } export interface MemoryImportActionPayload { success: boolean task?: MemoryImportTaskPayload error?: string } export interface MemoryTuningProfilePayload { success: boolean profile?: Record settings?: Record 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 reason?: string requested_by?: string } export interface MemoryDeletePreviewPayload { success: boolean mode: string selector: Record | 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 created_at?: number } export interface MemoryDeleteOperationPayload { operation_id: string mode: string selector?: Record | string reason?: string | null requested_by?: string | null status?: string created_at?: number restored_at?: number | null summary?: Record 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 MemoryFeedbackAffectedCountsPayload { relations?: number stale_paragraphs?: number episode_sources?: number profile_person_ids?: number correction_paragraphs?: number corrected_relations?: number } export interface MemoryFeedbackActionLogPayload { id: number task_id: number query_tool_id: string action_type: string target_hash: string reason?: string before_payload?: Record after_payload?: Record created_at?: number } export interface MemoryFeedbackCorrectionSummaryPayload { task_id: number query_tool_id: string session_id: string query_text: string query_timestamp?: number task_status: string decision: string decision_confidence: number feedback_message_count: number rollback_status: string affected_counts: MemoryFeedbackAffectedCountsPayload created_at?: number updated_at?: number } export interface MemoryFeedbackCorrectionDetailTaskPayload extends MemoryFeedbackCorrectionSummaryPayload { query_snapshot?: Record decision_payload?: Record rollback_plan_summary?: Record rollback_result?: Record rollback_error?: string rollback_requested_by?: string rollback_reason?: string rollback_requested_at?: number rolled_back_at?: number action_logs?: MemoryFeedbackActionLogPayload[] } export interface MemoryFeedbackCorrectionListPayload { success: boolean items: MemoryFeedbackCorrectionSummaryPayload[] count?: number error?: string } export interface MemoryFeedbackCorrectionDetailPayload { success: boolean task?: MemoryFeedbackCorrectionDetailTaskPayload | null error?: string } export interface MemoryFeedbackCorrectionRollbackPayload { success: boolean already_rolled_back?: boolean result?: Record task?: MemoryFeedbackCorrectionDetailTaskPayload | 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 interface MemoryEpisodeItemPayload extends Record { episode_id?: string id?: string title?: string summary?: string content?: string source?: string person_id?: string time_start?: number | null time_end?: number | null created_at?: number | null updated_at?: number | null } export interface MemoryEpisodeParagraphPayload extends Record { hash?: string content?: string preview?: string source?: string created_at?: number | null updated_at?: number | null } export interface MemoryEpisodeListPayload { success: boolean items: MemoryEpisodeItemPayload[] count?: number error?: string } export interface MemoryEpisodeDetailPayload { success: boolean episode?: MemoryEpisodeItemPayload & { paragraphs?: MemoryEpisodeParagraphPayload[] } error?: string } export interface MemoryEpisodeStatusPayload extends Record { success: boolean pending_queue?: number counts?: Record failed?: Array> error?: string } export interface MemoryEpisodeActionPayload extends Record { success: boolean error?: string detail?: string } export interface MemoryProfileItemPayload extends Record { person_id: string profile_version?: number profile_text?: string updated_at?: number | null expires_at?: number | null source_note?: string has_manual_override?: boolean manual_override?: Record | string | null } export interface MemoryProfileListPayload { success: boolean items: MemoryProfileItemPayload[] count?: number error?: string } export interface MemoryProfileQueryPayload extends Record { success?: boolean profile?: MemoryProfileItemPayload | Record person_id?: string profile_text?: string evidence?: Array> error?: string } export interface MemoryProfileOverridePayload extends Record { success: boolean override?: Record deleted?: boolean person_id?: string error?: string } export interface MemoryMaintenanceItemPayload extends Record { hash?: string relation_hash?: string subject?: string predicate?: string object?: string text?: string deleted_at?: number | null updated_at?: number | null source?: string } export interface MemoryRecycleBinPayload { success: boolean items: MemoryMaintenanceItemPayload[] count?: number error?: string } export interface MemoryMaintenanceActionPayload extends Record { success: boolean detail?: string error?: string } export async function getMemoryGraph(limit: number = 120): Promise { return requestJson(`/graph?limit=${limit}`) } export async function getMemoryGraphSearch( query: string, limit: number = 50, ): Promise { const params = new URLSearchParams({ query, limit: String(limit), }) return requestJson(`/graph/search?${params.toString()}`) } export async function getMemoryGraphNodeDetail( nodeId: string, options?: { relationLimit?: number paragraphLimit?: number evidenceNodeLimit?: number }, ): Promise { 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(`/graph/node-detail?${params.toString()}`) } export async function getMemoryGraphEdgeDetail( source: string, target: string, options?: { paragraphLimit?: number evidenceNodeLimit?: number }, ): Promise { const params = new URLSearchParams({ source, target, paragraph_limit: String(options?.paragraphLimit ?? 20), evidence_node_limit: String(options?.evidenceNodeLimit ?? 80), }) return requestJson(`/graph/edge-detail?${params.toString()}`) } export async function previewMemoryDelete( payload: MemoryDeleteRequestPayload, ): Promise { return requestJson('/delete/preview', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function executeMemoryDelete( payload: MemoryDeleteRequestPayload, ): Promise { return requestJson('/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 reason?: string requested_by?: string }): Promise> { 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 { const params = new URLSearchParams({ limit: String(limit) }) if (mode.trim()) { params.set('mode', mode) } return requestJson(`/delete/operations?${params.toString()}`) } export async function getMemoryDeleteOperation( operationId: string, ): Promise { return requestJson(`/delete/operations/${encodeURIComponent(operationId)}`) } export async function getMemoryFeedbackCorrections( options?: { limit?: number status?: string rollbackStatus?: string query?: string }, ): Promise { const params = new URLSearchParams({ limit: String(options?.limit ?? 50), }) if (options?.status?.trim()) { params.set('status', options.status.trim()) } if (options?.rollbackStatus?.trim()) { params.set('rollback_status', options.rollbackStatus.trim()) } if (options?.query?.trim()) { params.set('query', options.query.trim()) } return requestJson(`/feedback-corrections?${params.toString()}`) } export async function getMemoryFeedbackCorrection( taskId: number, ): Promise { return requestJson(`/feedback-corrections/${taskId}`) } export async function rollbackMemoryFeedbackCorrection( taskId: number, payload: { requested_by?: string reason?: string }, ): Promise { return requestJson(`/feedback-corrections/${taskId}/rollback`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function getMemorySources(): Promise { return requestJson('/sources') } export async function getMemoryEpisodes(options?: { query?: string limit?: number source?: string personId?: string timeStart?: number timeEnd?: number }): Promise { const params = new URLSearchParams({ query: options?.query ?? '', limit: String(options?.limit ?? 20), source: options?.source ?? '', person_id: options?.personId ?? '', }) if (options?.timeStart !== undefined) { params.set('time_start', String(options.timeStart)) } if (options?.timeEnd !== undefined) { params.set('time_end', String(options.timeEnd)) } return requestJson(`/episodes?${params.toString()}`) } export async function getMemoryEpisode(episodeId: string): Promise { return requestJson(`/episodes/${encodeURIComponent(episodeId)}`) } export async function rebuildMemoryEpisodes(payload: { source?: string sources?: string[] all?: boolean }): Promise { return requestJson('/episodes/rebuild', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function getMemoryEpisodeStatus(limit: number = 20): Promise { return requestJson(`/episodes/status?limit=${limit}`) } export async function processMemoryEpisodePending(payload: { limit?: number max_retry?: number }): Promise { return requestJson('/episodes/process-pending', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function getMemoryProfiles(limit: number = 50): Promise { return requestJson(`/profiles?limit=${limit}`) } export async function queryMemoryProfile(options: { personId?: string personKeyword?: string platform?: string userId?: string limit?: number forceRefresh?: boolean }): Promise { const params = new URLSearchParams({ person_id: options.personId ?? '', person_keyword: options.personKeyword ?? '', platform: options.platform ?? '', user_id: options.userId ?? '', limit: String(options.limit ?? 12), force_refresh: options.forceRefresh ? 'true' : 'false', }) return requestJson(`/profiles/query?${params.toString()}`) } export async function setMemoryProfileOverride(payload: { person_id: string override_text: string updated_by?: string source?: string }): Promise { return requestJson('/profiles/override', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function deleteMemoryProfileOverride(personId: string): Promise { return requestJson(`/profiles/override/${encodeURIComponent(personId)}`, { method: 'DELETE', }) } export async function getMemoryRecycleBin(limit: number = 50): Promise { return requestJson(`/maintenance/recycle-bin?limit=${limit}`) } function maintainMemory(path: string, payload: { target: string; hours?: number }): Promise { return requestJson(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function restoreMaintainedMemory(target: string): Promise { return maintainMemory('/maintenance/restore', { target }) } export async function reinforceMemory(target: string): Promise { return maintainMemory('/maintenance/reinforce', { target }) } export async function freezeMemory(target: string): Promise { return maintainMemory('/maintenance/freeze', { target }) } export async function protectMemory(target: string, hours?: number): Promise { return maintainMemory('/maintenance/protect', hours === undefined ? { target } : { target, hours }) } export async function getMemoryRuntimeConfig(): Promise { return requestJson('/runtime/config') } export async function refreshMemoryRuntimeSelfCheck(): Promise { return requestJson('/runtime/self-check/refresh', { method: 'POST', }) } export async function getMemoryConfigSchema(): Promise { return requestJson('/config/schema') } export async function getMemoryConfig(): Promise { return requestJson('/config') } export async function updateMemoryConfig(config: Record): Promise<{ success: boolean; message?: string }> { return requestJson('/config', { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ config }), }) } export async function getMemoryConfigRaw(): Promise { return requestJson('/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 { return requestJson('/import/guide') } export async function getMemoryImportSettings(): Promise { return requestJson('/import/settings') } export async function getMemoryImportPathAliases(): Promise { return requestJson('/import/path-aliases') } export async function resolveMemoryImportPath(payload: { alias: string relative_path?: string must_exist?: boolean }): Promise { return requestJson('/import/resolve-path', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function getMemoryImportTasks(limit: number = 20): Promise { return requestJson(`/import/tasks?limit=${limit}`) } export async function getMemoryImportTask(taskId: string, includeChunks: boolean = false): Promise { return requestJson( `/import/tasks/${encodeURIComponent(taskId)}?include_chunks=${includeChunks ? 'true' : 'false'}`, ) } export async function getMemoryImportTaskChunks( taskId: string, fileId: string, offset: number = 0, limit: number = 50, ): Promise { return requestJson( `/import/tasks/${encodeURIComponent(taskId)}/chunks/${encodeURIComponent(fileId)}?offset=${offset}&limit=${limit}`, ) } export async function createMemoryUploadImport(files: File[], payload: Record): Promise { const formData = new FormData() files.forEach((file) => { formData.append('files', file) }) formData.append('payload_json', JSON.stringify(payload)) return requestJson('/import/upload', { method: 'POST', body: formData, }) } export async function createMemoryPasteImport(payload: Record): Promise { return requestJson('/import/paste', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function createMemoryRawScanImport(payload: Record): Promise { return requestJson('/import/raw-scan', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function createMemoryLpmmOpenieImport(payload: Record): Promise { return requestJson('/import/lpmm-openie', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function createMemoryLpmmConvertImport(payload: Record): Promise { return requestJson('/import/lpmm-convert', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function createMemoryTemporalBackfillImport(payload: Record): Promise { return requestJson('/import/temporal-backfill', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function createMemoryMaibotMigrationImport(payload: Record): Promise { return requestJson('/import/maibot-migration', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function cancelMemoryImportTask(taskId: string): Promise { return requestJson(`/import/tasks/${encodeURIComponent(taskId)}/cancel`, { method: 'POST', }) } export async function retryMemoryImportTask( taskId: string, payload: { overrides?: Record } = {}, ): Promise { return requestJson(`/import/tasks/${encodeURIComponent(taskId)}/retry`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) } export async function getMemoryTuningProfile(): Promise { return requestJson('/retrieval_tuning/profile') } export async function getMemoryTuningTasks(limit: number = 20): Promise { return requestJson(`/retrieval_tuning/tasks?limit=${limit}`) } export async function createMemoryTuningTask(payload: Record): 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}`) }