Record synced workspace artifacts in packages

This commit is contained in:
john
2026-07-02 21:55:07 +08:00
parent c86a3fd15b
commit b4ae4a870c
6 changed files with 177 additions and 10 deletions
+87 -8
View File
@@ -39,6 +39,19 @@ function asNumber(value) {
return Number(value ?? 0);
}
function generatedArtifactKindForMime(mimeType, filename) {
const normalizedMime = String(mimeType ?? '').toLowerCase();
const normalizedName = String(filename ?? '').toLowerCase();
if (normalizedMime.startsWith('image/')) return 'generated_image';
if (normalizedName.endsWith('.docx') || normalizedName.endsWith('.doc')) return 'docx';
if (normalizedName.endsWith('.pdf')) return 'pdf';
return 'generated_file';
}
function workspaceAssetDownloadUrl(assetId) {
return `/api/mindspace/v1/assets/${encodeURIComponent(assetId)}/download`;
}
export function normalizeWorkspaceRelativePath(relativePath) {
const normalized = String(relativePath ?? '')
.normalize('NFKC')
@@ -100,7 +113,14 @@ export async function listWorkspaceZoneFiles(workspaceRoot, categoryCode) {
return files;
}
export function createWorkspaceAssetSync({ pool, storageRoot, h5Root, maxFileBytes, idFactory }) {
export function createWorkspaceAssetSync({
pool,
storageRoot,
h5Root,
maxFileBytes,
idFactory,
conversationPackageRegistry = null,
}) {
void storageRoot;
const loadExistingAssets = async (userId, categoryId) => {
const [rows] = await pool.query(
@@ -221,7 +241,16 @@ export function createWorkspaceAssetSync({ pool, storageRoot, h5Root, maxFileByt
[buffer.length, now, category.space_id, userId],
);
await conn.commit();
return { action: 'imported', assetId, filename: file.filename, checksum };
return {
action: 'imported',
assetId,
filename: file.filename,
checksum,
mimeType: detectedMimeType,
sizeBytes: buffer.length,
storageKey: finalStorageKey,
categoryCode: category.category_code,
};
} catch (error) {
await conn.rollback();
throw error;
@@ -323,7 +352,16 @@ export function createWorkspaceAssetSync({ pool, storageRoot, h5Root, maxFileByt
);
}
await conn.commit();
return { action: 'updated', assetId: existing.id, filename: file.filename, checksum };
return {
action: 'updated',
assetId: existing.id,
filename: file.filename,
checksum,
mimeType: detectedMimeType,
sizeBytes: buffer.length,
storageKey: finalStorageKey,
categoryCode: category.category_code,
};
} catch (error) {
await conn.rollback();
throw error;
@@ -332,7 +370,40 @@ export function createWorkspaceAssetSync({ pool, storageRoot, h5Root, maxFileByt
}
};
const syncCategory = async (userId, categoryCode) => {
const registerWorkspaceArtifactForConversation = async (userId, source, result, now = Date.now()) => {
const sessionId = String(source?.sessionId ?? source?.sourceSessionId ?? '').trim();
if (!conversationPackageRegistry || !sessionId || !result?.assetId) return null;
try {
const packageRecord = await conversationPackageRegistry.ensurePackage({
userId,
sessionId,
title: source?.title ?? null,
now,
});
const artifact = await conversationPackageRegistry.recordArtifact({
id: `ca_workspace_${result.assetId}`,
packageId: packageRecord.id,
artifactKind: generatedArtifactKindForMime(result.mimeType, result.filename),
role: 'assistant',
assetId: result.assetId,
messageId: source?.messageId ?? source?.sourceMessageId ?? null,
displayName: result.filename,
mimeType: result.mimeType,
sizeBytes: result.sizeBytes,
storageKey: result.storageKey,
canonicalUrl: workspaceAssetDownloadUrl(result.assetId),
sortOrder: now,
now,
});
await conversationPackageRegistry.writeManifestForSession({ userId, sessionId });
return artifact;
} catch (error) {
console.warn('[MindSpace] workspace conversation artifact registration failed:', error?.message ?? error);
return null;
}
};
const syncCategory = async (userId, categoryCode, source = {}) => {
if (!h5Root) return { imported: 0, updated: 0, skipped: 0 };
const workspaceRoot = resolveUserWorkspaceRoot(h5Root, { id: userId });
const files = await listWorkspaceZoneFiles(workspaceRoot, categoryCode);
@@ -369,11 +440,13 @@ export function createWorkspaceAssetSync({ pool, storageRoot, h5Root, maxFileByt
continue;
}
if (existing) {
await updateWorkspaceFile(userId, category, existing, file, buffer);
const result = await updateWorkspaceFile(userId, category, existing, file, buffer);
await registerWorkspaceArtifactForConversation(userId, source, result);
existing.checksum = checksum;
updated += 1;
} else {
await importWorkspaceFile(userId, category, file, buffer);
const result = await importWorkspaceFile(userId, category, file, buffer);
await registerWorkspaceArtifactForConversation(userId, source, result);
existingByName.set(file.filename, { checksum });
imported += 1;
}
@@ -381,13 +454,14 @@ export function createWorkspaceAssetSync({ pool, storageRoot, h5Root, maxFileByt
return { imported, updated, skipped };
};
const syncUserWorkspace = async (userId, { categoryCode } = {}) => {
const syncUserWorkspace = async (userId, { categoryCode, sourceSessionId, sourceMessageId, title } = {}) => {
const codes = categoryCode ? [categoryCode] : UPLOAD_ZONE_CODES;
let imported = 0;
let updated = 0;
let skipped = 0;
const source = { sessionId: sourceSessionId, messageId: sourceMessageId, title };
for (const code of codes) {
const result = await syncCategory(userId, code);
const result = await syncCategory(userId, code, source);
imported += result.imported;
updated += result.updated;
skipped += result.skipped;
@@ -398,6 +472,11 @@ export function createWorkspaceAssetSync({ pool, storageRoot, h5Root, maxFileByt
return { syncUserWorkspace, syncCategory, listWorkspaceZoneFiles };
}
export const workspaceSyncInternals = {
generatedArtifactKindForMime,
workspaceAssetDownloadUrl,
};
export function startWorkspaceAssetSyncWatcher({ publishRoot, syncUserWorkspaceByDirKey }) {
if (!publishRoot || !syncUserWorkspaceByDirKey) return () => {};