Initial commit: Memind H5 portal with MindSpace, Plaza, and agent jobs.

Track application source and tests; exclude local env, user workspaces, and runtime data via .gitignore.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
John
2026-06-15 15:04:43 -07:00
commit 2e14873f2d
272 changed files with 64133 additions and 0 deletions
+160
View File
@@ -0,0 +1,160 @@
import { Agent, fetch as undiciFetch } from 'undici';
import { jsonrepair } from 'jsonrepair';
import {
decryptSecret,
normalizeApiUrl,
resolveChatCompletionsUrl,
} from './llm-providers.mjs';
import { normalizeCoverMetaSuggestion, parseMindspaceCoverMeta } from './mindspace-cover-meta.mjs';
const insecureDispatcher = new Agent({
connect: { rejectUnauthorized: false },
});
function resolveEncryptionKey(explicitKey) {
const raw =
explicitKey ??
process.env.H5_SETTINGS_ENCRYPTION_KEY ??
process.env.TKMIND_SERVER__SECRET_KEY ??
'local-dev-secret';
return raw;
}
function parseJsonObject(text) {
const source = String(text ?? '').trim();
if (!source) return null;
try {
return JSON.parse(source);
} catch {
try {
return JSON.parse(jsonrepair(source));
} catch {
return null;
}
}
}
function extractJsonObject(text) {
const direct = parseJsonObject(text);
if (direct && typeof direct === 'object') return direct;
const source = String(text ?? '').trim();
const start = source.indexOf('{');
const end = source.lastIndexOf('}');
if (start < 0 || end <= start) {
throw Object.assign(new Error('AI 未返回有效 JSON'), { code: 'cover_ai_invalid_output' });
}
const raw = source.slice(start, end + 1);
try {
return JSON.parse(jsonrepair(raw));
} catch (error) {
throw Object.assign(new Error('AI 封面 JSON 解析失败'), {
code: 'cover_ai_invalid_output',
cause: error instanceof Error ? error.message : String(error),
});
}
}
function buildCoverPrompt({ title, summary, html, instruction, currentCover }) {
const excerpt = String(html ?? '')
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
.replace(/<[^>]+>/g, ' ')
.replace(/\s+/g, ' ')
.trim()
.slice(0, 1200);
return [
'你是 MindSpace 信息流封面设计助手。请根据页面内容生成 mindspace-cover 元数据。',
'只返回一个 JSON 对象,不要 Markdown,不要解释。',
'字段要求:',
'- tag: 2-4 个汉字分类,如 旅行/美食/活动/报告',
'- emoji: 1 个与主题高度相关的 emoji',
'- accent: 主色 hex,如 #eeb04e',
'- accent2: 辅色 hex',
'- subtitle: 12-28 字副标题,用于封面底部',
'- cover: 可选,若页面已有 hero 图路径可保留,否则留空字符串',
'',
`页面标题:${title || '未命名页面'}`,
`页面摘要:${summary || '无'}`,
`当前 cover 元数据:${JSON.stringify(currentCover ?? {})}`,
`页面正文摘录:${excerpt || '无'}`,
instruction ? `用户补充要求:${instruction}` : '',
]
.filter(Boolean)
.join('\n');
}
export async function suggestCoverMetaWithAi(
pool,
{ title, summary, html, instruction, encryptionKey },
) {
const [rows] = await pool.query(
`SELECT * FROM h5_llm_provider_keys WHERE is_selected = 1 AND status = 'active' LIMIT 1`,
);
const row = rows[0];
if (!row) {
throw Object.assign(new Error('请先在管理后台配置并启用 LLM'), { code: 'llm_not_configured' });
}
const apiKey = decryptSecret(
{
ciphertext: row.api_key_ciphertext,
iv: row.api_key_iv,
tag: row.api_key_tag,
},
resolveEncryptionKey(encryptionKey),
);
const apiUrl = normalizeApiUrl(row.api_url);
const url = resolveChatCompletionsUrl(apiUrl);
if (!url) {
throw Object.assign(new Error('LLM API 地址无效'), { code: 'llm_not_configured' });
}
const prompt = buildCoverPrompt({
title,
summary,
html,
instruction,
currentCover: parseMindspaceCoverMeta(html),
});
const upstream = await undiciFetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${apiKey}`,
},
body: JSON.stringify({
model: row.default_model,
messages: [{ role: 'user', content: prompt }],
stream: false,
...(row.relay_provider ? { provider: row.relay_provider } : {}),
}),
dispatcher: url.startsWith('https://') ? insecureDispatcher : undefined,
});
const text = await upstream.text().catch(() => '');
if (!upstream.ok) {
throw Object.assign(new Error(`AI 封面生成失败:${text.slice(0, 240) || upstream.status}`), {
code: 'cover_ai_failed',
});
}
let data;
try {
data = JSON.parse(text);
} catch {
throw Object.assign(new Error('AI 响应不是 JSON'), { code: 'cover_ai_failed' });
}
const reply =
data?.choices?.[0]?.message?.content ??
data?.message?.content ??
data?.output ??
text;
const parsed = normalizeCoverMetaSuggestion(extractJsonObject(reply));
if (!parsed.tag && !parsed.emoji && !parsed.accent) {
throw Object.assign(new Error('AI 未生成有效封面参数'), { code: 'cover_ai_invalid_output' });
}
return parsed;
}