feat(chat): add one-click Plaza publish for HTML page messages.
Speed up the quick-plaza path with session snapshot reads, publish timeouts, and a modal fix so parent re-renders no longer abort in-flight requests. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+84
-9
@@ -122,6 +122,7 @@ import {
|
||||
normalizePublicHtmlRelativePath,
|
||||
syncPublicHtmlAfterFinish,
|
||||
} from './mindspace-public-finish-sync.mjs';
|
||||
import { quickPlazaFromChat } from './mindspace-chat-plaza.mjs';
|
||||
import { extractCoverSignals, generateHtmlThumbnail } from './mindspace-thumbnails.mjs';
|
||||
import {
|
||||
extractSharePreviewMeta,
|
||||
@@ -2908,7 +2909,8 @@ function messageText(message) {
|
||||
.trim();
|
||||
}
|
||||
|
||||
async function resolveOwnedAssistantMessage(userId, sessionId, messageId) {
|
||||
async function resolveOwnedAssistantMessage(user, sessionId, messageId) {
|
||||
const userId = user?.id;
|
||||
if (!sessionId || !messageId) {
|
||||
throw Object.assign(new Error('缺少来源会话或消息'), {
|
||||
code: 'invalid_page_input',
|
||||
@@ -2917,13 +2919,52 @@ async function resolveOwnedAssistantMessage(userId, sessionId, messageId) {
|
||||
if (!(await userAuth.ownsSession(userId, sessionId))) {
|
||||
throw Object.assign(new Error('来源会话不存在'), { code: 'source_message_not_found' });
|
||||
}
|
||||
const upstream = await tkmindProxy.apiFetch(`/sessions/${encodeURIComponent(sessionId)}`, {
|
||||
method: 'GET',
|
||||
});
|
||||
if (!upstream.ok) {
|
||||
throw Object.assign(new Error('无法读取来源会话'), { code: 'source_message_not_found' });
|
||||
|
||||
let session = null;
|
||||
if (sessionSnapshotService?.isEnabled()) {
|
||||
const snapshot = await sessionSnapshotService.get(sessionId).catch(() => null);
|
||||
if (snapshot?.messages?.length) {
|
||||
session = {
|
||||
...snapshot.session,
|
||||
conversation: sanitizeSessionConversationPublicHtmlLinks(snapshot.messages, user),
|
||||
};
|
||||
if (authPool) {
|
||||
session = await repairSessionConversationFromDb(authPool, session, sessionId, userId);
|
||||
}
|
||||
}
|
||||
}
|
||||
const session = await upstream.json();
|
||||
|
||||
if (!session) {
|
||||
const target = await tkmindProxy.resolveTarget(sessionId);
|
||||
let upstream;
|
||||
try {
|
||||
upstream = await tkmindProxy.apiFetchTo(
|
||||
target,
|
||||
`/sessions/${encodeURIComponent(sessionId)}`,
|
||||
{ method: 'GET', signal: AbortSignal.timeout(15_000) },
|
||||
);
|
||||
} catch (error) {
|
||||
throw Object.assign(
|
||||
new Error(
|
||||
error?.name === 'TimeoutError' || error?.name === 'AbortError'
|
||||
? '读取来源会话超时,请稍后重试'
|
||||
: '无法读取来源会话',
|
||||
),
|
||||
{ code: 'source_message_not_found' },
|
||||
);
|
||||
}
|
||||
if (!upstream.ok) {
|
||||
throw Object.assign(new Error('无法读取来源会话'), { code: 'source_message_not_found' });
|
||||
}
|
||||
session = await upstream.json();
|
||||
if (Array.isArray(session.conversation)) {
|
||||
session.conversation = sanitizeSessionConversationPublicHtmlLinks(session.conversation, user);
|
||||
}
|
||||
if (authPool) {
|
||||
session = await repairSessionConversationFromDb(authPool, session, sessionId, userId);
|
||||
}
|
||||
}
|
||||
|
||||
const message = (session.conversation ?? []).find((item) => item.id === messageId);
|
||||
if (!message) {
|
||||
throw Object.assign(new Error('来源消息不存在'), { code: 'source_message_not_found' });
|
||||
@@ -2963,7 +3004,7 @@ async function resolveChatSaveBundle(user, h5Root, input = {}) {
|
||||
const previewTitle = String(input.previewTitle ?? input.preview_title ?? '').trim();
|
||||
const previewSummary = String(input.previewSummary ?? input.preview_summary ?? '').trim();
|
||||
|
||||
const source = await resolveOwnedAssistantMessage(user.id, sessionId, messageId);
|
||||
const source = await resolveOwnedAssistantMessage(user, sessionId, messageId);
|
||||
let { analysis, resolvedHtml } = await resolveChatSaveAnalysis({
|
||||
content: source.content,
|
||||
userId: user.id,
|
||||
@@ -3294,6 +3335,40 @@ api.post('/mindspace/v1/pages/quick-share-from-chat', async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
api.post('/mindspace/v1/pages/quick-plaza-from-chat', async (req, res) => {
|
||||
if (!mindSpacePages || !mindSpacePublications || !plazaPosts) {
|
||||
return sendError(res, req, 503, 'plaza_unavailable', 'Plaza 或 MindSpace 未启用');
|
||||
}
|
||||
const startedAt = Date.now();
|
||||
try {
|
||||
const bundle = await resolveChatSaveBundle(req.currentUser, __dirname, req.body);
|
||||
const publishDir = resolveMindSpaceUserPublishDir(__dirname, req.currentUser);
|
||||
const result = await quickPlazaFromChat({
|
||||
user: req.currentUser,
|
||||
h5Root: __dirname,
|
||||
bundle,
|
||||
body: req.body ?? {},
|
||||
mindSpacePages,
|
||||
mindSpacePublications,
|
||||
plazaPosts,
|
||||
publishDir,
|
||||
});
|
||||
console.info('[quick-plaza] ok', {
|
||||
ms: Date.now() - startedAt,
|
||||
pageId: result.pageId,
|
||||
publicationId: result.publicationId,
|
||||
postId: result.post?.id,
|
||||
});
|
||||
return sendData(res, req, result, 201);
|
||||
} catch (error) {
|
||||
console.error('[quick-plaza] error:', { ms: Date.now() - startedAt, error });
|
||||
if (error?.code && mapPlazaError(error) !== 500) {
|
||||
return plazaRouteError(res, req, error);
|
||||
}
|
||||
return mindSpaceError(res, req, error);
|
||||
}
|
||||
});
|
||||
|
||||
async function handleChatSaveDocx(req, res) {
|
||||
if (!mindSpacePages) return res.status(503).json({ message: 'MindSpace 未启用' });
|
||||
try {
|
||||
@@ -3348,7 +3423,7 @@ api.post('/mindspace/v1/pages/save-from-chat', async (req, res) => {
|
||||
throw Object.assign(new Error('无效的保存目标'), { code: 'invalid_category_code' });
|
||||
}
|
||||
const source = await resolveOwnedAssistantMessage(
|
||||
req.currentUser.id,
|
||||
req.currentUser,
|
||||
req.body?.session_id,
|
||||
req.body?.message_id,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user