feat: release chat and mindspace UI updates

This commit is contained in:
john
2026-06-27 21:57:21 +08:00
parent c924b2bb77
commit 8264e71f9e
24 changed files with 2042 additions and 209 deletions
+63 -7
View File
@@ -1,6 +1,7 @@
import express from 'express';
import crypto from 'node:crypto';
import fs from 'node:fs';
import fsPromises from 'node:fs/promises';
import { createProxyMiddleware } from 'http-proxy-middleware';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -23,7 +24,7 @@ import {
} from './user-auth.mjs';
import { createWikiAuth } from './wiki-auth.mjs';
import { isLocalDevHostname } from './scripts/local-test-config.mjs';
import { PUBLISH_ROOT_DIR, PUBLISH_KEY_UUID, PUBLIC_ZONE_DIR, resolvePublishDir } from './user-publish.mjs';
import { PUBLISH_ROOT_DIR, PUBLISH_KEY_UUID, PUBLIC_ZONE_DIR, resolvePublishDir, resolvePublicBaseUrl } from './user-publish.mjs';
import { ensureWorkspaceHtmlThumbnail, startWorkspaceThumbnailWatcher, workspaceThumbnailRelativePath } from './mindspace-workspace-thumbnails.mjs';
import { startWorkspaceAssetSyncWatcher } from './mindspace-workspace-sync.mjs';
import { attachRequestId, sendData, sendError } from './api-response.mjs';
@@ -32,7 +33,7 @@ import { assertMindSpaceRoute, mindspaceFlags } from './mindspace-flags.mjs';
import { createMindSpaceService, DEFAULT_MAX_FILE_BYTES } from './mindspace.mjs';
import { ensureMindSpaceConfig } from './mindspace-config.mjs';
import { createAssetService } from './mindspace-assets.mjs';
import { createPageService, pageInternals } from './mindspace-pages.mjs';
import { createPageService, pageInternals, inlinePrivateAssetsInHtml } from './mindspace-pages.mjs';
import { createPageLiveEditService } from './mindspace-page-live-edit.mjs';
import { createPageEditSessionService } from './mindspace-page-edit-session.mjs';
import { suggestCoverMetaWithAi } from './mindspace-cover-ai.mjs';
@@ -520,6 +521,7 @@ async function bootstrapUserAuth() {
userAuth,
llmProviderService,
subscriptionService,
sessionSnapshotService,
localFetchAsset: mindSpaceAssets
? async (userId, assetId) => {
const { asset, path: assetPath } = await mindSpaceAssets.readAsset(userId, assetId);
@@ -2506,6 +2508,13 @@ api.post('/mindspace/v1/pages/analyze-chat-save', async (req, res) => {
thumbnailReady = false;
}
}
const sessionId = String(req.body?.session_id ?? req.body?.sessionId ?? '').trim();
const messageId = String(req.body?.message_id ?? req.body?.messageId ?? '').trim();
const existingPage = await mindSpacePages.findPageBySourceMessage(
req.currentUser.id,
sessionId,
messageId,
).catch(() => null);
return sendData(res, req, {
contentMode: analysis.contentMode,
links: analysis.links,
@@ -2524,12 +2533,51 @@ api.post('/mindspace/v1/pages/analyze-chat-save', async (req, res) => {
filename: resolvedHtml?.filename ?? analysis.filename,
hasHtmlContent: Boolean(resolvedHtml),
privacyScan: scanContent(resolvedHtml?.content ?? source.content),
existingPage: existingPage
? { id: existingPage.id, title: existingPage.title, categoryCode: existingPage.categoryCode }
: null,
});
} catch (error) {
return mindSpaceError(res, req, error);
}
});
api.post('/mindspace/v1/pages/quick-share-from-chat', async (req, res) => {
if (!mindSpacePages) return res.status(503).json({ message: 'MindSpace 未启用' });
try {
const bundle = await resolveChatSaveBundle(req.currentUser, __dirname, req.body);
if (!bundle.resolvedHtml) {
throw Object.assign(new Error('无法读取链接页面内容'), { code: 'static_page_not_found' });
}
const storageRoot =
process.env.MINDSPACE_STORAGE_ROOT ?? path.join(__dirname, 'data', 'mindspace');
const { html: localizedHtml } = await inlinePrivateAssetsInHtml(
authPool,
storageRoot,
req.currentUser.id,
bundle.resolvedHtml.content,
);
const publishDir = resolvePublishDir(__dirname, req.currentUser);
const sharedDir = path.join(publishDir, PUBLIC_ZONE_DIR, 'shared');
await fsPromises.mkdir(sharedDir, { recursive: true });
const basename = bundle.resolvedHtml.filename.replace(/\.html$/i, '');
const filename = `${basename}-${crypto.randomUUID().slice(0, 8)}.html`;
const destPath = path.join(sharedDir, filename);
await fsPromises.writeFile(destPath, localizedHtml, 'utf8');
const publishKey = req.currentUser.id;
const publicUrl = `${resolvePublicBaseUrl()}/${PUBLISH_ROOT_DIR}/${encodeURIComponent(publishKey)}/${PUBLIC_ZONE_DIR}/shared/${encodeURIComponent(filename)}`;
return res.status(201).json({ data: { publicUrl, filename } });
} catch (error) {
console.error('[quick-share] error:', error);
return mindSpaceError(res, req, error);
}
});
api.post('/mindspace/v1/pages/save-from-chat', async (req, res) => {
if (!mindSpacePages || !mindSpaceAssets) {
return res.status(503).json({ message: 'MindSpace 未启用' });
@@ -2635,11 +2683,19 @@ api.post('/mindspace/v1/pages/save-from-chat', async (req, res) => {
}).catch(() => {});
}
const page = await mindSpacePages.createFromChat(req.currentUser.id, pageInput, {
sessionId: req.body.session_id,
messageId: req.body.message_id,
snapshot,
});
const replacePageId = req.body?.replace_page_id
? String(req.body.replace_page_id).trim()
: null;
let page;
if (replacePageId) {
page = await mindSpacePages.updatePage(req.currentUser.id, replacePageId, pageInput);
} else {
page = await mindSpacePages.createFromChat(req.currentUser.id, pageInput, {
sessionId: req.body.session_id,
messageId: req.body.message_id,
snapshot,
});
}
return res.status(201).json({ data: { kind: 'page', categoryCode: 'draft', page } });
} catch (error) {
return mindSpaceError(res, req, error);