@steerable/agent-shell-web 0.6.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +91 -0
- package/README.md +20 -0
- package/package.json +69 -0
- package/public/favicon.ico +0 -0
- package/public/favicon.png +0 -0
- package/src/AppShell.tsx +24 -0
- package/src/assets/logo-generic.png +0 -0
- package/src/brand.test.ts +22 -0
- package/src/brand.ts +48 -0
- package/src/components/AgentSidebar.test.tsx +127 -0
- package/src/components/AgentSidebar.tsx +1010 -0
- package/src/components/ChatHeader.test.tsx +230 -0
- package/src/components/ChatHeader.tsx +436 -0
- package/src/components/DockPanelHeader.tsx +69 -0
- package/src/components/LocalLlmSettingsModal.test.tsx +372 -0
- package/src/components/LocalLlmSettingsModal.tsx +72 -0
- package/src/components/TerminalPanel.tsx +68 -0
- package/src/components/TerminalView.tsx +228 -0
- package/src/components/chat/ApprovalModal.test.tsx +83 -0
- package/src/components/chat/ApprovalModal.tsx +229 -0
- package/src/components/chat/AskUserModalHost.test.tsx +176 -0
- package/src/components/chat/AskUserModalHost.tsx +61 -0
- package/src/components/chat/AskUserQuestionMenu.tsx +621 -0
- package/src/components/chat/AssistantMessage.test.tsx +68 -0
- package/src/components/chat/AssistantMessage.tsx +469 -0
- package/src/components/chat/ChatInput.test.tsx +609 -0
- package/src/components/chat/ChatInput.tsx +2068 -0
- package/src/components/chat/ChatProjectBadge.tsx +362 -0
- package/src/components/chat/EmptyChat.tsx +18 -0
- package/src/components/chat/ExecPolicyPicker.test.tsx +26 -0
- package/src/components/chat/ExecPolicyPicker.tsx +120 -0
- package/src/components/chat/ExecutedActionsCard.tsx +216 -0
- package/src/components/chat/InterruptedTurnCard.tsx +53 -0
- package/src/components/chat/LocalChatPanel.tsx +408 -0
- package/src/components/chat/Markdown.test.tsx +170 -0
- package/src/components/chat/Markdown.tsx +432 -0
- package/src/components/chat/MessageList.tsx +332 -0
- package/src/components/chat/ModelPicker.test.tsx +149 -0
- package/src/components/chat/ModelPicker.tsx +289 -0
- package/src/components/chat/OrchestrationChildrenCard.tsx +40 -0
- package/src/components/chat/PlanTodoList.tsx +121 -0
- package/src/components/chat/ProjectTrustBanner.tsx +91 -0
- package/src/components/chat/SessionTreeModal.test.tsx +161 -0
- package/src/components/chat/SessionTreeModal.tsx +221 -0
- package/src/components/chat/StreamingStatus.tsx +95 -0
- package/src/components/chat/TaskOutcomeCards.test.tsx +97 -0
- package/src/components/chat/TaskOutcomeCards.tsx +128 -0
- package/src/components/chat/TaskPanelModal.test.tsx +198 -0
- package/src/components/chat/TaskPanelModal.tsx +310 -0
- package/src/components/chat/TaskProcessPanel.test.tsx +111 -0
- package/src/components/chat/TaskProcessPanel.tsx +208 -0
- package/src/components/chat/TurnProcessGroup.test.tsx +161 -0
- package/src/components/chat/TurnProcessGroup.tsx +193 -0
- package/src/components/chat/UnifiedDiff.tsx +31 -0
- package/src/components/chat/UserMessage.tsx +155 -0
- package/src/components/chat/elapsed.test.ts +42 -0
- package/src/components/chat/elapsed.ts +45 -0
- package/src/components/chat/executed-actions-model.test.ts +134 -0
- package/src/components/chat/executed-actions-model.ts +143 -0
- package/src/components/chat/orchestration-children-model.test.ts +115 -0
- package/src/components/chat/orchestration-children-model.ts +104 -0
- package/src/components/chat/timestamp.ts +24 -0
- package/src/components/chat/turn-timeline.test.ts +97 -0
- package/src/components/chat/turn-timeline.ts +132 -0
- package/src/components/chat/useChatTasks.test.ts +136 -0
- package/src/components/chat/useChatTasks.ts +134 -0
- package/src/components/chat/useCopy.ts +62 -0
- package/src/components/settings/AgentCapabilityFields.tsx +343 -0
- package/src/components/settings/AgentsSettingsPanel.test.tsx +229 -0
- package/src/components/settings/AgentsSettingsPanel.tsx +412 -0
- package/src/components/settings/DiagnoseSettingsPanel.tsx +119 -0
- package/src/components/settings/InsightsSettingsPanel.test.tsx +132 -0
- package/src/components/settings/InsightsSettingsPanel.tsx +309 -0
- package/src/components/settings/LlmSettingsPanel.tsx +875 -0
- package/src/components/settings/McpSettingsPanel.tsx +466 -0
- package/src/components/settings/ModelCapabilityChips.tsx +46 -0
- package/src/components/settings/ModelIdCombobox.test.tsx +88 -0
- package/src/components/settings/ModelIdCombobox.tsx +185 -0
- package/src/components/settings/SecuritySettingsPanel.test.tsx +122 -0
- package/src/components/settings/SecuritySettingsPanel.tsx +169 -0
- package/src/components/settings/SettingsSaveButton.tsx +35 -0
- package/src/components/settings/SkillsSettingsPanel.test.tsx +62 -0
- package/src/components/settings/SkillsSettingsPanel.tsx +334 -0
- package/src/components/settings/TelemetrySettingsPanel.tsx +173 -0
- package/src/components/settings/UsagePanel.tsx +196 -0
- package/src/components/settings/WebSearchSettingsPanel.test.tsx +90 -0
- package/src/components/settings/WebSearchSettingsPanel.tsx +183 -0
- package/src/components/settings/compat-flags-model.test.ts +71 -0
- package/src/components/settings/compat-flags-model.ts +57 -0
- package/src/components/settings/llm-vendors.test.ts +171 -0
- package/src/components/settings/llm-vendors.ts +275 -0
- package/src/components/settings/model-capabilities.test.ts +88 -0
- package/src/components/settings/model-capabilities.ts +123 -0
- package/src/components/settings/preset-choice-model.test.ts +96 -0
- package/src/components/settings/preset-choice-model.ts +75 -0
- package/src/fixtures/browser-dev-data.json +315 -0
- package/src/hooks/useChatsAndAgents.ts +182 -0
- package/src/layouts/AgentLayout.tsx +574 -0
- package/src/lib/attachments.ts +78 -0
- package/src/lib/browser-dev-electron-mock.ts +640 -0
- package/src/lib/chat-title.ts +33 -0
- package/src/lib/chat-transport.ts +346 -0
- package/src/lib/date-groups.ts +34 -0
- package/src/lib/demo-flag.ts +15 -0
- package/src/lib/electron-bridge.ts +270 -0
- package/src/lib/exec-policy.ts +25 -0
- package/src/lib/http-bridge.ts +293 -0
- package/src/lib/insights.ts +17 -0
- package/src/lib/local-api.ts +714 -0
- package/src/lib/pending-first-message.test.ts +17 -0
- package/src/lib/pending-first-message.ts +36 -0
- package/src/lib/slash-sources.ts +191 -0
- package/src/main.tsx +103 -0
- package/src/packs/registry.ts +64 -0
- package/src/pages/AgentPage.tsx +1226 -0
- package/src/pages/ChatPanelPreviewPage.tsx +264 -0
- package/src/pages/SettingsPage.test.tsx +102 -0
- package/src/pages/SettingsPage.tsx +197 -0
- package/src/styles/index.css +156 -0
- package/src/vite-env.d.ts +1 -0
- package/vite.base.ts +94 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
Apache License
|
|
2
|
+
Version 2.0, January 2004
|
|
3
|
+
http://www.apache.org/licenses/
|
|
4
|
+
|
|
5
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
6
|
+
|
|
7
|
+
1. Definitions.
|
|
8
|
+
|
|
9
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
10
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
11
|
+
|
|
12
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
13
|
+
the copyright owner that is granting the License.
|
|
14
|
+
|
|
15
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
16
|
+
other entities that control, are controlled by, or are under common
|
|
17
|
+
control with that entity.
|
|
18
|
+
|
|
19
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
20
|
+
exercising permissions granted by this License.
|
|
21
|
+
|
|
22
|
+
"Source" form shall mean the preferred form for making modifications.
|
|
23
|
+
|
|
24
|
+
"Object" form shall mean any form resulting from mechanical
|
|
25
|
+
transformation or translation of a Source form.
|
|
26
|
+
|
|
27
|
+
"Work" shall mean the work of authorship made available under the
|
|
28
|
+
License.
|
|
29
|
+
|
|
30
|
+
"Derivative Works" shall mean any work based on the Work.
|
|
31
|
+
|
|
32
|
+
"Contribution" shall mean any work of authorship intentionally
|
|
33
|
+
submitted to Licensor for inclusion in the Work.
|
|
34
|
+
|
|
35
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
36
|
+
on behalf of whom a Contribution has been received by Licensor.
|
|
37
|
+
|
|
38
|
+
2. Grant of Copyright License.
|
|
39
|
+
Subject to the terms and conditions of this License, each Contributor
|
|
40
|
+
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
|
|
41
|
+
royalty-free, irrevocable copyright license to reproduce, prepare
|
|
42
|
+
Derivative Works of, publicly display, publicly perform, sublicense,
|
|
43
|
+
and distribute the Work and such Derivative Works in Source or Object
|
|
44
|
+
form.
|
|
45
|
+
|
|
46
|
+
3. Grant of Patent License.
|
|
47
|
+
Subject to the terms and conditions of this License, each Contributor
|
|
48
|
+
hereby grants to You a perpetual, worldwide, non-exclusive, no-charge,
|
|
49
|
+
royalty-free, irrevocable patent license to make, have made, use,
|
|
50
|
+
offer to sell, sell, import, and otherwise transfer the Work.
|
|
51
|
+
|
|
52
|
+
4. Redistribution.
|
|
53
|
+
You may reproduce and distribute copies of the Work or Derivative
|
|
54
|
+
Works thereof in any medium, with or without modifications, and in
|
|
55
|
+
Source or Object form, provided that You meet the following conditions:
|
|
56
|
+
|
|
57
|
+
(a) You must give any other recipients of the Work or Derivative
|
|
58
|
+
Works a copy of this License; and
|
|
59
|
+
(b) You must cause any modified files to carry prominent notices
|
|
60
|
+
stating that You changed the files; and
|
|
61
|
+
(c) You must retain, in the Source form of any Derivative Works that
|
|
62
|
+
You distribute, all copyright, patent, trademark, and attribution
|
|
63
|
+
notices from the Source form of the Work; and
|
|
64
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
65
|
+
distribution, then any Derivative Works that You distribute must
|
|
66
|
+
include a readable copy of the attribution notices contained within
|
|
67
|
+
such NOTICE file.
|
|
68
|
+
|
|
69
|
+
5. Submission of Contributions.
|
|
70
|
+
Unless You explicitly state otherwise, any Contribution intentionally
|
|
71
|
+
submitted for inclusion in the Work shall be under the terms and
|
|
72
|
+
conditions of this License.
|
|
73
|
+
|
|
74
|
+
6. Trademarks.
|
|
75
|
+
This License does not grant permission to use the trade names,
|
|
76
|
+
trademarks, service marks, or product names of the Licensor.
|
|
77
|
+
|
|
78
|
+
7. Disclaimer of Warranty.
|
|
79
|
+
Unless required by applicable law or agreed to in writing, Licensor
|
|
80
|
+
provides the Work on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS
|
|
81
|
+
OF ANY KIND.
|
|
82
|
+
|
|
83
|
+
8. Limitation of Liability.
|
|
84
|
+
In no event and under no legal theory shall any Contributor be liable
|
|
85
|
+
to You for damages arising as a result of this License or out of the
|
|
86
|
+
use or inability to use the Work.
|
|
87
|
+
|
|
88
|
+
9. Accepting Warranty or Additional Liability.
|
|
89
|
+
While redistributing the Work or Derivative Works thereof, You may
|
|
90
|
+
choose to offer, and charge a fee for, acceptance of support, warranty,
|
|
91
|
+
indemnity, or other liability obligations.
|
package/README.md
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# @steerable/agent-shell-web
|
|
2
|
+
|
|
3
|
+
Product-neutral renderer SPA source for `@steerable/agent-shell` (React + Vite).
|
|
4
|
+
|
|
5
|
+
This is a **source package**, not a build artifact. Product web entry points
|
|
6
|
+
consume it via the `@/` alias (anchored at this package's `src/`) and the
|
|
7
|
+
`createProductViteConfig` factory exported from `./vite.base`:
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
import { createProductViteConfig } from '@steerable/agent-shell-web/vite.base';
|
|
11
|
+
|
|
12
|
+
export default createProductViteConfig({ productDir, flavor });
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The product's Vite build compiles this package's `src/` into the product's own
|
|
16
|
+
bundle; there is no prebuilt `dist/` here. See
|
|
17
|
+
[`docs/spec/architecture.md`](https://github.com/pathlyapp/steerable-framework/blob/develop/docs/spec/architecture.md)
|
|
18
|
+
for the tier model.
|
|
19
|
+
|
|
20
|
+
License: Apache-2.0.
|
package/package.json
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@steerable/agent-shell-web",
|
|
3
|
+
"version": "0.6.15",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Steerable framework — product-neutral renderer SPA source for @steerable/agent-shell (React + Vite). Consumed by product web entry points via the `@/` alias (anchored at this package's src/) and the createProductViteConfig factory. Published as a source package (src/ + vite.base.ts), not a build artifact.",
|
|
6
|
+
"license": "Apache-2.0",
|
|
7
|
+
"homepage": "https://steerableframework.com/",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "git+https://github.com/pathlyapp/steerable-framework.git",
|
|
11
|
+
"directory": "packages/agent-shell/web"
|
|
12
|
+
},
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/pathlyapp/steerable-framework/issues"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
"./vite.base": "./vite.base.ts",
|
|
18
|
+
"./package.json": "./package.json"
|
|
19
|
+
},
|
|
20
|
+
"files": [
|
|
21
|
+
"src",
|
|
22
|
+
"public",
|
|
23
|
+
"vite.base.ts",
|
|
24
|
+
"README.md"
|
|
25
|
+
],
|
|
26
|
+
"publishConfig": {
|
|
27
|
+
"access": "public",
|
|
28
|
+
"provenance": true
|
|
29
|
+
},
|
|
30
|
+
"dependencies": {
|
|
31
|
+
"@xterm/addon-fit": "^0.11.0",
|
|
32
|
+
"@xterm/addon-web-links": "^0.12.0",
|
|
33
|
+
"@xterm/xterm": "^6.0.0",
|
|
34
|
+
"framer-motion": "^12.38.0",
|
|
35
|
+
"react": "^18.3.1",
|
|
36
|
+
"react-dom": "^18.3.1",
|
|
37
|
+
"react-icons": "^5.5.0",
|
|
38
|
+
"react-markdown": "^10.1.0",
|
|
39
|
+
"react-router-dom": "^6.30.0",
|
|
40
|
+
"rehype-raw": "^7.0.0",
|
|
41
|
+
"remark-gfm": "^4.0.1",
|
|
42
|
+
"@steerable/agent-protocol": "0.6.15",
|
|
43
|
+
"@steerable/agent-ui": "0.6.15"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@tailwindcss/vite": "^4.3.0",
|
|
47
|
+
"@testing-library/react": "^16.1.0",
|
|
48
|
+
"@types/react": "^18.3.12",
|
|
49
|
+
"@types/react-dom": "^18.3.1",
|
|
50
|
+
"@vitejs/plugin-react": "^4.7.0",
|
|
51
|
+
"happy-dom": "^15.11.7",
|
|
52
|
+
"tailwindcss": "^4.3.0",
|
|
53
|
+
"typescript": "^5.8.3",
|
|
54
|
+
"vite": "^5.4.21",
|
|
55
|
+
"vitest": "^3.2.4",
|
|
56
|
+
"@steerable/pack-sdk": "0.6.15"
|
|
57
|
+
},
|
|
58
|
+
"scripts": {
|
|
59
|
+
"gen": "echo agent-shell-web",
|
|
60
|
+
"build": "tsc --noEmit",
|
|
61
|
+
"typecheck": "tsc --noEmit",
|
|
62
|
+
"test": "vitest run",
|
|
63
|
+
"lint": "tsc --noEmit",
|
|
64
|
+
"check:drift": "echo ok",
|
|
65
|
+
"app:build": "vite build --config vite.app.config.ts",
|
|
66
|
+
"app:build:demo": "vite build --config vite.demo.config.ts",
|
|
67
|
+
"app:dev": "vite --config vite.app.config.ts"
|
|
68
|
+
}
|
|
69
|
+
}
|
|
Binary file
|
|
Binary file
|
package/src/AppShell.tsx
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Outlet } from 'react-router-dom';
|
|
2
|
+
import { isElectron } from './lib/electron-bridge';
|
|
3
|
+
import { isDemoMode } from './lib/demo-flag';
|
|
4
|
+
|
|
5
|
+
export function AppShell() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="flex h-full w-full flex-col bg-agent-canvas text-agent-foreground">
|
|
8
|
+
<main className="flex flex-1 overflow-hidden">
|
|
9
|
+
<Outlet />
|
|
10
|
+
</main>
|
|
11
|
+
{isDemoMode() ? (
|
|
12
|
+
<div className="border-t border-agent-border bg-agent-muted px-3 py-1 text-xs text-agent-muted-foreground">
|
|
13
|
+
Demo · simulated data, no live model
|
|
14
|
+
</div>
|
|
15
|
+
) : (
|
|
16
|
+
!isElectron() && (
|
|
17
|
+
<div className="border-t border-agent-border bg-agent-muted px-3 py-1 text-xs text-agent-muted-foreground">
|
|
18
|
+
Browser preview mode · running outside Electron, IPC bridge unavailable
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
)}
|
|
22
|
+
</div>
|
|
23
|
+
);
|
|
24
|
+
}
|
|
Binary file
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import { LOCAL_ASSISTANT_AGENT_ID, pickDefaultAgentId } from './brand';
|
|
3
|
+
|
|
4
|
+
describe('pickDefaultAgentId', () => {
|
|
5
|
+
const catalog = [
|
|
6
|
+
{ id: LOCAL_ASSISTANT_AGENT_ID },
|
|
7
|
+
{ id: 'data-analyst' },
|
|
8
|
+
{ id: 'all-round-assistant' },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
it('returns null when the catalog is empty', () => {
|
|
12
|
+
expect(pickDefaultAgentId([])).toBeNull();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it('prefers the build-time default (shell: 电脑操作员) even when it is not first', () => {
|
|
16
|
+
expect(pickDefaultAgentId(catalog)).toBe(LOCAL_ASSISTANT_AGENT_ID);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it('falls back to the first visible agent when the build default is absent', () => {
|
|
20
|
+
expect(pickDefaultAgentId([{ id: 'writer' }, { id: 'reader' }])).toBe('writer');
|
|
21
|
+
});
|
|
22
|
+
});
|
package/src/brand.ts
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 渲染进程品牌单一真源。品牌文案在 web 构建时由产品 vite 配置注入
|
|
3
|
+
* (VITE_BRAND_* define:product.json brand 优先,其次激活包 pack.json
|
|
4
|
+
* brand,3.1);logo 资产由激活包的 web 模块在注册时经
|
|
5
|
+
* setBrandLogoUrl() 注入(资产静态 import 在包内,只进本产品 bundle)。
|
|
6
|
+
* 未注入时一律回落到 shell 中性默认(Steerable Shell / generic logo)。
|
|
7
|
+
* 主进程侧对应物见 src/brand.ts。
|
|
8
|
+
*/
|
|
9
|
+
import shellLogoUrl from '@/assets/logo-generic.png';
|
|
10
|
+
|
|
11
|
+
/** 当前构建的 flavor(开放字符串,0.3g 起不再是二值联合)。 */
|
|
12
|
+
export const APP_FLAVOR: string = import.meta.env.VITE_APP_FLAVOR ?? 'generic';
|
|
13
|
+
|
|
14
|
+
export const LOCAL_ASSISTANT_AGENT_ID = 'local-assistant';
|
|
15
|
+
|
|
16
|
+
export const BRAND_NAME: string = import.meta.env.VITE_BRAND_NAME ?? 'Steerable Shell';
|
|
17
|
+
|
|
18
|
+
/** shell 默认 logo(中性通用图标);包品牌 logo 由包 web 模块注册覆盖。 */
|
|
19
|
+
let brandLogoUrl: string = shellLogoUrl;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 注册产品品牌 logo(包的 register*Renderer() 在 bootstrap 前调用)。
|
|
23
|
+
* 重复注册抛错(组装期笔误,fail fast——与 node 侧 setProductBrand 同语义)。
|
|
24
|
+
*/
|
|
25
|
+
export function setBrandLogoUrl(url: string): void {
|
|
26
|
+
if (brandLogoUrl !== shellLogoUrl && brandLogoUrl !== url) {
|
|
27
|
+
throw new Error('[brand] logo already set');
|
|
28
|
+
}
|
|
29
|
+
brandLogoUrl = url;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/** 当前产品品牌 logo(bootstrap 之后读取——包的注册已完成)。 */
|
|
33
|
+
export function getBrandLogoUrl(): string {
|
|
34
|
+
return brandLogoUrl;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/** 首页 / 新对话未选手动专家时绑定的内置智能体(包品牌可覆盖)。 */
|
|
38
|
+
export const DEFAULT_AGENT_ID: string =
|
|
39
|
+
import.meta.env.VITE_DEFAULT_AGENT_ID ?? LOCAL_ASSISTANT_AGENT_ID;
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 从当前可见专家目录里挑默认 id:优先当前构建的默认智能体(包品牌经
|
|
43
|
+
* VITE_DEFAULT_AGENT_ID 注入),缺席则退回列表第一项。
|
|
44
|
+
*/
|
|
45
|
+
export function pickDefaultAgentId(agents: ReadonlyArray<{ id: string }>): string | null {
|
|
46
|
+
if (agents.length === 0) return null;
|
|
47
|
+
return agents.some((agent) => agent.id === DEFAULT_AGENT_ID) ? DEFAULT_AGENT_ID : agents[0].id;
|
|
48
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
|
|
2
|
+
import { MemoryRouter, Outlet, Route, Routes, useLocation } from 'react-router-dom';
|
|
3
|
+
import { afterEach, describe, expect, it, vi } from 'vitest';
|
|
4
|
+
import type { UseChatsAndAgentsResult } from '@/hooks/useChatsAndAgents';
|
|
5
|
+
import type { LocalChat, LocalChatAgent } from '@/lib/local-api';
|
|
6
|
+
|
|
7
|
+
vi.mock('@/lib/electron-bridge', () => ({
|
|
8
|
+
isElectron: () => false,
|
|
9
|
+
getElectronBridge: () => null,
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
vi.mock('@/brand', () => ({
|
|
13
|
+
BRAND_NAME: '测试助手',
|
|
14
|
+
getBrandLogoUrl: () => 'data:image/svg+xml,<svg xmlns="http://www.w3.org/2000/svg"/>',
|
|
15
|
+
}));
|
|
16
|
+
|
|
17
|
+
const { AgentSidebar } = await import('./AgentSidebar');
|
|
18
|
+
|
|
19
|
+
afterEach(() => cleanup());
|
|
20
|
+
|
|
21
|
+
const agent: LocalChatAgent = {
|
|
22
|
+
id: 'local-assistant',
|
|
23
|
+
slug: 'local-assistant',
|
|
24
|
+
name: '电脑操作员',
|
|
25
|
+
icon: null,
|
|
26
|
+
color: '#4f46e5',
|
|
27
|
+
description: null,
|
|
28
|
+
rolePrompt: null,
|
|
29
|
+
isBuiltin: true,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
const existingChat: LocalChat = {
|
|
33
|
+
id: 'chat-with-content',
|
|
34
|
+
projectId: null,
|
|
35
|
+
userId: 'local',
|
|
36
|
+
title: '已经聊过的对话',
|
|
37
|
+
agentId: agent.id,
|
|
38
|
+
createdAt: '2026-01-01T00:00:00.000Z',
|
|
39
|
+
updatedAt: '2026-01-01T00:01:00.000Z',
|
|
40
|
+
isPinned: false,
|
|
41
|
+
systemPrompt: null,
|
|
42
|
+
pinnedRefs: null,
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
function LocationProbe() {
|
|
46
|
+
const loc = useLocation();
|
|
47
|
+
return (
|
|
48
|
+
<div data-testid="loc">
|
|
49
|
+
{loc.pathname}
|
|
50
|
+
{loc.search}
|
|
51
|
+
</div>
|
|
52
|
+
);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function renderSidebar(
|
|
56
|
+
initialEntry: string,
|
|
57
|
+
createChat = vi.fn(),
|
|
58
|
+
) {
|
|
59
|
+
const data: UseChatsAndAgentsResult = {
|
|
60
|
+
chats: [existingChat],
|
|
61
|
+
agents: [agent],
|
|
62
|
+
isLoading: false,
|
|
63
|
+
error: null,
|
|
64
|
+
selectedAgentId: agent.id,
|
|
65
|
+
setSelectedAgentId: vi.fn(),
|
|
66
|
+
refreshChats: vi.fn(async () => {}),
|
|
67
|
+
refreshAgents: vi.fn(async () => {}),
|
|
68
|
+
createChat,
|
|
69
|
+
deleteChat: vi.fn(async () => true),
|
|
70
|
+
patchChatTitle: vi.fn(),
|
|
71
|
+
isLoadingMoreChats: false,
|
|
72
|
+
hasMoreChats: false,
|
|
73
|
+
loadMoreChats: vi.fn(async () => {}),
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
render(
|
|
77
|
+
<MemoryRouter initialEntries={[initialEntry]}>
|
|
78
|
+
<Routes>
|
|
79
|
+
<Route
|
|
80
|
+
element={
|
|
81
|
+
<>
|
|
82
|
+
<AgentSidebar
|
|
83
|
+
data={data}
|
|
84
|
+
rightPanel={null}
|
|
85
|
+
onToggleRightPanel={() => {}}
|
|
86
|
+
chatSlots={[]}
|
|
87
|
+
onCollapse={() => {}}
|
|
88
|
+
/>
|
|
89
|
+
<LocationProbe />
|
|
90
|
+
<Outlet />
|
|
91
|
+
</>
|
|
92
|
+
}
|
|
93
|
+
>
|
|
94
|
+
<Route path="/" element={<div />} />
|
|
95
|
+
<Route path="/agent" element={<div />} />
|
|
96
|
+
<Route path="/agent/:chatId" element={<div />} />
|
|
97
|
+
<Route path="/settings" element={<div />} />
|
|
98
|
+
</Route>
|
|
99
|
+
</Routes>
|
|
100
|
+
</MemoryRouter>,
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
return { createChat };
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
describe('AgentSidebar 新对话不落库', () => {
|
|
107
|
+
it('打开落地页且不调用 createChat', () => {
|
|
108
|
+
const { createChat } = renderSidebar('/agent/chat-with-content');
|
|
109
|
+
fireEvent.click(screen.getByTestId('sidebar-new-chat'));
|
|
110
|
+
expect(createChat).not.toHaveBeenCalled();
|
|
111
|
+
expect(screen.getByTestId('loc').textContent).toBe('/agent');
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it('连点两次也不会多出侧栏行', () => {
|
|
115
|
+
renderSidebar('/agent');
|
|
116
|
+
const before = screen.getAllByTestId('sidebar-chat-row').length;
|
|
117
|
+
fireEvent.click(screen.getByTestId('sidebar-new-chat'));
|
|
118
|
+
fireEvent.click(screen.getByTestId('sidebar-new-chat'));
|
|
119
|
+
expect(screen.getAllByTestId('sidebar-chat-row')).toHaveLength(before);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it('打开智能体管理页', () => {
|
|
123
|
+
renderSidebar('/agent');
|
|
124
|
+
fireEvent.click(screen.getByTestId('sidebar-agent-settings'));
|
|
125
|
+
expect(screen.getByTestId('loc').textContent).toBe('/settings?section=agents');
|
|
126
|
+
});
|
|
127
|
+
});
|