Record synced workspace artifacts in packages
This commit is contained in:
@@ -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 () => {};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user