@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
|
@@ -0,0 +1,309 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from 'react';
|
|
2
|
+
import { LuChartBar } from 'react-icons/lu';
|
|
3
|
+
import { BRAND_NAME } from '@/brand';
|
|
4
|
+
import { getElectronBridge, isElectron } from '@/lib/electron-bridge';
|
|
5
|
+
|
|
6
|
+
type InsightsWire = {
|
|
7
|
+
installId: string;
|
|
8
|
+
shareBehavior: boolean;
|
|
9
|
+
shareConversation: boolean;
|
|
10
|
+
shareProfile: boolean;
|
|
11
|
+
promptedAt?: string;
|
|
12
|
+
apiBase?: string;
|
|
13
|
+
profile: {
|
|
14
|
+
displayName: string;
|
|
15
|
+
email: string;
|
|
16
|
+
company: string;
|
|
17
|
+
note: string;
|
|
18
|
+
};
|
|
19
|
+
stats?: { events: number; turns: number; profile: number; pending: number };
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const empty: InsightsWire = {
|
|
23
|
+
installId: '',
|
|
24
|
+
shareBehavior: false,
|
|
25
|
+
shareConversation: false,
|
|
26
|
+
shareProfile: false,
|
|
27
|
+
profile: { displayName: '', email: '', company: '', note: '' },
|
|
28
|
+
stats: { events: 0, turns: 0, profile: 0, pending: 0 },
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
export function InsightsConsentBanner() {
|
|
32
|
+
const [open, setOpen] = useState(false);
|
|
33
|
+
const [saving, setSaving] = useState(false);
|
|
34
|
+
|
|
35
|
+
useEffect(() => {
|
|
36
|
+
if (!isElectron()) return;
|
|
37
|
+
void getElectronBridge()!
|
|
38
|
+
.localBackend.request<InsightsWire>({ method: 'GET', path: '/api/v2/local-settings/insights' })
|
|
39
|
+
.then((data) => {
|
|
40
|
+
if (!data.promptedAt) setOpen(true);
|
|
41
|
+
})
|
|
42
|
+
.catch(() => {});
|
|
43
|
+
}, []);
|
|
44
|
+
|
|
45
|
+
const save = useCallback(async (upload: boolean) => {
|
|
46
|
+
if (!isElectron()) return;
|
|
47
|
+
setSaving(true);
|
|
48
|
+
try {
|
|
49
|
+
await getElectronBridge()!.localBackend.request({
|
|
50
|
+
method: 'POST',
|
|
51
|
+
path: '/api/v2/local-settings/insights',
|
|
52
|
+
body: {
|
|
53
|
+
shareBehavior: upload,
|
|
54
|
+
shareConversation: upload,
|
|
55
|
+
shareProfile: upload,
|
|
56
|
+
markPrompted: true,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
setOpen(false);
|
|
60
|
+
} catch {
|
|
61
|
+
setOpen(false);
|
|
62
|
+
} finally {
|
|
63
|
+
setSaving(false);
|
|
64
|
+
}
|
|
65
|
+
}, []);
|
|
66
|
+
|
|
67
|
+
if (!open) return null;
|
|
68
|
+
|
|
69
|
+
return (
|
|
70
|
+
<div
|
|
71
|
+
className="flex-shrink-0 border-t border-agent-border bg-agent-muted px-4 py-3"
|
|
72
|
+
data-testid="insights-consent-banner"
|
|
73
|
+
role="region"
|
|
74
|
+
aria-label="帮助改进产品同意"
|
|
75
|
+
>
|
|
76
|
+
<div className="flex items-center justify-between gap-3">
|
|
77
|
+
<div className="min-w-0">
|
|
78
|
+
<p className="text-sm font-medium text-agent-foreground">帮助改进{BRAND_NAME}</p>
|
|
79
|
+
<p className="mt-1 text-xs leading-relaxed text-agent-muted-foreground">
|
|
80
|
+
同意上传数据到服务器,帮助改进产品。之后可在设置里更改。
|
|
81
|
+
</p>
|
|
82
|
+
</div>
|
|
83
|
+
<div className="flex shrink-0 gap-2">
|
|
84
|
+
<button
|
|
85
|
+
type="button"
|
|
86
|
+
disabled={saving}
|
|
87
|
+
onClick={() => void save(true)}
|
|
88
|
+
className="h-8 whitespace-nowrap rounded-full bg-agent-foreground px-3.5 text-xs text-agent-canvas disabled:opacity-50"
|
|
89
|
+
>
|
|
90
|
+
保存
|
|
91
|
+
</button>
|
|
92
|
+
<button
|
|
93
|
+
type="button"
|
|
94
|
+
disabled={saving}
|
|
95
|
+
onClick={() => void save(false)}
|
|
96
|
+
className="h-8 whitespace-nowrap rounded-full border border-agent-border bg-agent-canvas px-3.5 text-xs text-agent-foreground disabled:opacity-50"
|
|
97
|
+
>
|
|
98
|
+
取消
|
|
99
|
+
</button>
|
|
100
|
+
</div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function InsightsSettingsPanel() {
|
|
107
|
+
const [data, setData] = useState<InsightsWire>(empty);
|
|
108
|
+
const [loading, setLoading] = useState(false);
|
|
109
|
+
const [saving, setSaving] = useState(false);
|
|
110
|
+
const [status, setStatus] = useState<string | null>(null);
|
|
111
|
+
const [error, setError] = useState<string | null>(null);
|
|
112
|
+
|
|
113
|
+
const load = useCallback(async () => {
|
|
114
|
+
if (!isElectron()) return;
|
|
115
|
+
setLoading(true);
|
|
116
|
+
setError(null);
|
|
117
|
+
try {
|
|
118
|
+
const res = await getElectronBridge()!.localBackend.request<InsightsWire>({
|
|
119
|
+
method: 'GET',
|
|
120
|
+
path: '/api/v2/local-settings/insights',
|
|
121
|
+
});
|
|
122
|
+
setData({ ...empty, ...res, profile: { ...empty.profile, ...res.profile } });
|
|
123
|
+
} catch (err) {
|
|
124
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
125
|
+
} finally {
|
|
126
|
+
setLoading(false);
|
|
127
|
+
}
|
|
128
|
+
}, []);
|
|
129
|
+
|
|
130
|
+
useEffect(() => {
|
|
131
|
+
void load();
|
|
132
|
+
}, [load]);
|
|
133
|
+
|
|
134
|
+
const save = async () => {
|
|
135
|
+
if (!isElectron()) return;
|
|
136
|
+
setSaving(true);
|
|
137
|
+
setError(null);
|
|
138
|
+
try {
|
|
139
|
+
const res = await getElectronBridge()!.localBackend.request<InsightsWire>({
|
|
140
|
+
method: 'POST',
|
|
141
|
+
path: '/api/v2/local-settings/insights',
|
|
142
|
+
body: {
|
|
143
|
+
shareBehavior: data.shareBehavior,
|
|
144
|
+
shareConversation: data.shareConversation,
|
|
145
|
+
shareProfile: data.shareProfile,
|
|
146
|
+
markPrompted: true,
|
|
147
|
+
apiBase: data.apiBase ?? '',
|
|
148
|
+
displayName: data.profile.displayName,
|
|
149
|
+
email: data.profile.email,
|
|
150
|
+
company: data.profile.company,
|
|
151
|
+
note: data.profile.note,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
setData({ ...empty, ...res, profile: { ...empty.profile, ...res.profile } });
|
|
155
|
+
setStatus('已保存。未勾选的种类只留在本机。');
|
|
156
|
+
} catch (err) {
|
|
157
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
158
|
+
} finally {
|
|
159
|
+
setSaving(false);
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const exportFile = async () => {
|
|
164
|
+
if (!isElectron()) return;
|
|
165
|
+
setError(null);
|
|
166
|
+
try {
|
|
167
|
+
const bundle = await getElectronBridge()!.localBackend.request<unknown>({
|
|
168
|
+
method: 'GET',
|
|
169
|
+
path: '/api/v2/insights/export',
|
|
170
|
+
});
|
|
171
|
+
const text = `${JSON.stringify(bundle, null, 2)}\n`;
|
|
172
|
+
const saved = await getElectronBridge()!.local?.saveTextFile?.({
|
|
173
|
+
title: '导出本地洞察记录',
|
|
174
|
+
defaultPath: `deeppath-insights-${new Date().toISOString().slice(0, 10)}.json`,
|
|
175
|
+
content: text,
|
|
176
|
+
});
|
|
177
|
+
if (saved?.canceled === false) setStatus(`已保存到 ${saved.filePath}`);
|
|
178
|
+
} catch (err) {
|
|
179
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
180
|
+
}
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const uploadNow = async () => {
|
|
184
|
+
if (!isElectron()) return;
|
|
185
|
+
setError(null);
|
|
186
|
+
try {
|
|
187
|
+
const res = await getElectronBridge()!.localBackend.request<{ ok: boolean; detail: string }>({
|
|
188
|
+
method: 'POST',
|
|
189
|
+
path: '/api/v2/insights/upload-local',
|
|
190
|
+
});
|
|
191
|
+
setStatus(res.ok ? `已上传到${BRAND_NAME}服务器` : '上传失败,记录仍在本机,可改导出文件发送');
|
|
192
|
+
await load();
|
|
193
|
+
} catch (err) {
|
|
194
|
+
setError(err instanceof Error ? err.message : String(err));
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
|
|
198
|
+
const stats = data.stats ?? empty.stats!;
|
|
199
|
+
|
|
200
|
+
return (
|
|
201
|
+
<div className="space-y-4" data-testid="insights-settings-panel">
|
|
202
|
+
<div className="rounded-agent-md border border-agent-border/60 bg-agent-muted/30 p-3.5 space-y-3">
|
|
203
|
+
<h4 className="flex items-center gap-1.5 text-xs font-semibold text-agent-foreground">
|
|
204
|
+
<LuChartBar className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
205
|
+
帮助改进产品(行为 / 对话 / 用户信息 分开确认)
|
|
206
|
+
</h4>
|
|
207
|
+
<p className="text-[11px] text-agent-muted-foreground">
|
|
208
|
+
即使用户不同意上传,记录也会留在本机。可导出 JSON 发给产品团队,或点一次「现在上传」。
|
|
209
|
+
</p>
|
|
210
|
+
{!isElectron() ? (
|
|
211
|
+
<p className="text-[11px] text-agent-muted-foreground">需要在桌面客户端中打开</p>
|
|
212
|
+
) : loading ? (
|
|
213
|
+
<p className="text-[11px] text-agent-muted-foreground">读取中…</p>
|
|
214
|
+
) : (
|
|
215
|
+
<>
|
|
216
|
+
<label className="flex items-start gap-2 text-xs">
|
|
217
|
+
<input
|
|
218
|
+
type="checkbox"
|
|
219
|
+
checked={data.shareBehavior}
|
|
220
|
+
onChange={(e) => setData((d) => ({ ...d, shareBehavior: e.target.checked }))}
|
|
221
|
+
/>
|
|
222
|
+
<span>
|
|
223
|
+
自动上传<strong>行为</strong>
|
|
224
|
+
<span className="block text-[10px] text-agent-muted-foreground">打开、发送、设置卡住等,不含问答正文</span>
|
|
225
|
+
</span>
|
|
226
|
+
</label>
|
|
227
|
+
<label className="flex items-start gap-2 text-xs">
|
|
228
|
+
<input
|
|
229
|
+
type="checkbox"
|
|
230
|
+
checked={data.shareConversation}
|
|
231
|
+
onChange={(e) => setData((d) => ({ ...d, shareConversation: e.target.checked }))}
|
|
232
|
+
/>
|
|
233
|
+
<span>
|
|
234
|
+
自动上传<strong>对话</strong>
|
|
235
|
+
<span className="block text-[10px] text-agent-muted-foreground">提问与回答(已脱敏密钥和用户目录)</span>
|
|
236
|
+
</span>
|
|
237
|
+
</label>
|
|
238
|
+
<label className="flex items-start gap-2 text-xs">
|
|
239
|
+
<input
|
|
240
|
+
type="checkbox"
|
|
241
|
+
checked={data.shareProfile}
|
|
242
|
+
onChange={(e) => setData((d) => ({ ...d, shareProfile: e.target.checked }))}
|
|
243
|
+
/>
|
|
244
|
+
<span>
|
|
245
|
+
自动上传<strong>用户信息</strong>
|
|
246
|
+
<span className="block text-[10px] text-agent-muted-foreground">与上面两项分开,不勾选则称呼/邮箱只留本机</span>
|
|
247
|
+
</span>
|
|
248
|
+
</label>
|
|
249
|
+
<div className="grid grid-cols-2 gap-2">
|
|
250
|
+
{(
|
|
251
|
+
[
|
|
252
|
+
['displayName', '称呼'],
|
|
253
|
+
['email', '邮箱'],
|
|
254
|
+
['company', '公司/团队'],
|
|
255
|
+
['note', '想告诉我们的'],
|
|
256
|
+
] as const
|
|
257
|
+
).map(([key, label]) => (
|
|
258
|
+
<label key={key} className="space-y-1 text-[11px] text-agent-muted-foreground">
|
|
259
|
+
{label}
|
|
260
|
+
<input
|
|
261
|
+
value={data.profile[key]}
|
|
262
|
+
onChange={(e) =>
|
|
263
|
+
setData((d) => ({ ...d, profile: { ...d.profile, [key]: e.target.value } }))
|
|
264
|
+
}
|
|
265
|
+
className="h-8 w-full rounded-agent-md border border-agent-border bg-agent-canvas px-2 text-xs text-agent-foreground"
|
|
266
|
+
/>
|
|
267
|
+
</label>
|
|
268
|
+
))}
|
|
269
|
+
</div>
|
|
270
|
+
<p className="text-[10px] text-agent-muted-foreground">
|
|
271
|
+
本机已记 行为 {stats.events} / 对话 {stats.turns} / 资料 {stats.profile},待上传 {stats.pending}
|
|
272
|
+
{data.installId ? ` · ${data.installId.slice(0, 8)}` : ''}
|
|
273
|
+
</p>
|
|
274
|
+
<div className="flex flex-wrap gap-2">
|
|
275
|
+
<button
|
|
276
|
+
type="button"
|
|
277
|
+
disabled={saving}
|
|
278
|
+
onClick={() => void save()}
|
|
279
|
+
className="h-8 rounded-full bg-agent-foreground px-4 text-xs text-agent-canvas"
|
|
280
|
+
>
|
|
281
|
+
保存
|
|
282
|
+
</button>
|
|
283
|
+
<button
|
|
284
|
+
type="button"
|
|
285
|
+
onClick={() => void exportFile()}
|
|
286
|
+
className="h-8 rounded-full border border-agent-border px-4 text-xs"
|
|
287
|
+
>
|
|
288
|
+
导出文件发给开发者
|
|
289
|
+
</button>
|
|
290
|
+
<button
|
|
291
|
+
type="button"
|
|
292
|
+
onClick={() => void uploadNow()}
|
|
293
|
+
className="h-8 rounded-full border border-agent-border px-4 text-xs"
|
|
294
|
+
>
|
|
295
|
+
现在上传本地记录
|
|
296
|
+
</button>
|
|
297
|
+
</div>
|
|
298
|
+
</>
|
|
299
|
+
)}
|
|
300
|
+
{status && <p className="text-[10px] text-agent-muted-foreground">{status}</p>}
|
|
301
|
+
</div>
|
|
302
|
+
{error && (
|
|
303
|
+
<div className="rounded-agent-md border border-agent-destructive/20 bg-agent-destructive/10 p-2.5 text-xs text-agent-destructive">
|
|
304
|
+
{error}
|
|
305
|
+
</div>
|
|
306
|
+
)}
|
|
307
|
+
</div>
|
|
308
|
+
);
|
|
309
|
+
}
|