feat(mindspace): 悬浮聊天勾选资料联动、UI 与交互优化

勾选单个文件时 Agent 获知上下文并主动问候;打开悬浮窗使用新会话。
统一弹窗浅色主题、固定高度与输入区布局,支持点击外部收起。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
john
2026-06-30 02:17:36 +08:00
parent 2c91689692
commit 71e536eceb
10 changed files with 609 additions and 47 deletions
+101
View File
@@ -3,6 +3,7 @@ import test from 'node:test';
import {
buildContextPrefix,
buildMindSpaceChatContext,
buildSelectedAssetGreeting,
formatContextChip,
mindspaceChatContextInternals,
} from './mindspace-chat-context.mjs';
@@ -169,3 +170,103 @@ test('excerptContent strips html and truncates long text', () => {
`${'a'.repeat(9)}`,
);
});
test('buildMindSpaceChatContext exposes single selected asset as focused asset', () => {
const asset = {
id: 'asset-9',
displayName: 'Q1-report.pdf',
mimeType: 'application/pdf',
filename: 'Q1-report.pdf',
sizeBytes: 1200,
categoryCode: 'oa',
assetType: 'file',
};
const context = buildMindSpaceChatContext({
space: sampleSpace,
selectedCategory: sampleCategory,
selectedPageId: null,
pages: [],
assets: [asset],
selectedAssets: [asset],
route: '/space?category=oa',
});
assert.equal(context.selectedAssets?.length, 1);
assert.equal(context.focusedAsset?.id, 'asset-9');
assert.equal(formatContextChip(context), '已选 · Q1-report.pdf');
});
test('buildContextPrefix binds deictic references to a single checked asset', () => {
const asset = {
id: 'asset-9',
displayName: 'sales.xlsx',
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
filename: 'sales.xlsx',
categoryCode: 'oa',
};
const prefix = buildContextPrefix(
buildMindSpaceChatContext({
space: sampleSpace,
selectedCategory: sampleCategory,
selectedPageId: null,
pages: [],
assets: [asset],
selectedAssets: [asset],
route: '/space?category=oa',
h5ApiBase: 'http://127.0.0.1:8081',
}),
);
assert.match(prefix, /【界面勾选资料(硬性)】/);
assert.match(prefix, /sales\.xlsxid: asset-9/);
assert.match(prefix, /DELETE \/api\/mindspace\/v1\/assets\/asset-9/);
assert.match(prefix, /指代「这个文件」「这份报告」「这个数据」时\*\*必须\*\*指该勾选资料/);
});
test('buildContextPrefix requires clarification when multiple assets are checked', () => {
const assets = [
{ id: 'asset-a', displayName: 'a.pdf', mimeType: 'application/pdf' },
{ id: 'asset-b', displayName: 'b.csv', mimeType: 'text/csv' },
];
const prefix = buildContextPrefix(
buildMindSpaceChatContext({
space: sampleSpace,
selectedCategory: sampleCategory,
selectedPageId: null,
pages: [],
assets,
selectedAssets: assets,
route: '/space?category=oa',
}),
);
assert.match(prefix, /同时勾选了 2 份资料/);
assert.match(prefix, /必须先追问/);
assert.doesNotMatch(prefix, /当前聚焦资料/);
assert.equal(formatContextChip(buildMindSpaceChatContext({
space: sampleSpace,
selectedCategory: sampleCategory,
selectedPageId: null,
pages: [],
assets,
selectedAssets: assets,
route: '/space?category=oa',
})), '已选 2 项资料');
});
test('buildSelectedAssetGreeting adapts wording to asset kind', () => {
assert.match(
buildSelectedAssetGreeting({
displayName: 'sales.xlsx',
mimeType: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
}),
/这份数据表/,
);
assert.match(
buildSelectedAssetGreeting({
displayName: 'review.pdf',
mimeType: 'application/pdf',
}),
/这份报告/,
);
});