6ee6fd64dd
Introduce page edit sessions with draft preview and patch API, chat skill picker, user memory profile, h5ApiBase resolution, voice WAV transport, and scripts for 105/g2 deployment and Plaza local dev. Co-authored-by: Cursor <cursoragent@cursor.com>
319 lines
9.9 KiB
JavaScript
319 lines
9.9 KiB
JavaScript
import { buildMindSpacePageSaveInstructions } from './mindspace-page-patch.mjs';
|
||
|
||
const VIEW_LABELS = {
|
||
home: '空间首页',
|
||
category: '分类列表',
|
||
page: '页面详情',
|
||
};
|
||
|
||
const STATUS_LABELS = {
|
||
draft: '草稿',
|
||
reviewing: '待检查',
|
||
risk_found: '发现风险',
|
||
ready: '可发布',
|
||
published: '已公开',
|
||
protected: '受保护',
|
||
expired: '已过期',
|
||
offline: '已下线',
|
||
};
|
||
|
||
const TEMPLATE_LABELS = {
|
||
editorial: '编辑长文',
|
||
report: '分析报告',
|
||
'knowledge-card': '知识卡片',
|
||
profile: '个人介绍',
|
||
'static-html': '静态 HTML',
|
||
};
|
||
|
||
const CONTENT_EXCERPT_LIMIT = 1800;
|
||
const ASSET_PREVIEW_LIMIT = 10;
|
||
|
||
function stripHtml(value) {
|
||
return String(value ?? '')
|
||
.replace(/<script[\s\S]*?<\/script>/gi, ' ')
|
||
.replace(/<style[\s\S]*?<\/style>/gi, ' ')
|
||
.replace(/<[^>]+>/g, ' ')
|
||
.replace(/\s+/g, ' ')
|
||
.trim();
|
||
}
|
||
|
||
function excerptContent(content, contentFormat, max = CONTENT_EXCERPT_LIMIT) {
|
||
const raw = String(content ?? '').trim();
|
||
if (!raw) return '';
|
||
const text = contentFormat === 'html' ? stripHtml(raw) : raw;
|
||
if (text.length <= max) return text;
|
||
return `${text.slice(0, max - 1)}…`;
|
||
}
|
||
|
||
function resolveCategory(space, selectedCategory, page) {
|
||
if (selectedCategory) return selectedCategory;
|
||
if (!page?.categoryCode) return null;
|
||
return space.categories?.find((item) => item.code === page.categoryCode) ?? null;
|
||
}
|
||
|
||
function resolvePageRecord(selectedPageId, pages, pageLive) {
|
||
const listed = selectedPageId ? pages.find((item) => item.id === selectedPageId) : undefined;
|
||
if (!pageLive) return listed;
|
||
return {
|
||
...(listed ?? {}),
|
||
...pageLive.page,
|
||
title: pageLive.title,
|
||
summary: pageLive.summary,
|
||
content: pageLive.content,
|
||
};
|
||
}
|
||
|
||
export function buildMindSpaceChatContext(input) {
|
||
const {
|
||
space,
|
||
ownerUsername,
|
||
selectedCategory,
|
||
selectedPageId,
|
||
pages = [],
|
||
pageLive = null,
|
||
assets = [],
|
||
focusedAsset = null,
|
||
route,
|
||
agentSessionId = null,
|
||
h5ApiBase = null,
|
||
pageEditMode = false,
|
||
parentAgentSessionId = null,
|
||
} = input;
|
||
|
||
const pageRecord = resolvePageRecord(selectedPageId, pages, pageLive);
|
||
const category = resolveCategory(space, selectedCategory, pageRecord);
|
||
|
||
let view = 'home';
|
||
if (pageRecord && selectedPageId) view = 'page';
|
||
else if (selectedCategory) view = 'category';
|
||
|
||
const context = {
|
||
spaceId: space.id,
|
||
spaceName: space.name,
|
||
view,
|
||
route,
|
||
...(ownerUsername ? { ownerUsername } : {}),
|
||
...(agentSessionId ? { agentSessionId } : {}),
|
||
...(h5ApiBase ? { h5ApiBase } : {}),
|
||
...(pageEditMode ? { pageEditMode: true } : {}),
|
||
...(parentAgentSessionId ? { parentAgentSessionId } : {}),
|
||
...(category
|
||
? {
|
||
category: {
|
||
code: category.code,
|
||
name: category.name,
|
||
itemCount: category.itemCount,
|
||
...(view === 'category' && assets.length
|
||
? {
|
||
assets: assets.slice(0, ASSET_PREVIEW_LIMIT).map((asset) => ({
|
||
id: asset.id,
|
||
displayName: asset.displayName,
|
||
mimeType: asset.mimeType,
|
||
})),
|
||
}
|
||
: {}),
|
||
},
|
||
}
|
||
: {}),
|
||
...(pageRecord && selectedPageId
|
||
? {
|
||
page: {
|
||
id: pageRecord.id,
|
||
title: pageLive?.title ?? pageRecord.title,
|
||
summary: pageLive?.summary ?? pageRecord.summary ?? '',
|
||
status: pageRecord.status,
|
||
versionNo: pageRecord.versionNo,
|
||
categoryCode: pageRecord.categoryCode,
|
||
templateId: pageRecord.templateId,
|
||
contentFormat: pageRecord.contentFormat ?? 'markdown',
|
||
pageType: pageRecord.pageType,
|
||
contentExcerpt: excerptContent(
|
||
pageLive?.content ?? pageRecord.content ?? '',
|
||
pageRecord.contentFormat ?? 'markdown',
|
||
),
|
||
publicationUrl: pageRecord.publication?.publicUrl ?? null,
|
||
},
|
||
}
|
||
: {}),
|
||
...(focusedAsset
|
||
? {
|
||
focusedAsset: {
|
||
id: focusedAsset.id,
|
||
displayName: focusedAsset.displayName,
|
||
mimeType: focusedAsset.mimeType,
|
||
},
|
||
}
|
||
: {}),
|
||
};
|
||
|
||
if (view === 'home' && space.categories?.length) {
|
||
context.homeCategories = space.categories.map((item) => ({
|
||
code: item.code,
|
||
name: item.name,
|
||
itemCount: item.itemCount,
|
||
}));
|
||
}
|
||
|
||
if (view === 'home' && pages.length) {
|
||
context.recentPages = pages.slice(0, 8).map((item) => ({
|
||
id: item.id,
|
||
title: item.title,
|
||
status: item.status,
|
||
categoryCode: item.categoryCode,
|
||
}));
|
||
}
|
||
|
||
return context;
|
||
}
|
||
|
||
function buildLocationHint(context) {
|
||
if (context.page) {
|
||
return `用户正在查看/编辑页面「${context.page.title}」(id: ${context.page.id})。指代「这个页面」「改这里」时默认指该页面。`;
|
||
}
|
||
if (context.focusedAsset) {
|
||
return `用户正在处理资料「${context.focusedAsset.displayName}」(id: ${context.focusedAsset.id})。指代「这个文件」「这份资料」时默认指该资产。`;
|
||
}
|
||
if (context.category) {
|
||
return `用户正在「${context.category.name}」分类中浏览。指代「这里的文件」时默认指该分类下的资料。`;
|
||
}
|
||
return '用户在 MindSpace 空间首页。指代「我的空间」时指整个个人空间。';
|
||
}
|
||
|
||
export function buildPageEditSubAgentInstructions(context) {
|
||
const pageTitle = context.page?.title ?? '当前页面';
|
||
const lines = [
|
||
'【页面编辑子 Agent(硬性)】',
|
||
`- 你是专注修改页面「${pageTitle}」的子 Agent,用户正在全屏预览中编辑。`,
|
||
'- 只做页面内容/标题/样式修改,不要跑题,不要 delegate / load_skill / 改其它文件。',
|
||
'- 每次完成一处修改后必须让预览立刻更新(见下方页面实时修改说明)。',
|
||
];
|
||
if (context.parentAgentSessionId) {
|
||
lines.push(`- 父对话 session:${context.parentAgentSessionId}(退出预览后摘要会合并回父对话记忆)。`);
|
||
}
|
||
lines.push('');
|
||
return lines.join('\n');
|
||
}
|
||
|
||
export function buildContextPrefix(context) {
|
||
const lines = [];
|
||
if (context.pageEditMode) {
|
||
lines.push(...buildPageEditSubAgentInstructions(context).split('\n'));
|
||
}
|
||
lines.push(
|
||
'[MindSpace 上下文]',
|
||
`- 空间:${context.spaceName}(id: ${context.spaceId})`,
|
||
);
|
||
|
||
if (context.ownerUsername) {
|
||
lines.push(`- 账号:${context.ownerUsername}`);
|
||
}
|
||
|
||
lines.push(`- 当前视图:${VIEW_LABELS[context.view]}`);
|
||
|
||
if (context.category) {
|
||
const count =
|
||
typeof context.category.itemCount === 'number'
|
||
? `,共 ${context.category.itemCount} 项`
|
||
: '';
|
||
lines.push(`- 分类:${context.category.name}(${context.category.code}${count})`);
|
||
if (context.category.assets?.length) {
|
||
lines.push('- 当前分类资料(节选):');
|
||
for (const asset of context.category.assets) {
|
||
lines.push(` · ${asset.displayName}(id: ${asset.id},${asset.mimeType})`);
|
||
}
|
||
}
|
||
}
|
||
|
||
if (context.page) {
|
||
lines.push(`- 页面:${context.page.title}(id: ${context.page.id})`);
|
||
if (context.page.categoryCode) {
|
||
lines.push(`- 页面所属分类:${context.page.categoryCode}`);
|
||
}
|
||
if (context.page.status) {
|
||
lines.push(
|
||
`- 页面状态:${STATUS_LABELS[context.page.status] ?? context.page.status}`,
|
||
);
|
||
}
|
||
if (typeof context.page.versionNo === 'number') {
|
||
lines.push(`- 页面版本:v${context.page.versionNo}`);
|
||
}
|
||
if (context.page.templateId) {
|
||
lines.push(
|
||
`- 页面模板:${TEMPLATE_LABELS[context.page.templateId] ?? context.page.templateId}`,
|
||
);
|
||
}
|
||
if (context.page.contentFormat) {
|
||
lines.push(`- 内容格式:${context.page.contentFormat}`);
|
||
}
|
||
if (context.page.summary?.trim()) {
|
||
lines.push(`- 页面摘要:${context.page.summary.trim()}`);
|
||
}
|
||
if (context.page.publicationUrl) {
|
||
lines.push(`- 公开地址:${context.page.publicationUrl}`);
|
||
}
|
||
if (context.page.contentExcerpt) {
|
||
lines.push('- 当前正文摘录(供直接修改参考):');
|
||
lines.push('"""');
|
||
lines.push(context.page.contentExcerpt);
|
||
lines.push('"""');
|
||
}
|
||
lines.push(...buildMindSpacePageSaveInstructions(context.page, {
|
||
sessionId: context.agentSessionId,
|
||
h5ApiBase: context.h5ApiBase,
|
||
}));
|
||
}
|
||
|
||
if (context.focusedAsset) {
|
||
lines.push(
|
||
`- 当前聚焦资料:${context.focusedAsset.displayName}(${context.focusedAsset.mimeType},id: ${context.focusedAsset.id})`,
|
||
);
|
||
}
|
||
|
||
if (context.homeCategories?.length) {
|
||
lines.push('- 空间分类概览:');
|
||
for (const item of context.homeCategories) {
|
||
lines.push(` · ${item.name}(${item.code},${item.itemCount ?? 0} 项)`);
|
||
}
|
||
}
|
||
|
||
if (context.recentPages?.length) {
|
||
lines.push('- 最近页面(节选):');
|
||
for (const item of context.recentPages) {
|
||
lines.push(` · ${item.title}(id: ${item.id},${item.categoryCode},${item.status})`);
|
||
}
|
||
}
|
||
|
||
lines.push(`- 路径:${context.route}`);
|
||
lines.push('');
|
||
lines.push('【定位说明】');
|
||
lines.push(buildLocationHint(context));
|
||
lines.push('');
|
||
lines.push('---');
|
||
lines.push('');
|
||
return `${lines.join('\n')}\n`;
|
||
}
|
||
|
||
export function formatContextChip(context) {
|
||
if (context.pageEditMode && context.page) {
|
||
const version =
|
||
typeof context.page.versionNo === 'number' ? ` · v${context.page.versionNo}` : '';
|
||
return `编辑 · ${context.page.title}${version}`;
|
||
}
|
||
if (context.page) {
|
||
const version =
|
||
typeof context.page.versionNo === 'number' ? ` · v${context.page.versionNo}` : '';
|
||
return `${context.page.title}${version}`;
|
||
}
|
||
if (context.category) {
|
||
const count =
|
||
typeof context.category.itemCount === 'number' ? ` · ${context.category.itemCount} 项` : '';
|
||
return `${context.category.name}${count}`;
|
||
}
|
||
return context.spaceName;
|
||
}
|
||
|
||
export const mindspaceChatContextInternals = {
|
||
stripHtml,
|
||
excerptContent,
|
||
};
|