722b18326f
含 MindSpace 三列布局与统计修复、聊天加载态与连接降级、平台页脚标记与 og:site_name 微信卡片、勾选资料删除 Agent 接口及内部话术过滤。 Co-authored-by: Cursor <cursoragent@cursor.com>
423 lines
14 KiB
JavaScript
423 lines
14 KiB
JavaScript
import { buildMindSpacePageSaveInstructions } from './mindspace-page-patch.mjs';
|
||
import { buildSelectedAssetDeleteInstructions } from './mindspace-asset-agent.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,
|
||
};
|
||
}
|
||
|
||
function normalizeSelectedAsset(asset) {
|
||
if (!asset?.id) return null;
|
||
return {
|
||
id: asset.id,
|
||
displayName: asset.displayName ?? asset.filename ?? asset.id,
|
||
mimeType: asset.mimeType ?? 'application/octet-stream',
|
||
...(asset.filename ? { filename: asset.filename } : {}),
|
||
...(typeof asset.sizeBytes === 'number' ? { sizeBytes: asset.sizeBytes } : {}),
|
||
...(asset.categoryCode ? { categoryCode: asset.categoryCode } : {}),
|
||
...(asset.assetType ? { assetType: asset.assetType } : {}),
|
||
};
|
||
}
|
||
|
||
function inferAssetKindLabel(asset) {
|
||
const mime = String(asset?.mimeType ?? '').toLowerCase();
|
||
const name = String(asset?.displayName ?? asset?.filename ?? '').toLowerCase();
|
||
if (mime.startsWith('image/')) return '图片';
|
||
if (
|
||
mime.includes('spreadsheet') ||
|
||
mime.includes('csv') ||
|
||
name.endsWith('.xlsx') ||
|
||
name.endsWith('.xls') ||
|
||
name.endsWith('.csv')
|
||
) {
|
||
return '数据表';
|
||
}
|
||
if (mime.includes('pdf') || name.endsWith('.pdf')) return '报告';
|
||
if (mime.includes('word') || name.endsWith('.doc') || name.endsWith('.docx')) return '文档';
|
||
if (mime.startsWith('text/')) return '文本资料';
|
||
return '文件';
|
||
}
|
||
|
||
function buildSelectedAssetInstructions(context) {
|
||
const selected = context.selectedAssets ?? [];
|
||
if (selected.length === 0) return [];
|
||
|
||
const lines = ['【界面勾选资料(硬性)】'];
|
||
if (selected.length === 1) {
|
||
const asset = selected[0];
|
||
const kind = inferAssetKindLabel(asset);
|
||
lines.push(
|
||
`- 用户在界面勾选了 1 份${kind}:「${asset.displayName}」(id: ${asset.id},${asset.mimeType})`,
|
||
'- 用户说「这个 / 这份 / 这篇 / 这份报告 / 这个数据 / 帮我分析 / 帮我删除」等指代词时,**必须**指这份勾选资料,不得猜测其它文件。',
|
||
`- 读取/下载:GET /api/mindspace/v1/assets/${asset.id}/download`,
|
||
'- 删除:须先与用户确认,确认后由你直接执行删除(见下方「勾选资料删除」);你对该用户空间有完整读写删改权限,**禁止**推给用户自行去界面删除。',
|
||
);
|
||
if (context.h5ApiBase) {
|
||
lines.push(`- H5 API 基址:${context.h5ApiBase}`);
|
||
}
|
||
} else {
|
||
lines.push(`- 用户在界面同时勾选了 ${selected.length} 份资料:`);
|
||
for (const asset of selected) {
|
||
lines.push(` · 「${asset.displayName}」(id: ${asset.id},${asset.mimeType})`);
|
||
}
|
||
lines.push(
|
||
'- 用户说「这个 / 这份」等指代词时,**必须先追问**要处理哪一份,不得擅自选一份执行。',
|
||
'- 不要主动对多选资料发起默认操作;等用户明确指定后再执行。',
|
||
);
|
||
}
|
||
lines.push('');
|
||
return lines;
|
||
}
|
||
|
||
export function buildSelectedAssetGreeting(asset) {
|
||
const kind = inferAssetKindLabel(asset);
|
||
const name = asset.displayName ?? asset.filename ?? '这份资料';
|
||
return `我看到你在界面里勾选了「${name}」。你是想查看或处理这份${kind}吗?可以直接说「帮我分析」「总结一下」或「帮我删除」——确认后我可以直接在这份${kind}上操作(含删除)。`;
|
||
}
|
||
|
||
export function buildMindSpaceChatContext(input) {
|
||
const {
|
||
space,
|
||
ownerUsername,
|
||
selectedCategory,
|
||
selectedPageId,
|
||
pages = [],
|
||
pageLive = null,
|
||
assets = [],
|
||
selectedAssets = [],
|
||
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 normalizedSelected = selectedAssets
|
||
.map((asset) => normalizeSelectedAsset(asset))
|
||
.filter(Boolean);
|
||
const resolvedFocus =
|
||
normalizedSelected.length === 1
|
||
? normalizedSelected[0]
|
||
: normalizeSelectedAsset(focusedAsset);
|
||
|
||
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,
|
||
},
|
||
}
|
||
: {}),
|
||
...(resolvedFocus
|
||
? {
|
||
focusedAsset: resolvedFocus,
|
||
}
|
||
: {}),
|
||
...(normalizedSelected.length
|
||
? {
|
||
selectedAssets: normalizedSelected,
|
||
}
|
||
: {}),
|
||
};
|
||
|
||
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) {
|
||
const recentPages = [...pages].sort((left, right) => {
|
||
const createdDiff = (right.createdAt ?? 0) - (left.createdAt ?? 0);
|
||
if (createdDiff !== 0) return createdDiff;
|
||
return (right.updatedAt ?? 0) - (left.updatedAt ?? 0);
|
||
});
|
||
context.recentPages = recentPages.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.selectedAssets?.length === 1) {
|
||
const asset = context.selectedAssets[0];
|
||
const kind = inferAssetKindLabel(asset);
|
||
return `用户在界面勾选了${kind}「${asset.displayName}」(id: ${asset.id})。指代「这个文件」「这份报告」「这个数据」时**必须**指该勾选资料。`;
|
||
}
|
||
if (context.selectedAssets?.length > 1) {
|
||
return `用户在界面同时勾选了 ${context.selectedAssets.length} 份资料。指代「这个 / 这份」时必须先让用户说明要处理哪一份。`;
|
||
}
|
||
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})`,
|
||
);
|
||
}
|
||
|
||
lines.push(...buildSelectedAssetInstructions(context));
|
||
lines.push(...buildSelectedAssetDeleteInstructions(context));
|
||
|
||
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.selectedAssets?.length === 1) {
|
||
return `已选 · ${context.selectedAssets[0].displayName}`;
|
||
}
|
||
if (context.selectedAssets?.length > 1) {
|
||
return `已选 ${context.selectedAssets.length} 项资料`;
|
||
}
|
||
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,
|
||
inferAssetKindLabel,
|
||
normalizeSelectedAsset,
|
||
};
|