2026-06-15 22:09:38 -07:00
|
|
|
|
import { buildMindSpacePageSaveInstructions } from './mindspace-page-patch.mjs';
|
|
|
|
|
|
|
2026-06-15 15:04:43 -07:00
|
|
|
|
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,
|
2026-06-15 22:09:38 -07:00
|
|
|
|
agentSessionId = null,
|
|
|
|
|
|
h5ApiBase = null,
|
|
|
|
|
|
pageEditMode = false,
|
|
|
|
|
|
parentAgentSessionId = null,
|
2026-06-15 15:04:43 -07:00
|
|
|
|
} = 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 } : {}),
|
2026-06-15 22:09:38 -07:00
|
|
|
|
...(agentSessionId ? { agentSessionId } : {}),
|
|
|
|
|
|
...(h5ApiBase ? { h5ApiBase } : {}),
|
|
|
|
|
|
...(pageEditMode ? { pageEditMode: true } : {}),
|
|
|
|
|
|
...(parentAgentSessionId ? { parentAgentSessionId } : {}),
|
2026-06-15 15:04:43 -07:00
|
|
|
|
...(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 空间首页。指代「我的空间」时指整个个人空间。';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-06-15 22:09:38 -07:00
|
|
|
|
export function buildPageEditSubAgentInstructions(context) {
|
|
|
|
|
|
const pageTitle = context.page?.title ?? '当前页面';
|
2026-06-15 15:04:43 -07:00
|
|
|
|
const lines = [
|
2026-06-15 22:09:38 -07:00
|
|
|
|
'【页面编辑子 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(
|
2026-06-15 15:04:43 -07:00
|
|
|
|
'[MindSpace 上下文]',
|
|
|
|
|
|
`- 空间:${context.spaceName}(id: ${context.spaceId})`,
|
2026-06-15 22:09:38 -07:00
|
|
|
|
);
|
2026-06-15 15:04:43 -07:00
|
|
|
|
|
|
|
|
|
|
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('"""');
|
|
|
|
|
|
}
|
2026-06-15 22:09:38 -07:00
|
|
|
|
lines.push(...buildMindSpacePageSaveInstructions(context.page, {
|
|
|
|
|
|
sessionId: context.agentSessionId,
|
|
|
|
|
|
h5ApiBase: context.h5ApiBase,
|
|
|
|
|
|
}));
|
2026-06-15 15:04:43 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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) {
|
2026-06-15 22:09:38 -07:00
|
|
|
|
if (context.pageEditMode && context.page) {
|
|
|
|
|
|
const version =
|
|
|
|
|
|
typeof context.page.versionNo === 'number' ? ` · v${context.page.versionNo}` : '';
|
|
|
|
|
|
return `编辑 · ${context.page.title}${version}`;
|
|
|
|
|
|
}
|
2026-06-15 15:04:43 -07:00
|
|
|
|
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,
|
|
|
|
|
|
};
|