import { buildMindSpacePageSaveInstructions } from './mindspace-page-patch.mjs'; import { buildSelectedAssetDeleteInstructions, buildSelectedAssetReadInstructions, } from './mindspace-asset-agent-instructions.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(//gi, ' ') .replace(//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 normalizeWorkspaceRelativePath(value) { const trimmed = String(value ?? '').trim().replace(/^\.?\//, ''); if (!trimmed || trimmed.includes('..')) return null; return trimmed; } function normalizeSelectedAsset(asset) { if (!asset?.id) return null; const workspaceRelativePath = normalizeWorkspaceRelativePath( asset.workspaceRelativePath ?? asset.workspace_relative_path, ); 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 } : {}), ...(workspaceRelativePath ? { workspaceRelativePath } : {}), }; } function resolveCategoryItemCount(category, view, assets) { const listed = typeof category?.itemCount === 'number' ? category.itemCount : 0; if (view !== 'category' || !Array.isArray(assets) || assets.length === 0) { return category?.itemCount; } return Math.max(listed, assets.length); } 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); const workspacePath = normalizeWorkspaceRelativePath( asset.workspaceRelativePath ?? asset.workspace_relative_path, ); lines.push( `- 用户在界面勾选了 1 份${kind}:「${asset.displayName}」(id: ${asset.id},${asset.mimeType})`, '- 用户说「这个 / 这份 / 这篇 / 这份报告 / 这个数据 / 帮我分析 / 帮我删除」等指代词时,**必须**指这份勾选资料,不得猜测其它文件。', workspacePath ? `- 读取/分析:文件已落盘 \`${workspacePath}\`,直接 read/脚本读取;见下方「勾选资料读取」。` : '- 读取/分析:优先 `list_dir oa` / `read_file oa/文件名`;若工作区尚无副本,见下方「勾选资料读取」用 Agent 代理下载落盘。', '- 删除:须先与用户确认,确认后由你直接执行删除(见下方「勾选资料删除」);你对该用户空间有完整读写删改权限,**禁止**推给用户自行去界面删除。', ); 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: resolveCategoryItemCount(category, view, assets), ...(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.workspacePublicUrl) { lines.push(`- 公开地址(MindSpace):${context.page.workspacePublicUrl}`); } else 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(...buildSelectedAssetReadInstructions(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, };