Record published pages in conversation packages
This commit is contained in:
@@ -296,6 +296,7 @@ export function createPublicationService(pool, options = {}) {
|
|||||||
const h5Root = options.h5Root ?? null;
|
const h5Root = options.h5Root ?? null;
|
||||||
const idFactory = options.idFactory ?? (() => crypto.randomUUID());
|
const idFactory = options.idFactory ?? (() => crypto.randomUUID());
|
||||||
const publicPageLimitFallback = Number(options.publicPageLimit ?? 5);
|
const publicPageLimitFallback = Number(options.publicPageLimit ?? 5);
|
||||||
|
const conversationPackageRegistry = options.conversationPackageRegistry ?? null;
|
||||||
|
|
||||||
let imgproxySigner = null;
|
let imgproxySigner = null;
|
||||||
if (process.env.IMGPROXY_BASE_URL && process.env.IMGPROXY_SIGNING_KEY && process.env.IMGPROXY_SIGNING_SALT) {
|
if (process.env.IMGPROXY_BASE_URL && process.env.IMGPROXY_SIGNING_KEY && process.env.IMGPROXY_SIGNING_SALT) {
|
||||||
@@ -362,6 +363,7 @@ export function createPublicationService(pool, options = {}) {
|
|||||||
const loadVersion = async (userId, pageId, pageVersionId) => {
|
const loadVersion = async (userId, pageId, pageVersionId) => {
|
||||||
const [rows] = await pool.query(
|
const [rows] = await pool.query(
|
||||||
`SELECT p.id AS page_id, p.title, p.summary, p.page_type, p.template_id, p.current_version_id,
|
`SELECT p.id AS page_id, p.title, p.summary, p.page_type, p.template_id, p.current_version_id,
|
||||||
|
p.source_session_id, p.source_message_id,
|
||||||
p.user_id, p.space_id, pv.id AS page_version_id, pv.version_no,
|
p.user_id, p.space_id, pv.id AS page_version_id, pv.version_no,
|
||||||
pv.bundle_asset_id, av.storage_key,
|
pv.bundle_asset_id, av.storage_key,
|
||||||
JSON_UNQUOTE(JSON_EXTRACT(pv.source_snapshot_json, '$.relative_path')) AS source_relative_path
|
JSON_UNQUOTE(JSON_EXTRACT(pv.source_snapshot_json, '$.relative_path')) AS source_relative_path
|
||||||
@@ -729,7 +731,7 @@ export function createPublicationService(pool, options = {}) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
await conn.commit();
|
await conn.commit();
|
||||||
return {
|
const publication = {
|
||||||
...publicationResponse({
|
...publicationResponse({
|
||||||
id: publishId,
|
id: publishId,
|
||||||
page_id: pageId,
|
page_id: pageId,
|
||||||
@@ -745,6 +747,17 @@ export function createPublicationService(pool, options = {}) {
|
|||||||
}),
|
}),
|
||||||
findings: result.findings,
|
findings: result.findings,
|
||||||
};
|
};
|
||||||
|
await registerPublicationArtifactForConversation({
|
||||||
|
registry: conversationPackageRegistry,
|
||||||
|
userId,
|
||||||
|
page,
|
||||||
|
publication,
|
||||||
|
bundleAssetId,
|
||||||
|
storageKey,
|
||||||
|
htmlBytes,
|
||||||
|
now,
|
||||||
|
});
|
||||||
|
return publication;
|
||||||
} 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(() => {});
|
||||||
@@ -1046,6 +1059,53 @@ export function createPublicationService(pool, options = {}) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function registerPublicationArtifactForConversation({
|
||||||
|
registry,
|
||||||
|
userId,
|
||||||
|
page,
|
||||||
|
publication,
|
||||||
|
bundleAssetId,
|
||||||
|
storageKey,
|
||||||
|
htmlBytes,
|
||||||
|
now,
|
||||||
|
}) {
|
||||||
|
const sessionId = String(page?.source_session_id ?? '').trim();
|
||||||
|
if (!registry || !sessionId || !publication?.id) return null;
|
||||||
|
try {
|
||||||
|
const packageRecord = await registry.ensurePackage({
|
||||||
|
userId,
|
||||||
|
sessionId,
|
||||||
|
title: page.title,
|
||||||
|
now,
|
||||||
|
});
|
||||||
|
const artifact = await registry.recordArtifact({
|
||||||
|
id: `ca_${publication.id}`,
|
||||||
|
packageId: packageRecord.id,
|
||||||
|
artifactKind: 'public_html',
|
||||||
|
role: 'assistant',
|
||||||
|
assetId: bundleAssetId,
|
||||||
|
pageId: page.page_id,
|
||||||
|
publicationId: publication.id,
|
||||||
|
messageId: page.source_message_id ?? null,
|
||||||
|
displayName: `${page.title}.html`,
|
||||||
|
mimeType: 'text/html',
|
||||||
|
sizeBytes: htmlBytes,
|
||||||
|
storageKey,
|
||||||
|
canonicalUrl: publication.publicUrl,
|
||||||
|
sortOrder: Number.isFinite(Number(publication.publishedAt)) ? Number(publication.publishedAt) : 0,
|
||||||
|
now,
|
||||||
|
});
|
||||||
|
await registry.writeManifestForSession({ userId, sessionId });
|
||||||
|
return artifact;
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(
|
||||||
|
'[MindSpace] conversation package publication artifact registration failed:',
|
||||||
|
error instanceof Error ? error.message : error,
|
||||||
|
);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export const publicationInternals = {
|
export const publicationInternals = {
|
||||||
normalizeSlug,
|
normalizeSlug,
|
||||||
normalizeAccessMode,
|
normalizeAccessMode,
|
||||||
@@ -1062,5 +1122,6 @@ export const publicationInternals = {
|
|||||||
rewriteWorkspacePublicAssetReferences,
|
rewriteWorkspacePublicAssetReferences,
|
||||||
localizePrivateImageReferences,
|
localizePrivateImageReferences,
|
||||||
prepareHtmlPublishContent,
|
prepareHtmlPublishContent,
|
||||||
|
registerPublicationArtifactForConversation,
|
||||||
scanContent, // re-exported from mindspace-content-scan.mjs
|
scanContent, // re-exported from mindspace-content-scan.mjs
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -219,6 +219,74 @@ test('prepareHtmlPublishContent rewrites imgproxy local image urls to public sta
|
|||||||
assert.doesNotMatch(prepared, /plain\/local:\/\//);
|
assert.doesNotMatch(prepared, /plain\/local:\/\//);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('registerPublicationArtifactForConversation records public html artifact', 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 publicationInternals.registerPublicationArtifactForConversation({
|
||||||
|
registry,
|
||||||
|
userId: 'user-1',
|
||||||
|
page: {
|
||||||
|
page_id: 'page-1',
|
||||||
|
title: '发布页',
|
||||||
|
source_session_id: 'session-1',
|
||||||
|
source_message_id: 'message-1',
|
||||||
|
},
|
||||||
|
publication: {
|
||||||
|
id: 'pub-1',
|
||||||
|
publicUrl: 'https://m.tkmind.cn/u/john/pages/published',
|
||||||
|
publishedAt: 3000,
|
||||||
|
},
|
||||||
|
bundleAssetId: 'asset-pub',
|
||||||
|
storageKey: 'users/user-1/publications/pub-1/index.html',
|
||||||
|
htmlBytes: 256,
|
||||||
|
now: 3000,
|
||||||
|
});
|
||||||
|
|
||||||
|
assert.equal(result.id, 'ca_pub-1');
|
||||||
|
assert.deepEqual(calls[0], [
|
||||||
|
'ensurePackage',
|
||||||
|
{
|
||||||
|
userId: 'user-1',
|
||||||
|
sessionId: 'session-1',
|
||||||
|
title: '发布页',
|
||||||
|
now: 3000,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
assert.equal(calls[1][1].artifactKind, 'public_html');
|
||||||
|
assert.equal(calls[1][1].publicationId, 'pub-1');
|
||||||
|
assert.equal(calls[1][1].canonicalUrl, 'https://m.tkmind.cn/u/john/pages/published');
|
||||||
|
assert.deepEqual(calls[2], [
|
||||||
|
'writeManifestForSession',
|
||||||
|
{ userId: 'user-1', sessionId: 'session-1' },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('registerPublicationArtifactForConversation skips pages without source session', async () => {
|
||||||
|
assert.equal(
|
||||||
|
await publicationInternals.registerPublicationArtifactForConversation({
|
||||||
|
registry: { ensurePackage: async () => assert.fail('should not be called') },
|
||||||
|
userId: 'user-1',
|
||||||
|
page: { page_id: 'page-1', title: '发布页' },
|
||||||
|
publication: { id: 'pub-1' },
|
||||||
|
}),
|
||||||
|
null,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
test('prepareHtmlPublishContent rewrites imgproxy urls in srcset and css url contexts', async () => {
|
test('prepareHtmlPublishContent rewrites imgproxy urls in srcset and css url contexts', async () => {
|
||||||
const prepared = await publicationInternals.prepareHtmlPublishContent({
|
const prepared = await publicationInternals.prepareHtmlPublishContent({
|
||||||
pool: {
|
pool: {
|
||||||
|
|||||||
@@ -339,6 +339,7 @@ async function bootstrapUserAuth() {
|
|||||||
h5Root: __dirname,
|
h5Root: __dirname,
|
||||||
storageRoot: mindSpaceStorageRoot,
|
storageRoot: mindSpaceStorageRoot,
|
||||||
publicPageLimit: Number(process.env.MINDSPACE_FREE_PUBLIC_PAGE_LIMIT ?? 5),
|
publicPageLimit: Number(process.env.MINDSPACE_FREE_PUBLIC_PAGE_LIMIT ?? 5),
|
||||||
|
conversationPackageRegistry,
|
||||||
});
|
});
|
||||||
setInterval(async () => {
|
setInterval(async () => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
Reference in New Issue
Block a user