558 lines
14 KiB
JavaScript
558 lines
14 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,
|
|
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 ?? '',
|
|
),
|
|
};
|
|
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,
|
|
);
|
|
autoBind =
|
|
await maybeAutoBindPageDataHtmlPagesFn({
|
|
pool,
|
|
userId: currentUser.id,
|
|
publishDir,
|
|
h5Root,
|
|
storageRoot,
|
|
findPageByRelativePath,
|
|
});
|
|
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') {
|
|
deliveryArtifacts =
|
|
buildPageDataDeliveryArtifactsFromBindResultFn(
|
|
autoBind,
|
|
publishDir,
|
|
{ publicBaseUrl },
|
|
);
|
|
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,
|
|
});
|
|
const {
|
|
linkExists: _linkExists,
|
|
...deliveryEvaluation
|
|
} = evaluated ?? {};
|
|
return attachCanonicalUrls(
|
|
{
|
|
...result,
|
|
deliveryEvaluation,
|
|
},
|
|
currentUser,
|
|
);
|
|
},
|
|
|
|
preparePageDataAfterFinish:
|
|
preparePageDataForUser,
|
|
|
|
prepareWechatPageDataDelivery:
|
|
prepareWechatPageDataForUser,
|
|
|
|
prepareWechatHtmlDelivery:
|
|
prepareWechatHtmlForUser,
|
|
|
|
ensureWechatFreshPageThumbnails:
|
|
ensureWechatFreshPageThumbnailsForUser,
|
|
};
|
|
}
|
|
|
|
export const mindSpacePublicFinishServiceInternals = {
|
|
eventMessages,
|
|
sanitizePageDataPreparation,
|
|
};
|