From 4262c8fb5c922a3813da84ad033103a4c6e9d472 Mon Sep 17 00:00:00 2001 From: kris Date: Sun, 29 Mar 2026 15:11:17 +0800 Subject: [PATCH] feat: add attachment storage config model --- src/lib/boss-data.ts | 129 ++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 126 insertions(+), 3 deletions(-) diff --git a/src/lib/boss-data.ts b/src/lib/boss-data.ts index f3c4f06..100f526 100644 --- a/src/lib/boss-data.ts +++ b/src/lib/boss-data.ts @@ -20,7 +20,34 @@ export type MessageKind = | "video_intent" | "forward_notice" | "forward_single" - | "forward_bundle"; + | "forward_bundle" + | "attachment" + | "analysis_card"; +export type AttachmentKind = "image" | "video" | "pdf" | "text" | "office" | "binary"; +export type AttachmentStorageBackend = "server_file" | "aliyun_oss"; +export type AttachmentAnalysisState = + | "not_applicable" + | "queued_auto" + | "ready_manual" + | "processing" + | "completed" + | "failed"; + +export interface MessageAttachment { + attachmentId: string; + fileName: string; + mimeType: string; + fileSizeBytes: number; + attachmentKind: AttachmentKind; + storageBackend: AttachmentStorageBackend; + storagePath: string; + previewAvailable: boolean; + uploadedAt: string; + uploadedBy: string; + analysisState: AttachmentAnalysisState; + analysisSummary?: string; + analysisCardId?: string; +} export interface ForwardSource { sourceProjectId: string; @@ -150,6 +177,23 @@ export interface Message { forwardBundle?: ForwardBundlePayload; } +export interface UserAttachmentStorageConfig { + account: string; + mode: "server_file" | "oss"; + ossProvider?: "aliyun_oss"; + aliyunOss?: { + enabled: boolean; + accessKeyId: string; + accessKeySecretEncrypted: string; + bucket: string; + endpoint: string; + region: string; + prefix?: string; + }; + updatedAt: string; + validatedAt?: string; +} + export interface GoalItem { id: string; text: string; @@ -609,6 +653,7 @@ export interface BossState { otaUpdateLogs: OtaUpdateLog[]; deviceSkills: DeviceSkill[]; appLogs: AppLogEntry[]; + userAttachmentStorageConfigs: UserAttachmentStorageConfig[]; threadContextSnapshots: ThreadContextSnapshot[]; threadHandoffPackages: ThreadHandoffPackage[]; threadContextAlerts: ThreadContextAlert[]; @@ -1016,6 +1061,13 @@ const initialState: BossState = { reason: "初始化默认主控身份", }, ], + userAttachmentStorageConfigs: [ + { + account: PRIMARY_ADMIN_ACCOUNT, + mode: "server_file", + updatedAt: "2026-03-29T00:00:00+08:00", + }, + ], masterAgentTasks: [], otaUpdates: [ { @@ -1847,6 +1899,33 @@ function normalizeMessage(raw: Partial): Message { }; } +function normalizeAttachmentStorageConfig( + raw: Partial, + fallback: UserAttachmentStorageConfig, +): UserAttachmentStorageConfig { + return { + account: raw.account ?? fallback.account, + mode: raw.mode ?? fallback.mode, + ossProvider: raw.ossProvider ?? fallback.ossProvider, + aliyunOss: raw.aliyunOss + ? { + enabled: raw.aliyunOss.enabled ?? fallback.aliyunOss?.enabled ?? false, + accessKeyId: raw.aliyunOss.accessKeyId ?? fallback.aliyunOss?.accessKeyId ?? "", + accessKeySecretEncrypted: + raw.aliyunOss.accessKeySecretEncrypted ?? + fallback.aliyunOss?.accessKeySecretEncrypted ?? + "", + bucket: raw.aliyunOss.bucket ?? fallback.aliyunOss?.bucket ?? "", + endpoint: raw.aliyunOss.endpoint ?? fallback.aliyunOss?.endpoint ?? "", + region: raw.aliyunOss.region ?? fallback.aliyunOss?.region ?? "", + prefix: raw.aliyunOss.prefix ?? fallback.aliyunOss?.prefix, + } + : fallback.aliyunOss, + updatedAt: raw.updatedAt ?? fallback.updatedAt, + validatedAt: raw.validatedAt ?? fallback.validatedAt, + }; +} + function normalizeProject(raw: Partial, fallback?: Project): Project { const base = fallback ?? cloneInitialState().projects[0]; const projectId = raw.id ?? base.id; @@ -2034,6 +2113,15 @@ function normalizeState(raw: Partial | undefined): BossState { mirroredToProject: log.mirroredToProject ?? false, createdAt: log.createdAt ?? nowIso(), })), + userAttachmentStorageConfigs: ensureArray( + raw.userAttachmentStorageConfigs, + base.userAttachmentStorageConfigs, + ).map((config, index) => + normalizeAttachmentStorageConfig( + config, + base.userAttachmentStorageConfigs[index % base.userAttachmentStorageConfigs.length], + ), + ), threadContextSnapshots: ensureArray(raw.threadContextSnapshots, base.threadContextSnapshots).map( (snapshot, index) => ({ ...base.threadContextSnapshots[index % base.threadContextSnapshots.length], @@ -2573,7 +2661,12 @@ export async function readState(): Promise { try { const state = normalizeState(JSON.parse(raw) as Partial); - lastPersistedStateText = JSON.stringify(state, null, 2); + const normalizedText = JSON.stringify(state, null, 2); + lastPersistedStateText = normalizedText; + if (normalizedText !== raw) { + await fs.writeFile(dataFile, normalizedText, "utf8"); + await fs.writeFile(backupFile, normalizedText, "utf8"); + } return state; } catch { const fallbackText = @@ -2582,7 +2675,12 @@ export async function readState(): Promise { JSON.stringify(syncDerivedState(cloneInitialState()), null, 2); await fs.writeFile(dataFile, fallbackText, "utf8"); const state = normalizeState(JSON.parse(fallbackText) as Partial); - lastPersistedStateText = JSON.stringify(state, null, 2); + const normalizedText = JSON.stringify(state, null, 2); + lastPersistedStateText = normalizedText; + if (normalizedText !== fallbackText) { + await fs.writeFile(dataFile, normalizedText, "utf8"); + await fs.writeFile(backupFile, normalizedText, "utf8"); + } return state; } } @@ -2627,6 +2725,31 @@ export async function getDevice(deviceId: string) { return state.devices.find((device) => device.id === deviceId) ?? null; } +export async function getAttachmentStorageConfig(account: string) { + const state = await readState(); + return ( + state.userAttachmentStorageConfigs.find((item) => item.account === account) ?? { + account, + mode: "server_file" as const, + updatedAt: nowIso(), + } + ); +} + +export async function upsertAttachmentStorageConfig(config: UserAttachmentStorageConfig) { + return mutateState((state) => { + const index = state.userAttachmentStorageConfigs.findIndex( + (item) => item.account === config.account, + ); + if (index >= 0) { + state.userAttachmentStorageConfigs[index] = config; + } else { + state.userAttachmentStorageConfigs.push(config); + } + return config; + }); +} + function preferredDeviceForAccount( state: BossState, account: string,