Files
memind/mindspace-conversation-package-public-html.mjs

133 lines
4.4 KiB
JavaScript

import crypto from 'node:crypto';
import fsPromises from 'node:fs/promises';
import path from 'node:path';
import {
collectOwnPublicHtmlArtifactRefs,
normalizePublicHtmlRelativePath,
} from './mindspace-public-finish-sync.mjs';
import {
buildMindSpacePublicUrlForUser,
resolveMindSpaceUserPublishDir,
} from './mindspace-runtime-config.mjs';
export async function registerPublicHtmlArtifactsForConversationPackage({
conversationPackageRegistry,
h5Root,
env = process.env,
user,
publishDir,
sessionId,
title = null,
relativePaths = [],
artifactRefs = [],
} = {}) {
if (
!conversationPackageRegistry ||
!user?.id ||
!publishDir ||
!sessionId ||
((!Array.isArray(relativePaths) || relativePaths.length === 0) &&
(!Array.isArray(artifactRefs) || artifactRefs.length === 0))
) {
return [];
}
try {
const publishRoot = path.resolve(publishDir);
const packageTitle = String(title ?? '').trim() || null;
const existingManifest = await conversationPackageRegistry
.readManifestForSession({ userId: user.id, sessionId })
.catch(() => null);
const packageRecord =
existingManifest?.packageId && existingManifest.title
? { id: existingManifest.packageId }
: await conversationPackageRegistry.ensurePackage({
userId: user.id,
sessionId,
title: packageTitle ?? existingManifest?.title ?? null,
});
const recorded = [];
const now = Date.now();
const refs =
Array.isArray(artifactRefs) && artifactRefs.length > 0
? artifactRefs
: relativePaths.map((relativePath) => ({ relativePath }));
for (const ref of refs) {
const relativePath = typeof ref === 'string' ? ref : ref?.relativePath;
const normalized = normalizePublicHtmlRelativePath(relativePath);
if (!normalized || !normalized.toLowerCase().endsWith('.html')) continue;
const absolutePath = path.resolve(publishRoot, normalized);
if (absolutePath !== publishRoot && !absolutePath.startsWith(`${publishRoot}${path.sep}`)) continue;
let stat = null;
try {
stat = await fsPromises.stat(absolutePath);
} catch {
continue;
}
if (!stat.isFile()) continue;
const hash = crypto
.createHash('sha256')
.update(`${user.id}:${sessionId}:${normalized}`)
.digest('hex')
.slice(0, 16);
const artifactId = `ca_public_html_${hash}`;
await conversationPackageRegistry.recordArtifact({
id: artifactId,
packageId: packageRecord.id,
artifactKind: 'public_html',
role: 'assistant',
messageId:
typeof ref?.messageId === 'string' && ref.messageId.trim() ? ref.messageId.trim() : null,
displayName: path.posix.basename(normalized),
mimeType: 'text/html',
sizeBytes: stat.size,
canonicalUrl: buildMindSpacePublicUrlForUser({
h5Root,
env,
user,
relativePath: normalized,
}),
sortOrder: Number.isFinite(stat.mtimeMs) ? Math.round(stat.mtimeMs) : now,
now,
});
recorded.push({ artifactId, relativePath: normalized });
}
if (recorded.length > 0) {
await conversationPackageRegistry.writeManifestForSession({
userId: user.id,
sessionId,
});
}
return recorded;
} catch (error) {
console.warn('[MindSpace] failed to record public html artifacts:', error?.message ?? error);
return [];
}
}
export function createConversationPackagePublicHtmlHydrator({
h5Root,
resolveSessionSnapshot,
registerPublicHtmlArtifactsForConversation,
} = {}) {
return async function hydrateConversationPackagePublicHtmlArtifacts({ user, sessionId }) {
if (!user?.id || !sessionId || typeof resolveSessionSnapshot !== 'function') return [];
const snapshot = await resolveSessionSnapshot(sessionId).catch(() => null);
const messages = Array.isArray(snapshot?.messages) ? snapshot.messages : [];
const publishDir = resolveMindSpaceUserPublishDir(h5Root, { id: user.id });
const artifactRefs = collectOwnPublicHtmlArtifactRefs({
messages,
currentUser: user,
publishDir,
});
return (
(await registerPublicHtmlArtifactsForConversation?.({
user,
publishDir,
sessionId,
title: snapshot?.session?.name,
artifactRefs,
})) ?? []
);
};
}