Files
memind/mindspace-chat-context.mjs
T
John 6ee6fd64dd Add MindSpace page live edit, chat skills, and H5 deploy tooling.
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>
2026-06-15 22:09:38 -07:00

319 lines
9.9 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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,
};