fde6503bdf
Memind CI / Test, build, and release guards (push) Has been cancelled
Enable optional SEO/GEO injection and discovery routes for confirmed public pages while keeping private pages noindex. Add premium page template skills, portal catalog API, template shop UI, and Baidu push gated by memind_adm config. Co-authored-by: Cursor <cursoragent@cursor.com>
586 lines
15 KiB
JavaScript
586 lines
15 KiB
JavaScript
import {
|
|
collectOwnPublicHtmlArtifactRefs,
|
|
materializePublicHtmlWritesFromSessionEvent,
|
|
syncPublicHtmlAfterFinish,
|
|
} from './mindspace-public-finish-sync.mjs';
|
|
import {
|
|
buildMindSpacePublicUrlForUser,
|
|
resolveMindSpaceUserPublishDir,
|
|
} from './mindspace-runtime-config.mjs';
|
|
import {
|
|
evaluateH5HtmlFinishGuard,
|
|
} from './mindspace-h5-html-finish-guard.mjs';
|
|
import {
|
|
buildPageDataDeliveryArtifactsFromBindResult,
|
|
ensurePageDataDeliveryReady,
|
|
maybeAutoBindPageDataHtmlPages,
|
|
preparePageDataAfterFinish,
|
|
resolvePageDataCollectOutcomeAsync,
|
|
resolvePageDataRequestRelativePaths,
|
|
rewritePageDataDeliveryLinks,
|
|
} from './mindspace-page-data-finish-guard.mjs';
|
|
import {
|
|
createPageService,
|
|
} from './mindspace-pages.mjs';
|
|
import {
|
|
ensureWechatFreshPageThumbnailsAtWorkspace,
|
|
prepareWechatHtmlDeliveryAtWorkspace,
|
|
} from './mindspace-wechat-html-delivery.mjs';
|
|
|
|
function eventMessages(event, recentCount) {
|
|
if (event?.type === 'Message' && event.message) {
|
|
return [event.message];
|
|
}
|
|
if (
|
|
event?.type === 'UpdateConversation' &&
|
|
Array.isArray(event.conversation)
|
|
) {
|
|
return event.conversation.slice(
|
|
-Math.max(1, recentCount),
|
|
);
|
|
}
|
|
return [];
|
|
}
|
|
|
|
function sanitizePageDataPreparation(value) {
|
|
if (!value) return null;
|
|
return JSON.parse(
|
|
JSON.stringify(value, (key, item) => {
|
|
if (
|
|
[
|
|
'absolutePath',
|
|
'content',
|
|
'localPath',
|
|
].includes(key)
|
|
) {
|
|
return undefined;
|
|
}
|
|
if (item instanceof Map) {
|
|
return Object.fromEntries(item);
|
|
}
|
|
return item;
|
|
}),
|
|
);
|
|
}
|
|
|
|
export function createMindSpacePublicFinishService({
|
|
pool,
|
|
h5Root,
|
|
storageRoot,
|
|
env = process.env,
|
|
syncWorkspaceAssets = null,
|
|
conversationArtifactService,
|
|
resolveMindSpaceUserPublishDirFn =
|
|
resolveMindSpaceUserPublishDir,
|
|
buildMindSpacePublicUrlForUserFn =
|
|
buildMindSpacePublicUrlForUser,
|
|
materializeSessionEventFn =
|
|
materializePublicHtmlWritesFromSessionEvent,
|
|
collectArtifactRefsFn =
|
|
collectOwnPublicHtmlArtifactRefs,
|
|
syncAfterFinishFn = syncPublicHtmlAfterFinish,
|
|
evaluateH5HtmlFinishGuardFn =
|
|
evaluateH5HtmlFinishGuard,
|
|
preparePageDataAfterFinishFn =
|
|
preparePageDataAfterFinish,
|
|
resolvePageDataCollectOutcomeAsyncFn =
|
|
resolvePageDataCollectOutcomeAsync,
|
|
maybeAutoBindPageDataHtmlPagesFn =
|
|
maybeAutoBindPageDataHtmlPages,
|
|
buildPageDataDeliveryArtifactsFromBindResultFn =
|
|
buildPageDataDeliveryArtifactsFromBindResult,
|
|
ensurePageDataDeliveryReadyFn =
|
|
ensurePageDataDeliveryReady,
|
|
rewritePageDataDeliveryLinksFn =
|
|
rewritePageDataDeliveryLinks,
|
|
createPageServiceFn = createPageService,
|
|
prepareWechatHtmlDeliveryAtWorkspaceFn =
|
|
prepareWechatHtmlDeliveryAtWorkspace,
|
|
ensureWechatFreshPageThumbnailsAtWorkspaceFn =
|
|
ensureWechatFreshPageThumbnailsAtWorkspace,
|
|
} = {}) {
|
|
if (
|
|
!pool ||
|
|
typeof pool.query !== 'function' ||
|
|
!h5Root ||
|
|
!storageRoot
|
|
) {
|
|
throw new Error(
|
|
'createMindSpacePublicFinishService requires pool, h5Root, and storageRoot',
|
|
);
|
|
}
|
|
if (
|
|
!conversationArtifactService ||
|
|
typeof conversationArtifactService
|
|
.registerPublicHtmlArtifacts !== 'function'
|
|
) {
|
|
throw new Error(
|
|
'createMindSpacePublicFinishService requires conversationArtifactService',
|
|
);
|
|
}
|
|
|
|
const resolveContext = (userId) => {
|
|
const normalizedUserId = String(userId ?? '').trim();
|
|
if (!normalizedUserId) {
|
|
throw Object.assign(
|
|
new Error('MindSpace public finish requires userId'),
|
|
{ code: 'invalid_input' },
|
|
);
|
|
}
|
|
const currentUser = { id: normalizedUserId };
|
|
return {
|
|
currentUser,
|
|
publishDir: resolveMindSpaceUserPublishDirFn(
|
|
h5Root,
|
|
currentUser,
|
|
),
|
|
};
|
|
};
|
|
|
|
const attachCanonicalUrls = (result, currentUser) => {
|
|
const artifactRefs = Array.isArray(
|
|
result?.publicHtmlArtifactRefs,
|
|
)
|
|
? result.publicHtmlArtifactRefs
|
|
: [];
|
|
return {
|
|
...result,
|
|
publicHtmlArtifacts: artifactRefs.map((ref) => ({
|
|
...ref,
|
|
canonicalUrl: buildMindSpacePublicUrlForUserFn({
|
|
h5Root,
|
|
env,
|
|
user: currentUser,
|
|
relativePath: ref.relativePath,
|
|
}),
|
|
})),
|
|
};
|
|
};
|
|
|
|
const buildCanonicalUrl = (
|
|
currentUser,
|
|
relativePath,
|
|
) =>
|
|
buildMindSpacePublicUrlForUserFn({
|
|
h5Root,
|
|
env,
|
|
user: currentUser,
|
|
relativePath,
|
|
});
|
|
|
|
const syncAndRegisterWechatArtifacts = async ({
|
|
currentUser,
|
|
sessionId,
|
|
relativePaths,
|
|
}) => {
|
|
const normalizedRelativePaths = [
|
|
...new Set(
|
|
relativePaths
|
|
.map((value) =>
|
|
String(value ?? '').trim(),
|
|
)
|
|
.filter(Boolean),
|
|
),
|
|
];
|
|
if (normalizedRelativePaths.length === 0) {
|
|
return;
|
|
}
|
|
if (
|
|
typeof syncWorkspaceAssets === 'function'
|
|
) {
|
|
await syncWorkspaceAssets(currentUser.id, {
|
|
categoryCode: 'public',
|
|
sourceSessionId:
|
|
String(sessionId ?? '').trim() ||
|
|
null,
|
|
onlyRelativePaths:
|
|
normalizedRelativePaths,
|
|
});
|
|
}
|
|
if (String(sessionId ?? '').trim()) {
|
|
await conversationArtifactService
|
|
.registerPublicHtmlArtifacts({
|
|
userId: currentUser.id,
|
|
sessionId,
|
|
relativePaths:
|
|
normalizedRelativePaths.filter(
|
|
(relativePath) =>
|
|
relativePath
|
|
.toLowerCase()
|
|
.endsWith('.html'),
|
|
),
|
|
});
|
|
}
|
|
};
|
|
|
|
const preparePageDataForUser = async ({
|
|
userId,
|
|
messages,
|
|
userText = '',
|
|
} = {}) => {
|
|
const { currentUser, publishDir } =
|
|
resolveContext(userId);
|
|
return sanitizePageDataPreparation(
|
|
await preparePageDataAfterFinishFn({
|
|
userId: currentUser.id,
|
|
publishDir,
|
|
messages,
|
|
pool,
|
|
h5Root,
|
|
storageRoot,
|
|
userText,
|
|
}),
|
|
);
|
|
};
|
|
|
|
const prepareWechatPageDataForUser = async ({
|
|
userId,
|
|
reply = null,
|
|
intent = null,
|
|
publicBaseUrl = null,
|
|
requestStartedAt = 0,
|
|
} = {}) => {
|
|
const { currentUser, publishDir } =
|
|
resolveContext(userId);
|
|
const normalizedReply = {
|
|
text: String(reply?.text ?? ''),
|
|
messages: Array.isArray(reply?.messages)
|
|
? reply.messages
|
|
: [],
|
|
};
|
|
const normalizedIntent = {
|
|
agentText: String(intent?.agentText ?? ''),
|
|
displayText: String(
|
|
intent?.displayText ?? '',
|
|
),
|
|
};
|
|
const scopedRelativePaths = resolvePageDataRequestRelativePaths({
|
|
publishDir,
|
|
messages: normalizedReply.messages,
|
|
requestStartedAt,
|
|
});
|
|
let outcome =
|
|
await resolvePageDataCollectOutcomeAsyncFn({
|
|
reply: normalizedReply,
|
|
intent: normalizedIntent,
|
|
publishDir,
|
|
requestStartedAt,
|
|
pool,
|
|
userId: currentUser.id,
|
|
findPageByRelativePath: null,
|
|
apiBase: publicBaseUrl,
|
|
});
|
|
let autoBind = null;
|
|
if (outcome?.action === 'skip') {
|
|
return sanitizePageDataPreparation({
|
|
outcome,
|
|
autoBind,
|
|
deliveryArtifacts: [],
|
|
deliveryCheck: null,
|
|
rewrittenText: normalizedReply.text,
|
|
});
|
|
}
|
|
|
|
const pageService = createPageServiceFn(pool, {
|
|
h5Root,
|
|
storageRoot,
|
|
});
|
|
const findPageByRelativePath =
|
|
pageService.findPageByRelativePath.bind(
|
|
pageService,
|
|
);
|
|
const relevantRelativePaths = [
|
|
...new Set([
|
|
...scopedRelativePaths,
|
|
...(outcome?.evaluation?.relevantFiles ?? [])
|
|
.map((file) => file?.relativePath)
|
|
.filter(Boolean),
|
|
]),
|
|
];
|
|
autoBind =
|
|
await maybeAutoBindPageDataHtmlPagesFn({
|
|
pool,
|
|
userId: currentUser.id,
|
|
publishDir,
|
|
h5Root,
|
|
storageRoot,
|
|
findPageByRelativePath,
|
|
onlyRelativePaths:
|
|
relevantRelativePaths.length > 0
|
|
? relevantRelativePaths
|
|
: null,
|
|
});
|
|
outcome =
|
|
await resolvePageDataCollectOutcomeAsyncFn({
|
|
reply: normalizedReply,
|
|
intent: normalizedIntent,
|
|
publishDir,
|
|
requestStartedAt,
|
|
pool,
|
|
userId: currentUser.id,
|
|
findPageByRelativePath,
|
|
apiBase: publicBaseUrl,
|
|
});
|
|
|
|
let deliveryArtifacts = [];
|
|
let deliveryCheck = null;
|
|
let rewrittenText = normalizedReply.text;
|
|
if (outcome?.action === 'send') {
|
|
const deliveryRelativePaths = [
|
|
...new Set([
|
|
...scopedRelativePaths,
|
|
...(outcome?.evaluation?.relevantFiles ?? [])
|
|
.map((file) => file?.relativePath)
|
|
.filter(Boolean),
|
|
]),
|
|
];
|
|
deliveryArtifacts =
|
|
buildPageDataDeliveryArtifactsFromBindResultFn(
|
|
autoBind,
|
|
publishDir,
|
|
{ publicBaseUrl },
|
|
deliveryRelativePaths,
|
|
);
|
|
if (deliveryArtifacts.length > 0) {
|
|
deliveryCheck =
|
|
await ensurePageDataDeliveryReadyFn({
|
|
publishDir,
|
|
userId: currentUser.id,
|
|
pool,
|
|
h5Root,
|
|
storageRoot,
|
|
apiBase: publicBaseUrl,
|
|
artifacts: deliveryArtifacts,
|
|
});
|
|
rewrittenText =
|
|
rewritePageDataDeliveryLinksFn(
|
|
normalizedReply.text,
|
|
deliveryArtifacts,
|
|
);
|
|
}
|
|
}
|
|
return sanitizePageDataPreparation({
|
|
outcome,
|
|
autoBind,
|
|
deliveryArtifacts,
|
|
deliveryCheck,
|
|
rewrittenText,
|
|
});
|
|
};
|
|
|
|
const prepareWechatHtmlForUser = async ({
|
|
userId,
|
|
sessionId = null,
|
|
reply = null,
|
|
intent = null,
|
|
requestStartedAt = 0,
|
|
allowRecentArtifacts = true,
|
|
} = {}) => {
|
|
const { currentUser, publishDir } =
|
|
resolveContext(userId);
|
|
const normalizedReply = {
|
|
text: String(reply?.text ?? ''),
|
|
messages: Array.isArray(reply?.messages)
|
|
? reply.messages
|
|
: [],
|
|
};
|
|
const normalizedIntent = {
|
|
agentText: String(
|
|
intent?.agentText ?? '',
|
|
),
|
|
displayText: String(
|
|
intent?.displayText ?? '',
|
|
),
|
|
};
|
|
const prepared =
|
|
prepareWechatHtmlDeliveryAtWorkspaceFn({
|
|
reply: normalizedReply,
|
|
intent: normalizedIntent,
|
|
publishDir,
|
|
requestStartedAt,
|
|
allowRecentArtifacts:
|
|
allowRecentArtifacts === true,
|
|
buildCanonicalUrl: (relativePath) =>
|
|
buildCanonicalUrl(
|
|
currentUser,
|
|
relativePath,
|
|
),
|
|
});
|
|
|
|
await syncAfterFinishFn({
|
|
messages: normalizedReply.messages,
|
|
currentUser,
|
|
publishDir,
|
|
sessionId,
|
|
pool,
|
|
storageRoot,
|
|
h5Root,
|
|
syncWorkspaceAssets,
|
|
registerPublicHtmlArtifacts:
|
|
(_registeredUserId, options) =>
|
|
conversationArtifactService
|
|
.registerPublicHtmlArtifacts({
|
|
userId: currentUser.id,
|
|
sessionId: options?.sessionId,
|
|
relativePaths:
|
|
options?.relativePaths,
|
|
artifactRefs:
|
|
options?.artifactRefs,
|
|
}),
|
|
});
|
|
const confirmedRelativePaths =
|
|
prepared.confirmedArtifacts.map(
|
|
(artifact) => artifact.relativePath,
|
|
);
|
|
await syncAndRegisterWechatArtifacts({
|
|
currentUser,
|
|
sessionId,
|
|
relativePaths: confirmedRelativePaths,
|
|
});
|
|
return prepared;
|
|
};
|
|
|
|
const ensureWechatFreshPageThumbnailsForUser =
|
|
async ({
|
|
userId,
|
|
sessionId = null,
|
|
artifacts = [],
|
|
images = [],
|
|
messages = [],
|
|
repairEnabled = false,
|
|
} = {}) => {
|
|
const { currentUser, publishDir } =
|
|
resolveContext(userId);
|
|
const result =
|
|
await ensureWechatFreshPageThumbnailsAtWorkspaceFn(
|
|
{
|
|
artifacts,
|
|
images,
|
|
messages: Array.isArray(messages)
|
|
? messages
|
|
: [],
|
|
publishDir,
|
|
repairEnabled:
|
|
repairEnabled === true,
|
|
buildCanonicalUrl: (relativePath) =>
|
|
buildCanonicalUrl(
|
|
currentUser,
|
|
relativePath,
|
|
),
|
|
},
|
|
);
|
|
if (result.ok) {
|
|
await syncAndRegisterWechatArtifacts({
|
|
currentUser,
|
|
sessionId,
|
|
relativePaths: [
|
|
...result.matchRelativePaths,
|
|
...result.thumbnailRelativePaths,
|
|
],
|
|
});
|
|
}
|
|
return result;
|
|
};
|
|
|
|
return {
|
|
async materializeSessionEvent({
|
|
userId,
|
|
event,
|
|
recentCount = 20,
|
|
} = {}) {
|
|
const { currentUser, publishDir } =
|
|
resolveContext(userId);
|
|
const result = materializeSessionEventFn(event, {
|
|
publishDir,
|
|
recentCount,
|
|
});
|
|
const publicHtmlArtifactRefs = collectArtifactRefsFn({
|
|
messages: eventMessages(event, recentCount),
|
|
currentUser,
|
|
publishDir,
|
|
materialized: result?.materialized,
|
|
skipped: result?.skipped,
|
|
});
|
|
return attachCanonicalUrls(
|
|
{
|
|
...result,
|
|
publicHtmlRelativePaths:
|
|
publicHtmlArtifactRefs.map(
|
|
(ref) => ref.relativePath,
|
|
),
|
|
publicHtmlArtifactRefs,
|
|
},
|
|
currentUser,
|
|
);
|
|
},
|
|
|
|
async syncAfterFinish({
|
|
userId,
|
|
sessionId,
|
|
messages,
|
|
currentUser: suppliedCurrentUser = null,
|
|
} = {}) {
|
|
const { currentUser, publishDir } =
|
|
resolveContext(userId);
|
|
const result = await syncAfterFinishFn({
|
|
messages,
|
|
currentUser,
|
|
publishDir,
|
|
sessionId,
|
|
pool,
|
|
storageRoot,
|
|
h5Root,
|
|
syncWorkspaceAssets,
|
|
registerPublicHtmlArtifacts:
|
|
(_registeredUserId, options) =>
|
|
conversationArtifactService
|
|
.registerPublicHtmlArtifacts({
|
|
userId: currentUser.id,
|
|
sessionId: options?.sessionId,
|
|
relativePaths: options?.relativePaths,
|
|
artifactRefs: options?.artifactRefs,
|
|
}),
|
|
});
|
|
const evaluated =
|
|
evaluateH5HtmlFinishGuardFn({
|
|
messages,
|
|
currentUser: {
|
|
...suppliedCurrentUser,
|
|
id: currentUser.id,
|
|
},
|
|
publishDir,
|
|
syncResult: result,
|
|
h5Root,
|
|
});
|
|
const {
|
|
linkExists: _linkExists,
|
|
...deliveryEvaluation
|
|
} = evaluated ?? {};
|
|
return attachCanonicalUrls(
|
|
{
|
|
...result,
|
|
deliveryEvaluation,
|
|
},
|
|
currentUser,
|
|
);
|
|
},
|
|
|
|
preparePageDataAfterFinish:
|
|
preparePageDataForUser,
|
|
|
|
prepareWechatPageDataDelivery:
|
|
prepareWechatPageDataForUser,
|
|
|
|
prepareWechatHtmlDelivery:
|
|
prepareWechatHtmlForUser,
|
|
|
|
ensureWechatFreshPageThumbnails:
|
|
ensureWechatFreshPageThumbnailsForUser,
|
|
};
|
|
}
|
|
|
|
export const mindSpacePublicFinishServiceInternals = {
|
|
eventMessages,
|
|
sanitizePageDataPreparation,
|
|
};
|