feat(mindspace): 悬浮聊天勾选资料联动、UI 与交互优化
勾选单个文件时 Agent 获知上下文并主动问候;打开悬浮窗使用新会话。 统一弹窗浅色主题、固定高度与输入区布局,支持点击外部收起。 Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
+103
-6
@@ -63,6 +63,75 @@ function resolvePageRecord(selectedPageId, pages, pageLive) {
|
||||
};
|
||||
}
|
||||
|
||||
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`,
|
||||
`- 删除:DELETE /api/mindspace/v1/assets/${asset.id}(执行删除前必须向用户确认)`,
|
||||
);
|
||||
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}吗?可以直接说「帮我分析」「总结一下」或「帮我删除」——我会针对这份资料来操作。`;
|
||||
}
|
||||
|
||||
export function buildMindSpaceChatContext(input) {
|
||||
const {
|
||||
space,
|
||||
@@ -72,6 +141,7 @@ export function buildMindSpaceChatContext(input) {
|
||||
pages = [],
|
||||
pageLive = null,
|
||||
assets = [],
|
||||
selectedAssets = [],
|
||||
focusedAsset = null,
|
||||
route,
|
||||
agentSessionId = null,
|
||||
@@ -87,6 +157,14 @@ export function buildMindSpaceChatContext(input) {
|
||||
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,
|
||||
@@ -135,13 +213,14 @@ export function buildMindSpaceChatContext(input) {
|
||||
},
|
||||
}
|
||||
: {}),
|
||||
...(focusedAsset
|
||||
...(resolvedFocus
|
||||
? {
|
||||
focusedAsset: {
|
||||
id: focusedAsset.id,
|
||||
displayName: focusedAsset.displayName,
|
||||
mimeType: focusedAsset.mimeType,
|
||||
},
|
||||
focusedAsset: resolvedFocus,
|
||||
}
|
||||
: {}),
|
||||
...(normalizedSelected.length
|
||||
? {
|
||||
selectedAssets: normalizedSelected,
|
||||
}
|
||||
: {}),
|
||||
};
|
||||
@@ -175,6 +254,14 @@ 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})。指代「这个文件」「这份资料」时默认指该资产。`;
|
||||
}
|
||||
@@ -274,6 +361,8 @@ export function buildContextPrefix(context) {
|
||||
);
|
||||
}
|
||||
|
||||
lines.push(...buildSelectedAssetInstructions(context));
|
||||
|
||||
if (context.homeCategories?.length) {
|
||||
lines.push('- 空间分类概览:');
|
||||
for (const item of context.homeCategories) {
|
||||
@@ -304,6 +393,12 @@ export function formatContextChip(context) {
|
||||
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}` : '';
|
||||
@@ -320,4 +415,6 @@ export function formatContextChip(context) {
|
||||
export const mindspaceChatContextInternals = {
|
||||
stripHtml,
|
||||
excerptContent,
|
||||
inferAssetKindLabel,
|
||||
normalizeSelectedAsset,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user