Add image-designer skill, align billing to DeepSeek ×1, and enrich Plaza demo.

Introduce AI image generation with chat shortcut and agent API, improve MindSpace chat-to-page save resolution, and seed Plaza covers with production deploy scripts.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
John
2026-06-16 16:29:19 -07:00
parent 03690ee354
commit 6d99d762da
155 changed files with 2748 additions and 190 deletions
+63 -11
View File
@@ -21,7 +21,7 @@ import {
userSessionCookie,
} from './user-auth.mjs';
import { createWikiAuth } from './wiki-auth.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, resolvePublicBaseUrl, resolvePublishDir } 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';
@@ -31,6 +31,7 @@ import { createMindSpaceService } from './mindspace.mjs';
import { createAssetService } from './mindspace-assets.mjs';
import { createPageService, pageInternals } from './mindspace-pages.mjs';
import { createPageLiveEditService } from './mindspace-page-live-edit.mjs';
import { createImageGenerationService } from './image-generation.mjs';
import { createPageEditSessionService } from './mindspace-page-edit-session.mjs';
import { suggestCoverMetaWithAi } from './mindspace-cover-ai.mjs';
import { createPublicationService } from './mindspace-publications.mjs';
@@ -54,7 +55,6 @@ import { createCleanupService } from './mindspace-cleanup.mjs';
import { createAgentJobService } from './mindspace-agent-jobs.mjs';
import { createMindSpaceAgentRunner } from './mindspace-agent-runner.mjs';
import {
analyzeChatMessageForSave,
buildChatSavePreviewFrameUrl,
buildChatSaveThumbnailUrl,
buildWorkspaceAssetUrl,
@@ -62,7 +62,6 @@ import {
buildWorkspaceThumbnailUrl,
injectHtmlBaseHref,
resolveChatSaveAnalysis,
resolveStaticHtmlContent,
} from './mindspace-chat-save.mjs';
import { generateHtmlThumbnail } from './mindspace-thumbnails.mjs';
import { scanContent } from './mindspace-content-scan.mjs';
@@ -101,6 +100,7 @@ const API_SECRET = process.env.TKMIND_SERVER__SECRET_KEY ?? 'local-dev-secret';
const INTERNAL_AGENT_SECRET = process.env.MINDSPACE_INTERNAL_AGENT_SECRET ?? API_SECRET;
const ACCESS_PASSWORD = process.env.H5_ACCESS_PASSWORD;
const USERS_ROOT = process.env.H5_USERS_ROOT ?? path.join(__dirname, 'users');
const PUBLIC_BASE_URL = resolvePublicBaseUrl(process.env);
const app = express();
app.set('trust proxy', 1);
@@ -176,6 +176,7 @@ let mindSpaceAssets = null;
let mindSpaceAudit = null;
let mindSpacePages = null;
let mindSpacePageLiveEdit = null;
let imageGeneration = null;
let mindSpacePageEditSession = null;
let mindSpacePublications = null;
let plazaPosts = null;
@@ -225,6 +226,18 @@ async function bootstrapUserAuth() {
return rows[0]?.user_id ?? null;
},
});
imageGeneration = createImageGenerationService({
pool,
h5Root: __dirname,
publicBaseUrl: PUBLIC_BASE_URL,
resolveUserIdForAgentSession: async (sessionId) => {
const [rows] = await pool.query(
`SELECT user_id FROM h5_user_sessions WHERE agent_session_id = ? LIMIT 1`,
[sessionId],
);
return rows[0]?.user_id ?? null;
},
});
mindSpacePublications = createPublicationService(pool, {
storageRoot:
process.env.MINDSPACE_STORAGE_ROOT ?? path.join(__dirname, 'data', 'mindspace'),
@@ -436,7 +449,16 @@ app.post('/auth/login', jsonBody, async (req, res) => {
return res.status(401).json({ message: result.message });
}
setUserLoginCookies(res, req, result.token);
return res.json({ authenticated: true, user: result.user, mode: 'user' });
const row = await userAuth.getUserById(result.user.id);
const capabilityState = row ? await userAuth.resolveUserCapabilities(row) : null;
return res.json({
authenticated: true,
user: result.user,
mode: 'user',
capabilities: capabilityState?.capabilities,
grantedSkills: capabilityState?.grantedSkills ?? [],
unrestricted: capabilityState?.unrestricted,
});
}
if (!legacyAuth) {
@@ -1289,6 +1311,7 @@ api.use(async (req, res, next) => {
if (req.path === '/status') return next();
if (req.path.startsWith('/internal/agent/')) return next();
if (req.path === '/agent/mindspace_page_patch') return next();
if (req.path === '/agent/generate_image') return next();
const plazaPublic = isPlazaPublicRead(req.path, req.method);
@@ -2068,6 +2091,7 @@ async function resolveChatSaveBundle(user, h5Root, input = {}) {
username: user.username,
h5Root,
selectedLinkIndex,
publicBaseUrl: PUBLIC_BASE_URL,
});
return {
source,
@@ -2224,12 +2248,13 @@ api.post('/mindspace/v1/pages/save-from-chat', async (req, res) => {
req.body?.session_id,
req.body?.message_id,
);
const analysis = analyzeChatMessageForSave({
const { analysis, resolvedHtml } = await resolveChatSaveAnalysis({
content: source.content,
userId: req.currentUser.id,
username: req.currentUser.username,
h5Root: __dirname,
selectedLinkIndex: Number(req.body?.selected_link_index ?? 0),
publicBaseUrl: PUBLIC_BASE_URL,
});
const snapshot = {
session_name: source.session.name,
@@ -2237,13 +2262,9 @@ api.post('/mindspace/v1/pages/save-from-chat', async (req, res) => {
role: source.message.role,
content_mode: analysis.contentMode,
public_url: analysis.previewUrl,
relative_path: analysis.relativePath,
relative_path: resolvedHtml?.relativePath ?? analysis.relativePath,
};
let resolvedHtml = null;
if (analysis.contentMode === 'static_html') {
resolvedHtml = await resolveStaticHtmlContent(analysis).catch(() => null);
}
const privacyScan = scanContent(resolvedHtml?.content ?? source.content);
assertPrivateSaveAllowed(categoryCode, privacyScan, req.body?.acknowledged_finding_ids);
@@ -2309,7 +2330,7 @@ api.post('/mindspace/v1/pages/save-from-chat', async (req, res) => {
if (analysis.contentMode === 'static_html' && resolvedHtml?.content && analysis.relativePath) {
const publishDir = resolvePublishDir(__dirname, req.currentUser);
await ensureWorkspaceHtmlThumbnail(publishDir, analysis.relativePath, resolvedHtml.content, {
void ensureWorkspaceHtmlThumbnail(publishDir, analysis.relativePath, resolvedHtml.content, {
title: pageInput.title,
subtitle: pageInput.summary,
}).catch(() => {});
@@ -2557,6 +2578,37 @@ api.post('/agent/mindspace_page_patch', async (req, res) => {
}
});
api.post('/agent/generate_image', async (req, res) => {
if (!imageGeneration) {
return res.status(503).json({ message: '图片生成未启用' });
}
try {
const result = await imageGeneration.generateForAgentSession(req.body ?? {});
return sendData(res, req, {
relative_path: result.relativePath,
public_url: result.publicUrl,
mime_type: result.mimeType,
size: result.size,
style: result.style,
quality: result.quality,
revised_prompt: result.revisedPrompt,
prompt: result.prompt,
});
} catch (error) {
const code = error?.code ?? 'internal_error';
const statusByCode = {
image_gen_unavailable: 503,
image_gen_not_configured: 503,
invalid_request: 400,
invalid_output_path: 400,
forbidden: 403,
image_gen_failed: 502,
};
const status = statusByCode[code] ?? 500;
return sendError(res, req, status, code, error?.message || '图片生成失败', error?.details);
}
});
api.get('/mindspace/v1/pages/:pageId/thumbnail', async (req, res) => {
if (!mindSpacePages) return res.status(503).json({ message: 'MindSpace 未启用' });
try {