import fs from 'node:fs/promises'; import path from 'node:path'; import { normalizeWorkspaceRelativePath } from './mindspace-pages.mjs'; export function slugFromPageTitle(title, pageId) { const ascii = String(title ?? '') .normalize('NFKC') .toLowerCase() .replace(/[^a-z0-9]+/g, '-') .replace(/^-+|-+$/g, '') .slice(0, pageId ? 54 : 64); const base = ascii || 'page'; if (pageId) { return `${base}-${String(pageId).replace(/-/g, '').slice(0, 8)}`.slice(0, 64); } return base; } async function resolveExistingPage({ userId, sessionId, messageId, analysis, mindSpacePages, }) { const byMessage = await mindSpacePages .findPageBySourceMessage(userId, sessionId, messageId) .catch(() => null); if (byMessage) return byMessage; if (analysis.contentMode === 'static_html' && analysis.relativePath) { const byPath = await mindSpacePages .findPageByRelativePath(userId, analysis.relativePath) .catch(() => null); if (byPath) return byPath; } return null; } async function buildPageInput({ bundle, body = {} }) { const { source, analysis, resolvedHtml } = bundle; let pageInput = { title: body.title, summary: body.summary, templateId: body.template_id ?? 'editorial', pageType: body.page_type, categoryCode: 'draft', }; if (analysis.contentMode === 'static_html') { if (!resolvedHtml) { throw Object.assign(new Error('无法读取链接页面内容'), { code: 'static_page_not_found' }); } pageInput = { ...pageInput, title: body.title || resolvedHtml.suggestedTitle, summary: body.summary || resolvedHtml.suggestedSummary, content: resolvedHtml.content, contentFormat: 'html', pageType: 'html', }; } else { pageInput = { ...pageInput, title: body.title || 'AI 创作', content: source.content, contentFormat: 'markdown', }; } return pageInput; } export async function ensureChatPageForPlaza({ userId, bundle, mindSpacePages, ensureWorkspaceHtmlThumbnail, publishDir, body = {}, skipThumbnail = false, }) { const { source, analysis, resolvedHtml } = bundle; const sessionId = body.session_id ?? body.sessionId; const messageId = body.message_id ?? body.messageId; const snapshot = { session_name: source.session.name, message_created: source.message.created, role: source.message.role, content_mode: analysis.contentMode, public_url: analysis.previewUrl, relative_path: analysis.relativePath ?? null, }; const pageInput = await buildPageInput({ bundle, body }); const existingPage = await resolveExistingPage({ userId, sessionId, messageId, analysis, mindSpacePages, }); if ( !skipThumbnail && analysis.contentMode === 'static_html' && resolvedHtml?.content && analysis.relativePath && ensureWorkspaceHtmlThumbnail && publishDir ) { await ensureWorkspaceHtmlThumbnail(publishDir, analysis.relativePath, resolvedHtml.content, { title: pageInput.title, subtitle: pageInput.summary, }).catch(() => {}); } if (existingPage) { return mindSpacePages.updatePage(userId, existingPage.id, { ...pageInput, expectedVersion: existingPage.versionNo, }); } return mindSpacePages.createFromChat(userId, pageInput, { sessionId, messageId, snapshot, }); } export async function ensurePagePublicationForPlaza({ userId, page, mindSpacePublications, refreshIfOnline = true, }) { const existing = await mindSpacePublications.getCurrent(userId, page.id); if (existing?.id) { if (refreshIfOnline && page.pageType === 'html' && mindSpacePublications.refreshOnlinePublicationHtml) { return ( (await mindSpacePublications.refreshOnlinePublicationHtml(userId, page.id)) ?? existing ); } return existing; } const preferredSlug = slugFromPageTitle(page.title, page.id); return mindSpacePublications.publish(userId, page.id, { pageVersionId: page.currentVersionId, accessMode: 'public', urlSlug: preferredSlug, autoAcknowledgeFindings: true, }); } function throwAlreadyPublished(postId) { throw Object.assign(new Error('该内容已发布到广场'), { code: 'ALREADY_PUBLISHED', details: { post_id: postId }, }); } async function assertWorkspacePathNotAlreadyOnPlaza({ userId, relativePath, mindSpacePages }) { if (!mindSpacePages?.findPlazaContextByWorkspacePath) return null; const context = await mindSpacePages.findPlazaContextByWorkspacePath(userId, relativePath); if ( context?.postId && ['published', 'pending_review'].includes(String(context.postStatus ?? '')) ) { throwAlreadyPublished(context.postId); } return context; } async function ensureWorkspaceHtmlPageForPlaza({ userId, relativePath, content, title, mindSpacePages, }) { const normalized = normalizePublicHtmlRelativePath(relativePath); let page = await mindSpacePages.findPageByRelativePath(userId, normalized); const pageInput = { title: title ?? page?.title ?? 'MindSpace 页面', content, contentFormat: 'html', pageType: 'html', categoryCode: 'draft', }; if (page) { return mindSpacePages.updatePage(userId, page.id, { ...pageInput, expectedVersion: page.versionNo, }); } return mindSpacePages.createFromChat(userId, pageInput, { snapshot: { content_mode: 'static_html', relative_path: normalized, }, }); } async function publishPageToPlaza({ userId, page, mindSpacePublications, plazaPosts }) { const publication = await ensurePagePublicationForPlaza({ userId, page, mindSpacePublications, refreshIfOnline: true, }); const { post } = await ensurePlazaPostForPublication({ userId, publication, plazaPosts, }); return { pageId: page.id, publicationId: publication.id, publicUrl: publication.publicUrl ?? publication.public_url ?? null, post: { id: post.id, status: post.status, }, }; } export async function ensurePlazaPostForPublication({ userId, publication, plazaPosts, categorySlug = 'other', }) { const post = await plazaPosts.publishPostForPublication( userId, { publication_id: publication.id, category_slug: categorySlug, cover_url: '', allow_comment: true, }, { forcePublished: true, deferPostPublishedHooks: true }, ); return { post }; } export async function quickPlazaFromChat({ user, h5Root, bundle, body, mindSpacePages, mindSpacePublications, plazaPosts, publishDir, }) { if (!mindSpacePages || !mindSpacePublications || !plazaPosts) { throw Object.assign(new Error('Plaza 或 MindSpace 未启用'), { code: 'plaza_unavailable' }); } const { analysis } = bundle; const page = await ensureChatPageForPlaza({ userId: user.id, bundle, mindSpacePages, publishDir, body, skipThumbnail: true, }); if (analysis.contentMode === 'static_html' && analysis.relativePath) { await assertWorkspacePathNotAlreadyOnPlaza({ userId: user.id, relativePath: analysis.relativePath, mindSpacePages, }); } return publishPageToPlaza({ userId: user.id, page, mindSpacePublications, plazaPosts, }); } function titleFromPublicHtml(content, relativePath) { const match = String(content ?? '').match(/