Record page drafts in conversation packages

This commit is contained in:
john
2026-07-02 21:22:43 +08:00
parent 97b648322a
commit 39e4fffe91
3 changed files with 155 additions and 7 deletions
+63 -1
View File
@@ -257,6 +257,7 @@ export function createPageService(pool, options = {}) {
const storageRoot = path.resolve(options.storageRoot ?? path.join(process.cwd(), 'data', 'mindspace'));
const h5Root = options.h5Root ? path.resolve(options.h5Root) : null;
const idFactory = options.idFactory ?? (() => crypto.randomUUID());
const conversationPackageRegistry = options.conversationPackageRegistry ?? null;
const resolveWorkspacePublishDir = async (userId) => {
if (!h5Root) return null;
@@ -553,7 +554,20 @@ export function createPageService(pool, options = {}) {
}).catch(() => {});
});
}
return getPage(userId, pageId);
const page = await getPage(userId, pageId);
await registerPageArtifactForConversation({
registry: conversationPackageRegistry,
userId,
source,
page,
contentAssetId,
contentMimeType,
contentBytes: normalized.contentBytes,
storageKey: stored.storageKey,
versionNo: nextVersionNo,
now,
});
return page;
} catch (error) {
await conn.rollback();
if (writtenPath) await fs.rm(writtenPath, { force: true }).catch(() => {});
@@ -1438,6 +1452,53 @@ export function createPageService(pool, options = {}) {
};
}
async function registerPageArtifactForConversation({
registry,
userId,
source,
page,
contentAssetId,
contentMimeType,
contentBytes,
storageKey,
versionNo,
now,
}) {
const sessionId = String(source?.sessionId ?? '').trim();
if (!registry || !sessionId || !page?.id) return null;
try {
const packageRecord = await registry.ensurePackage({
userId,
sessionId,
title: page.title,
now,
});
const artifact = await registry.recordArtifact({
id: `ca_${page.id}_${page.currentVersionId}`,
packageId: packageRecord.id,
artifactKind: 'page',
role: 'assistant',
assetId: contentAssetId,
pageId: page.id,
messageId: source.messageId ?? page.sourceMessageId ?? null,
displayName: page.title,
mimeType: contentMimeType,
sizeBytes: contentBytes,
storageKey,
sortOrder: Number.isFinite(Number(versionNo)) ? Number(versionNo) : 0,
now,
});
await registry.writeManifestForSession({ userId, sessionId });
return artifact;
} catch (error) {
console.warn(
'[MindSpace] conversation package page artifact registration failed:',
error instanceof Error ? error.message : error,
);
return null;
}
}
export const pageInternals = {
escapeHtml,
normalizePageInput,
@@ -1449,6 +1510,7 @@ export const pageInternals = {
previewContentSecurityPolicy,
isHtmlPage,
renderPublicationHtml,
registerPageArtifactForConversation,
redactContent,
buildPageDeleteSummary,
};
+75
View File
@@ -132,3 +132,78 @@ test('normalizeWorkspaceRelativePath unifies bare html filenames under public/',
assert.equal(normalizeWorkspaceRelativePath('public/ai-robot-report.html'), 'public/ai-robot-report.html');
assert.equal(normalizeWorkspaceRelativePath('/public/demo.html'), 'public/demo.html');
});
test('registerPageArtifactForConversation records page artifact and refreshes manifest', async () => {
const calls = [];
const registry = {
async ensurePackage(input) {
calls.push(['ensurePackage', input]);
return { id: 'cp_session-1' };
},
async recordArtifact(input) {
calls.push(['recordArtifact', input]);
return input;
},
async writeManifestForSession(input) {
calls.push(['writeManifestForSession', input]);
return { storageKey: 'users/user-1/conversations/session-1/manifest.json' };
},
};
const result = await pageInternals.registerPageArtifactForConversation({
registry,
userId: 'user-1',
source: { sessionId: 'session-1', messageId: 'message-1' },
page: {
id: 'page-1',
currentVersionId: 'version-1',
title: '旅行计划',
sourceMessageId: 'message-fallback',
},
contentAssetId: 'asset-1',
contentMimeType: 'text/html',
contentBytes: 128,
storageKey: 'users/user-1/pages/page-1/versions/v1.html',
versionNo: 1,
now: 1000,
});
assert.equal(result.id, 'ca_page-1_version-1');
assert.deepEqual(calls[0], [
'ensurePackage',
{
userId: 'user-1',
sessionId: 'session-1',
title: '旅行计划',
now: 1000,
},
]);
assert.equal(calls[1][1].artifactKind, 'page');
assert.equal(calls[1][1].messageId, 'message-1');
assert.equal(calls[1][1].storageKey, 'users/user-1/pages/page-1/versions/v1.html');
assert.deepEqual(calls[2], [
'writeManifestForSession',
{ userId: 'user-1', sessionId: 'session-1' },
]);
});
test('registerPageArtifactForConversation skips missing registry or session safely', async () => {
assert.equal(
await pageInternals.registerPageArtifactForConversation({
registry: null,
userId: 'user-1',
source: { sessionId: 'session-1' },
page: { id: 'page-1' },
}),
null,
);
assert.equal(
await pageInternals.registerPageArtifactForConversation({
registry: { ensurePackage: async () => assert.fail('should not be called') },
userId: 'user-1',
source: {},
page: { id: 'page-1' },
}),
null,
);
});
+17 -6
View File
@@ -42,6 +42,10 @@ import { createMindSpaceService, DEFAULT_MAX_FILE_BYTES } from './mindspace.mjs'
import { ensureMindSpaceConfig } from './mindspace-config.mjs';
import { createAssetService } from './mindspace-assets.mjs';
import { createPageService, pageInternals, inlinePrivateAssetsInHtml, normalizeWorkspaceRelativePath } from './mindspace-pages.mjs';
import { createConversationPackageRegistry } from './mindspace-conversation-package-registry.mjs';
import { createConversationPackageStore } from './mindspace-conversation-package-store.mjs';
import { createMindSpaceServiceFacade } from './mindspace-service.mjs';
import { createLocalMindSpaceStorageAdapter } from './mindspace-storage-adapter.mjs';
import { createPageLiveEditService } from './mindspace-page-live-edit.mjs';
import { createAssetAgentService } from './mindspace-asset-agent.mjs';
import { createPageEditSessionService } from './mindspace-page-edit-session.mjs';
@@ -297,16 +301,24 @@ async function bootstrapUserAuth() {
monthlyViewLimit: Number(process.env.MINDSPACE_FREE_MONTHLY_VIEW_LIMIT ?? 1000),
scheduleService,
});
const mindSpaceStorageRoot =
process.env.MINDSPACE_STORAGE_ROOT ?? path.join(__dirname, 'data', 'mindspace');
const conversationPackageRegistry = createConversationPackageRegistry({
store: createConversationPackageStore(pool),
service: createMindSpaceServiceFacade({
storageAdapter: createLocalMindSpaceStorageAdapter(mindSpaceStorageRoot),
publicBaseUrl: resolvePublicBaseUrl(),
}),
});
mindSpaceAssets = createAssetService(pool, {
h5Root: __dirname,
storageRoot:
process.env.MINDSPACE_STORAGE_ROOT ?? path.join(__dirname, 'data', 'mindspace'),
storageRoot: mindSpaceStorageRoot,
maxFileBytes: Number(process.env.MINDSPACE_MAX_FILE_BYTES ?? DEFAULT_MAX_FILE_BYTES),
});
mindSpacePages = createPageService(pool, {
h5Root: __dirname,
storageRoot:
process.env.MINDSPACE_STORAGE_ROOT ?? path.join(__dirname, 'data', 'mindspace'),
storageRoot: mindSpaceStorageRoot,
conversationPackageRegistry,
});
const resolveUserIdForAgentSession = async (sessionId) => {
const [rows] = await pool.query(
@@ -325,8 +337,7 @@ async function bootstrapUserAuth() {
});
mindSpacePublications = createPublicationService(pool, {
h5Root: __dirname,
storageRoot:
process.env.MINDSPACE_STORAGE_ROOT ?? path.join(__dirname, 'data', 'mindspace'),
storageRoot: mindSpaceStorageRoot,
publicPageLimit: Number(process.env.MINDSPACE_FREE_PUBLIC_PAGE_LIMIT ?? 5),
});
setInterval(async () => {