Skip to content

Commit 67deb8a

Browse files
committed
feat(agent-workspace): expose scope and focus hit evidence
1 parent 3067cd8 commit 67deb8a

9 files changed

Lines changed: 655 additions & 77 deletions

File tree

docs/diataxis/en/explanation/development-progress-dashboard.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,32 @@
33
This page is the implementation-facing dashboard for the Knowledge Mastery evolution plan.
44
It tracks what is already implemented, where the hard gaps remain, and how to verify progress from code and runtime behavior.
55

6+
## 2026-06-06 Knowledge Workspace Scope Switcher and Evidence-Focused Hit UI
7+
8+
This slice closes the frontend usability gap that made the active RAG scope hard to see from inside the Knowledge Workspace itself.
9+
The prior implementation depended on the global folder selector and request payload state, so users could ask a scoped question without a local, task-adjacent confirmation of the actual retrieval boundary.
10+
It also rendered summaries, citations, matched sections, and actions in every hit card, which pushed the question/reply area out of view after results returned and increased first-level choice density.
11+
12+
Code-vs-plan reconciliation for this slice:
13+
14+
| Requirement | Current implementation evidence | Progress call |
15+
|---|---|---|
16+
| Show and switch the Knowledge Workspace scope in the workspace window | `src/frontend/index.html` now includes an in-pane `agent-workspace-scope-select`; `src/frontend/agent_workspace.js` mirrors the global `folder-select`, publishes the same active-target event, updates `localStorage.nc_last_target`, and sends `activeTarget` plus `scope` with conversation requests. | Implemented |
17+
| Keep the question/reply area visible after hits return | `src/frontend/styles.css` gives chat messages a stable flex floor and caps the knowledge-hit list height, so the conversation stream and input remain part of the active workspace instead of being displaced by result cards. | Implemented |
18+
| Show only interactive knowledge-point filenames at the first level | `src/frontend/workspace_panes.js` now renders each knowledge point as a file button resolved from `sourcePath`, `citation.sourcePath`, or `matchedSpans[0].sourcePath`; summaries, scores, citations, and matched section snippets are no longer first-level card content. | Implemented |
19+
| Move secondary actions behind a long-press/context menu | Typed capability buttons remain backward-compatible, but are hidden in `agent-knowledge-actions-menu` until long-press, context menu, or keyboard context-menu activation opens them. | Implemented |
20+
| Highlight matched passages in the right pane | Clicking the filename opens the graph-focus pane with the knowledge point title and `matchedSpans`, rendering each matched passage as highlighted evidence. | Implemented |
21+
| Bilingual locale and contract compatibility | `agentWorkspace.scope.*` and new knowledge action labels are present in both frontend locale bundles, and the locale contract test verifies key coverage and placeholder parity. | Preserved |
22+
23+
Verification for this slice:
24+
25+
- `npm.cmd exec -- jest src/agent_workspace.frontend.test.ts -t "scope selector|knowledge hits as file entries" --runInBand`
26+
- `npm.cmd exec -- jest src/agent_workspace.frontend.test.ts --runInBand`
27+
- `npm.cmd exec -- jest src/agent_workspace.locale.contract.test.ts --runInBand`
28+
- `node --check src/frontend/agent_workspace.js`
29+
- `node --check src/frontend/workspace_panes.js`
30+
- `npm.cmd run build`
31+
632
## 2026-06-06 Active-Scope Miss Recovery and Document-Augmented RAG Patch
733

834
This patch resolves the live "what is water glass?" failure that reproduced while the WebView was already running on `npm run tauri:dev:mini:gpu`.

docs/diataxis/zh/explanation/development-progress-dashboard.md

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,31 @@
33
本页是“知识彻底掌握演进方案”的实现侧进度看板。
44
它用于回答三件事:哪些能力已落地、哪些关键缺口仍在、如何用代码与运行时证据验证推进结果。
55

6+
## 2026-06-06 知识工作区 scope 切换器与证据聚焦命中 UI
7+
8+
本切片关闭的是知识工作区前端可用性缺口:旧实现依赖全局文件夹选择器和请求载荷状态,用户在知识工作区内提问时,无法在同一任务窗口中直接确认当前 RAG 检索边界。
9+
同时,旧命中卡片会把摘要、引用、命中 section 与动作按钮全部铺在首层,导致结果返回后提问 / 回复区域容易被挤出视野,也让用户在第一层面对过多选择。
10+
11+
本切片的代码 / 方案对齐结果:
12+
13+
| 要求 | 当前实现证据 | 进度判断 |
14+
|---|---|---|
15+
| 在知识工作区窗口内默认显示并可切换 scope | `src/frontend/index.html` 现在在工作区内加入 `agent-workspace-scope-select``src/frontend/agent_workspace.js` 会镜像全局 `folder-select`,发布同一 active-target 事件,更新 `localStorage.nc_last_target`,并在 conversation 请求中发送 `activeTarget``scope`| 已实现 |
16+
| 命中返回后仍保持提问 / 回复区域可见 | `src/frontend/styles.css` 为聊天消息区设置稳定 flex 下限,并限制知识命中列表最大高度,使对话流和输入框不会被结果卡片挤出当前工作区。 | 已实现 |
17+
| 首层命中内容只显示可交互的知识点文件名 | `src/frontend/workspace_panes.js` 现在将每个知识点渲染为文件按钮,文件名来自 `sourcePath``citation.sourcePath``matchedSpans[0].sourcePath`;摘要、得分、引用与命中片段不再作为首层卡片内容。 | 已实现 |
18+
| 其他动作进入长按 / 右键菜单 | typed capability 按钮继续保持向前兼容,但默认隐藏在 `agent-knowledge-actions-menu` 中,只在长按、右键或键盘 context-menu 操作时展开。 | 已实现 |
19+
| 点击知识点后在右侧高亮命中段落 | 点击文件名会打开 graph-focus 面板,展示知识点标题与 `matchedSpans`,每个命中段落作为高亮证据渲染。 | 已实现 |
20+
| 双语文案与契约兼容 | `agentWorkspace.scope.*` 与新增知识点操作文案已经同步到中英 locale bundle,并由 locale contract 测试校验 key 覆盖与占位符一致性。 | 已保留 |
21+
22+
本切片验证:
23+
24+
- `npm.cmd exec -- jest src/agent_workspace.frontend.test.ts -t "scope selector|knowledge hits as file entries" --runInBand`
25+
- `npm.cmd exec -- jest src/agent_workspace.frontend.test.ts --runInBand`
26+
- `npm.cmd exec -- jest src/agent_workspace.locale.contract.test.ts --runInBand`
27+
- `node --check src/frontend/agent_workspace.js`
28+
- `node --check src/frontend/workspace_panes.js`
29+
- `npm.cmd run build`
30+
631
## 2026-06-06 active scope miss recovery 与 document-augmented RAG 修复
732

833
本次补丁修复了 WebView 已在 `npm run tauri:dev:mini:gpu` 中运行时复现的 “what is water glass?” 失败。

src/agent_workspace.frontend.test.ts

Lines changed: 122 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -700,10 +700,20 @@ function createWorkspaceHtml() {
700700
<!doctype html>
701701
<html>
702702
<body>
703+
<select id="folder-select">
704+
<option value="ALL_FOLDERS">All folders</option>
705+
<option value="financial">financial</option>
706+
<option value="waterglass">waterglass</option>
707+
</select>
703708
<div id="graph-wrapper">
704709
<div id="agent-workspace-shell">
705710
<section id="agent-chat-pane">
706711
<input id="agent-workspace-user-id" value="path_user_default" />
712+
<div class="agent-scope-control">
713+
<label for="agent-workspace-scope-select">Scope</label>
714+
<select id="agent-workspace-scope-select"></select>
715+
<div id="agent-workspace-scope-summary"></div>
716+
</div>
707717
<div id="agent-workspace-chat-messages"></div>
708718
<textarea id="agent-workspace-chat-input"></textarea>
709719
<button id="btn-agent-workspace-send"></button>
@@ -2331,8 +2341,8 @@ describe('workspace panes controller', () => {
23312341
expect(buttonsAfter).toEqual(['聚焦', '学习路径']);
23322342
});
23332343

2334-
test('renders grouped knowledge-point hit spans inside a single card', () => {
2335-
const { controller, document } = loadWorkspacePanesHarness();
2344+
test('renders knowledge hits as file entries and opens matched spans in the focus pane', () => {
2345+
const { controller, document, window } = loadWorkspacePanesHarness();
23362346
controller.init();
23372347

23382348
controller.renderKnowledgePoints([
@@ -2361,21 +2371,57 @@ describe('workspace panes controller', () => {
23612371
score: 0.81,
23622372
},
23632373
],
2364-
capabilities: [],
2374+
capabilities: [
2375+
{
2376+
actionId: 'open_focus_mode',
2377+
label: 'Focus',
2378+
},
2379+
{
2380+
actionId: 'open_learning_path',
2381+
label: 'Guided Learning',
2382+
},
2383+
],
23652384
},
23662385
], {
23672386
onCapability: jest.fn(),
23682387
});
23692388

23702389
const cards = Array.from(document.querySelectorAll('.agent-knowledge-card'));
23712390
expect(cards).toHaveLength(1);
2372-
const cardText = String(cards[0]?.textContent || '');
2373-
expect(cardText).toContain('Matched evidence');
2374-
expect(cardText).toContain('Definition');
2375-
expect(cardText).toContain('Thermal exchange');
2376-
expect(cardText).toContain('Knowledge_Base/waterglass/water glass.md:1');
2377-
expect(cardText).toContain('Knowledge_Base/waterglass/water glass.md:6');
2378-
expect(cards[0]?.querySelectorAll('.agent-knowledge-hit')).toHaveLength(2);
2391+
const fileButton = cards[0]?.querySelector('.agent-knowledge-file-button') as HTMLButtonElement;
2392+
expect(fileButton).not.toBeNull();
2393+
expect(String(fileButton?.textContent || '')).toBe('water glass.md');
2394+
expect(cards[0]?.querySelector('.agent-knowledge-summary')).toBeNull();
2395+
expect(cards[0]?.querySelectorAll('.agent-knowledge-hit')).toHaveLength(0);
2396+
2397+
const actionMenu = cards[0]?.querySelector('.agent-knowledge-actions-menu') as HTMLElement;
2398+
expect(actionMenu).not.toBeNull();
2399+
expect(actionMenu.hidden).toBe(true);
2400+
expect(actionMenu.querySelectorAll('button')).toHaveLength(2);
2401+
2402+
fileButton.dispatchEvent(new window.MouseEvent('contextmenu', {
2403+
bubbles: true,
2404+
cancelable: true,
2405+
}));
2406+
expect(actionMenu.hidden).toBe(false);
2407+
expect(fileButton.getAttribute('aria-expanded')).toBe('true');
2408+
fileButton.dispatchEvent(new window.KeyboardEvent('keydown', {
2409+
key: 'Escape',
2410+
bubbles: true,
2411+
}));
2412+
expect(actionMenu.hidden).toBe(true);
2413+
2414+
fileButton.click();
2415+
2416+
const graphPane = document.getElementById('agent-graph-focus-pane');
2417+
const graphBody = document.getElementById('agent-graph-focus-body');
2418+
expect(graphPane?.getAttribute('data-open')).toBe('true');
2419+
const focusText = String(graphBody?.textContent || '');
2420+
expect(focusText).toContain('Water Glass');
2421+
expect(focusText).toContain('Definition');
2422+
expect(focusText).toContain('A water glass is a physical system');
2423+
expect(focusText).toContain('Thermal exchange');
2424+
expect(focusText).toContain('The water glass exchanges heat');
23792425
});
23802426

23812427
test('keeps conversation card append kinds aligned with rerender registry', () => {
@@ -2656,6 +2702,68 @@ describe('agent workspace learning-path integration', () => {
26562702
expect(diagnostics.legacyActionFallbacks).toEqual([]);
26572703
});
26582704

2705+
test('shows a workspace scope selector and uses it for conversation requests', async () => {
2706+
const {
2707+
document,
2708+
window,
2709+
fetchMock,
2710+
} = loadAgentWorkspaceHarness();
2711+
if (!fetchMock) {
2712+
throw new Error('expected fetch mock');
2713+
}
2714+
2715+
fetchMock.mockImplementationOnce(async () => createSseResponse([
2716+
{
2717+
event: 'turn_completed',
2718+
payload: {
2719+
type: 'turn_completed',
2720+
turnId: 'turn_scope_selector',
2721+
emittedAt: '2026-04-13T00:00:00.100Z',
2722+
result: {
2723+
assistantMessage: 'scoped selector response',
2724+
citations: [],
2725+
recalledMemories: [],
2726+
memoryActions: [],
2727+
knowledgePoints: [],
2728+
summary: {
2729+
generatedAt: '2026-04-13T00:00:00.100Z',
2730+
topK: 6,
2731+
returnedKnowledgePoints: 0,
2732+
returnedCitations: 0,
2733+
recalledMemoryCount: 0,
2734+
queryEvidenceCoverageRatioPct: 0,
2735+
},
2736+
},
2737+
},
2738+
},
2739+
]));
2740+
2741+
const scopeSelect = document.getElementById('agent-workspace-scope-select') as HTMLSelectElement;
2742+
expect(scopeSelect).not.toBeNull();
2743+
expect(Array.from(scopeSelect.options).map((option) => option.value)).toEqual([
2744+
'ALL_FOLDERS',
2745+
'financial',
2746+
'waterglass',
2747+
]);
2748+
scopeSelect.value = 'waterglass';
2749+
scopeSelect.dispatchEvent(new window.Event('change', { bubbles: true }));
2750+
2751+
const scopeSummary = document.getElementById('agent-workspace-scope-summary');
2752+
expect(String(scopeSummary?.textContent || '')).toContain('waterglass');
2753+
2754+
const input = document.getElementById('agent-workspace-chat-input') as HTMLTextAreaElement;
2755+
input.value = 'what is water glass?';
2756+
await (window as any).NoteConnectionAgentWorkspace.sendConversation();
2757+
2758+
const requestBody = JSON.parse(String(fetchMock.mock.calls[0]?.[1]?.body || '{}'));
2759+
expect(requestBody.activeTarget).toBe('waterglass');
2760+
expect(requestBody.scope).toEqual({
2761+
workspaceId: 'waterglass',
2762+
corpusId: 'waterglass',
2763+
sourcePathPrefixes: ['Knowledge_Base/waterglass'],
2764+
});
2765+
});
2766+
26592767
test('prefers SSE turn streaming for conversation and renders the completed turn payload', async () => {
26602768
const {
26612769
document,
@@ -2798,9 +2906,10 @@ describe('agent workspace learning-path integration', () => {
27982906
).toBe(true);
27992907
const knowledgeCards = Array.from(document.querySelectorAll('.agent-knowledge-card'));
28002908
expect(knowledgeCards.length).toBeGreaterThan(0);
2801-
expect(String(knowledgeCards[0]?.textContent || '')).toContain('Stream Node');
2802-
expect(String(knowledgeCards[0]?.textContent || '')).toContain('Citation');
2803-
expect(String(knowledgeCards[0]?.textContent || '')).toContain('Knowledge_Base/optics/stream.md:12');
2909+
const fileButton = knowledgeCards[0]?.querySelector('.agent-knowledge-file-button') as HTMLButtonElement;
2910+
expect(String(fileButton?.textContent || '')).toBe('stream.md');
2911+
expect(knowledgeCards[0]?.querySelector('.agent-knowledge-summary')).toBeNull();
2912+
expect(knowledgeCards[0]?.querySelectorAll('.agent-knowledge-hit')).toHaveLength(0);
28042913
});
28052914

28062915
test('renders structured assistant blocks without breaking scoped conversation flow', async () => {

0 commit comments

Comments
 (0)