Record page drafts in conversation packages
This commit is contained in:
+63
-1
@@ -257,6 +257,7 @@ export function createPageService(pool, options = {}) {
|
|||||||
const storageRoot = path.resolve(options.storageRoot ?? path.join(process.cwd(), 'data', 'mindspace'));
|
const storageRoot = path.resolve(options.storageRoot ?? path.join(process.cwd(), 'data', 'mindspace'));
|
||||||
const h5Root = options.h5Root ? path.resolve(options.h5Root) : null;
|
const h5Root = options.h5Root ? path.resolve(options.h5Root) : null;
|
||||||
const idFactory = options.idFactory ?? (() => crypto.randomUUID());
|
const idFactory = options.idFactory ?? (() => crypto.randomUUID());
|
||||||
|
const conversationPackageRegistry = options.conversationPackageRegistry ?? null;
|
||||||
|
|
||||||
const resolveWorkspacePublishDir = async (userId) => {
|
const resolveWorkspacePublishDir = async (userId) => {
|
||||||
if (!h5Root) return null;
|
if (!h5Root) return null;
|
||||||
@@ -553,7 +554,20 @@ export function createPageService(pool, options = {}) {
|
|||||||
}).catch(() => {});
|
}).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) {
|
} catch (error) {
|
||||||
await conn.rollback();
|
await conn.rollback();
|
||||||
if (writtenPath) await fs.rm(writtenPath, { force: true }).catch(() => {});
|
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 = {
|
export const pageInternals = {
|
||||||
escapeHtml,
|
escapeHtml,
|
||||||
normalizePageInput,
|
normalizePageInput,
|
||||||
@@ -1449,6 +1510,7 @@ export const pageInternals = {
|
|||||||
previewContentSecurityPolicy,
|
previewContentSecurityPolicy,
|
||||||
isHtmlPage,
|
isHtmlPage,
|
||||||
renderPublicationHtml,
|
renderPublicationHtml,
|
||||||
|
registerPageArtifactForConversation,
|
||||||
redactContent,
|
redactContent,
|
||||||
buildPageDeleteSummary,
|
buildPageDeleteSummary,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -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/ai-robot-report.html'), 'public/ai-robot-report.html');
|
||||||
assert.equal(normalizeWorkspaceRelativePath('/public/demo.html'), 'public/demo.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
@@ -42,6 +42,10 @@ import { createMindSpaceService, DEFAULT_MAX_FILE_BYTES } from './mindspace.mjs'
|
|||||||
import { ensureMindSpaceConfig } from './mindspace-config.mjs';
|
import { ensureMindSpaceConfig } from './mindspace-config.mjs';
|
||||||
import { createAssetService } from './mindspace-assets.mjs';
|
import { createAssetService } from './mindspace-assets.mjs';
|
||||||
import { createPageService, pageInternals, inlinePrivateAssetsInHtml, normalizeWorkspaceRelativePath } from './mindspace-pages.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 { createPageLiveEditService } from './mindspace-page-live-edit.mjs';
|
||||||
import { createAssetAgentService } from './mindspace-asset-agent.mjs';
|
import { createAssetAgentService } from './mindspace-asset-agent.mjs';
|
||||||
import { createPageEditSessionService } from './mindspace-page-edit-session.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),
|
monthlyViewLimit: Number(process.env.MINDSPACE_FREE_MONTHLY_VIEW_LIMIT ?? 1000),
|
||||||
scheduleService,
|
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, {
|
mindSpaceAssets = createAssetService(pool, {
|
||||||
h5Root: __dirname,
|
h5Root: __dirname,
|
||||||
storageRoot:
|
storageRoot: mindSpaceStorageRoot,
|
||||||
process.env.MINDSPACE_STORAGE_ROOT ?? path.join(__dirname, 'data', 'mindspace'),
|
|
||||||
maxFileBytes: Number(process.env.MINDSPACE_MAX_FILE_BYTES ?? DEFAULT_MAX_FILE_BYTES),
|
maxFileBytes: Number(process.env.MINDSPACE_MAX_FILE_BYTES ?? DEFAULT_MAX_FILE_BYTES),
|
||||||
});
|
});
|
||||||
mindSpacePages = createPageService(pool, {
|
mindSpacePages = createPageService(pool, {
|
||||||
h5Root: __dirname,
|
h5Root: __dirname,
|
||||||
storageRoot:
|
storageRoot: mindSpaceStorageRoot,
|
||||||
process.env.MINDSPACE_STORAGE_ROOT ?? path.join(__dirname, 'data', 'mindspace'),
|
conversationPackageRegistry,
|
||||||
});
|
});
|
||||||
const resolveUserIdForAgentSession = async (sessionId) => {
|
const resolveUserIdForAgentSession = async (sessionId) => {
|
||||||
const [rows] = await pool.query(
|
const [rows] = await pool.query(
|
||||||
@@ -325,8 +337,7 @@ async function bootstrapUserAuth() {
|
|||||||
});
|
});
|
||||||
mindSpacePublications = createPublicationService(pool, {
|
mindSpacePublications = createPublicationService(pool, {
|
||||||
h5Root: __dirname,
|
h5Root: __dirname,
|
||||||
storageRoot:
|
storageRoot: mindSpaceStorageRoot,
|
||||||
process.env.MINDSPACE_STORAGE_ROOT ?? path.join(__dirname, 'data', 'mindspace'),
|
|
||||||
publicPageLimit: Number(process.env.MINDSPACE_FREE_PUBLIC_PAGE_LIMIT ?? 5),
|
publicPageLimit: Number(process.env.MINDSPACE_FREE_PUBLIC_PAGE_LIMIT ?? 5),
|
||||||
});
|
});
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user