@steerable/agent-shell-web 0.6.53 → 0.6.56
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/README.md +3 -4
- package/package.json +4 -4
- package/src/AppShell.tsx +3 -3
- package/src/brand.ts +1 -1
- package/src/components/AgentSidebar.tsx +64 -56
- package/src/components/ChatHeader.tsx +41 -25
- package/src/components/ConfirmDialog.tsx +4 -3
- package/src/components/CreateProjectModal.tsx +31 -21
- package/src/components/LocalLlmSettingsModal.tsx +4 -3
- package/src/components/SidebarRelease.tsx +6 -5
- package/src/components/TerminalPanel.tsx +6 -5
- package/src/components/TerminalView.tsx +15 -10
- package/src/components/chat/ApprovalModal.tsx +40 -26
- package/src/components/chat/ApprovalPromptProvider.tsx +5 -5
- package/src/components/chat/AskUserPromptProvider.tsx +4 -4
- package/src/components/chat/AskUserQuestionMenu.tsx +19 -14
- package/src/components/chat/AssistantMessage.tsx +18 -13
- package/src/components/chat/ChatInput.tsx +56 -40
- package/src/components/chat/ChatProjectBadge.tsx +27 -22
- package/src/components/chat/EmptyChatHero.tsx +2 -1
- package/src/components/chat/ExecPolicyPicker.tsx +12 -11
- package/src/components/chat/ExecutedActionsCard.tsx +45 -33
- package/src/components/chat/FilePathCode.tsx +8 -3
- package/src/components/chat/InterruptedTurnCard.tsx +4 -3
- package/src/components/chat/LocalChatPanel.tsx +2 -1
- package/src/components/chat/Markdown.tsx +2 -1
- package/src/components/chat/MessageList.tsx +3 -2
- package/src/components/chat/ModelPicker.tsx +25 -16
- package/src/components/chat/OrchestrationChildrenCard.tsx +2 -1
- package/src/components/chat/OrchestrationFlowCard.tsx +23 -22
- package/src/components/chat/PlanTodoList.tsx +2 -1
- package/src/components/chat/ProjectTrustBanner.tsx +19 -5
- package/src/components/chat/SessionOrchestrationFlow.tsx +27 -26
- package/src/components/chat/SessionTodoList.tsx +8 -7
- package/src/components/chat/SessionTreeModal.tsx +10 -9
- package/src/components/chat/StreamingStatus.tsx +4 -3
- package/src/components/chat/SuggestedReplies.tsx +3 -2
- package/src/components/chat/TaskOutcomeCards.tsx +8 -5
- package/src/components/chat/TaskPanelModal.tsx +33 -23
- package/src/components/chat/TaskProcessPanel.tsx +16 -15
- package/src/components/chat/TurnFilesCard.tsx +14 -13
- package/src/components/chat/UserMessage.tsx +12 -11
- package/src/components/chat/executed-actions-model.ts +47 -35
- package/src/components/chat/orchestration-flow-model.ts +16 -5
- package/src/components/chat/process-status.ts +8 -7
- package/src/components/chat/todo-list-model.ts +5 -4
- package/src/components/chat/turn-files.ts +8 -8
- package/src/components/chat/useChatTasks.ts +3 -3
- package/src/components/settings/AgentCapabilityFields.tsx +26 -23
- package/src/components/settings/AgentsSettingsPanel.tsx +33 -28
- package/src/components/settings/AppUpdateSettingsPanel.tsx +3 -2
- package/src/components/settings/AppearanceSettingsPanel.tsx +81 -37
- package/src/components/settings/DiagnoseSettingsPanel.tsx +11 -8
- package/src/components/settings/InsightsSettingsPanel.tsx +58 -43
- package/src/components/settings/LlmSettingsPanel.tsx +107 -86
- package/src/components/settings/McpSettingsPanel.tsx +72 -46
- package/src/components/settings/ModelIdCombobox.tsx +2 -1
- package/src/components/settings/OrchestrationSettingsPanel.tsx +8 -5
- package/src/components/settings/PortableSettingsPanel.tsx +83 -55
- package/src/components/settings/PythonRunnerSettingsPanel.tsx +26 -23
- package/src/components/settings/SecuritySettingsPanel.tsx +58 -34
- package/src/components/settings/SettingsNavMenu.tsx +3 -2
- package/src/components/settings/SettingsSaveButton.tsx +2 -1
- package/src/components/settings/SkillsSettingsPanel.tsx +43 -38
- package/src/components/settings/TelemetrySettingsPanel.tsx +20 -17
- package/src/components/settings/UsagePanel.tsx +22 -23
- package/src/components/settings/WebSearchSettingsPanel.tsx +28 -19
- package/src/components/settings/llm-vendors.ts +12 -7
- package/src/components/settings/model-capabilities.ts +16 -15
- package/src/components/settings/preset-choice-model.ts +6 -3
- package/src/fixtures/browser-dev-data.json +24 -24
- package/src/hooks/useChatsAndAgents.ts +9 -9
- package/src/hooks/useProjects.ts +3 -3
- package/src/i18n/agent-label.ts +15 -0
- package/src/i18n/index.tsx +68 -0
- package/src/i18n/runtime.ts +117 -0
- package/src/i18n/translate.ts +65 -0
- package/src/layouts/AgentLayout.tsx +13 -12
- package/src/lib/app-release.ts +16 -11
- package/src/lib/attachments.ts +18 -9
- package/src/lib/{browser-dev-electron-mock.ts → browser-dev-host-mock.ts} +42 -42
- package/src/lib/chat-title.ts +3 -2
- package/src/lib/chat-transport.ts +16 -12
- package/src/lib/date-groups.ts +23 -15
- package/src/lib/host-bridge.ts +371 -4
- package/src/lib/host-paste.ts +1 -1
- package/src/lib/http-bridge.ts +12 -9
- package/src/lib/insights.ts +2 -2
- package/src/lib/local-api.ts +5 -5
- package/src/lib/portable.ts +11 -10
- package/src/lib/show-thinking-content.ts +15 -3
- package/src/lib/slash-sources.ts +3 -3
- package/src/lib/tauri-bridge.ts +1 -1
- package/src/main.tsx +27 -4
- package/src/pages/AgentPage.tsx +39 -34
- package/src/pages/ChatPanelPreviewPage.tsx +46 -44
- package/src/pages/SettingsPage.tsx +51 -45
- package/src/vite-env.d.ts +3 -0
- package/vite.base.js +6 -2
- package/src/lib/electron-bridge.ts +0 -390
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { LuLoaderCircle, LuChartBar, LuRefreshCw } from 'react-icons/lu';
|
|
3
|
-
import {
|
|
3
|
+
import { t } from '@/i18n';
|
|
4
|
+
import { getHostBridge, hasHostBridge } from '@/lib/host-bridge';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* UsagePanel — W6-9 用量与成本归因的桌面面板。
|
|
@@ -40,11 +41,7 @@ interface UsageSummaryWire {
|
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
43
|
|
|
43
|
-
const WINDOW_OPTIONS = [
|
|
44
|
-
{ days: 7, label: '近 7 天' },
|
|
45
|
-
{ days: 30, label: '近 30 天' },
|
|
46
|
-
{ days: 90, label: '近 90 天' },
|
|
47
|
-
] as const;
|
|
44
|
+
const WINDOW_OPTIONS = [{ days: 7 }, { days: 30 }, { days: 90 }] as const;
|
|
48
45
|
|
|
49
46
|
function formatTokens(n: number): string {
|
|
50
47
|
if (n >= 1_000_000) return `${(n / 1_000_000).toFixed(2)}M`;
|
|
@@ -69,11 +66,11 @@ export function UsagePanel() {
|
|
|
69
66
|
const [error, setError] = useState<string | null>(null);
|
|
70
67
|
|
|
71
68
|
const fetchSummary = useCallback(async (windowDays: number) => {
|
|
72
|
-
if (!
|
|
69
|
+
if (!hasHostBridge()) return;
|
|
73
70
|
setLoading(true);
|
|
74
71
|
setError(null);
|
|
75
72
|
try {
|
|
76
|
-
const res = await
|
|
73
|
+
const res = await getHostBridge()!.localBackend.request<UsageSummaryWire>({
|
|
77
74
|
method: 'GET',
|
|
78
75
|
path: `/api/v2/usage/summary?days=${windowDays}`,
|
|
79
76
|
});
|
|
@@ -95,7 +92,7 @@ export function UsagePanel() {
|
|
|
95
92
|
<div className="flex items-center justify-between">
|
|
96
93
|
<h4 className="text-xs font-semibold text-agent-foreground flex items-center gap-1.5">
|
|
97
94
|
<LuChartBar className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
98
|
-
|
|
95
|
+
{t('Usage and cost')}
|
|
99
96
|
</h4>
|
|
100
97
|
<div className="flex items-center gap-1.5">
|
|
101
98
|
{WINDOW_OPTIONS.map((opt) => (
|
|
@@ -109,7 +106,7 @@ export function UsagePanel() {
|
|
|
109
106
|
: 'bg-agent-canvas text-agent-muted-foreground border border-agent-border hover:bg-agent-muted/40'
|
|
110
107
|
}`}
|
|
111
108
|
>
|
|
112
|
-
{opt.
|
|
109
|
+
{t('Last {days} days', { days: opt.days })}
|
|
113
110
|
</button>
|
|
114
111
|
))}
|
|
115
112
|
<button
|
|
@@ -117,7 +114,7 @@ export function UsagePanel() {
|
|
|
117
114
|
onClick={() => void fetchSummary(days)}
|
|
118
115
|
disabled={loading}
|
|
119
116
|
className="h-6 w-6 rounded-full border border-agent-border bg-agent-canvas text-agent-muted-foreground hover:bg-agent-muted/40 flex items-center justify-center"
|
|
120
|
-
title=
|
|
117
|
+
title={t('Refresh')}
|
|
121
118
|
>
|
|
122
119
|
<LuRefreshCw className={`h-3 w-3 ${loading ? 'animate-spin' : ''}`} />
|
|
123
120
|
</button>
|
|
@@ -127,30 +124,30 @@ export function UsagePanel() {
|
|
|
127
124
|
{loading && !summary ? (
|
|
128
125
|
<div className="flex items-center gap-2 py-4 text-xs text-agent-muted-foreground">
|
|
129
126
|
<LuLoaderCircle className="h-3.5 w-3.5 animate-spin" />
|
|
130
|
-
|
|
127
|
+
{t('Loading usage...')}
|
|
131
128
|
</div>
|
|
132
129
|
) : !summary || summary.byModel.length === 0 ? (
|
|
133
130
|
<p className="text-[11px] text-agent-muted-foreground py-2">
|
|
134
|
-
|
|
131
|
+
{t('No usage in this time window yet. After a conversation turn finishes, tokens and cost are grouped by model here.')}
|
|
135
132
|
</p>
|
|
136
133
|
) : (
|
|
137
134
|
<>
|
|
138
135
|
{/* 总计卡片 */}
|
|
139
136
|
<div className="grid grid-cols-4 gap-2">
|
|
140
137
|
<div className="rounded-agent-md border border-agent-border bg-agent-canvas px-2.5 py-2">
|
|
141
|
-
<div className="text-[10px] text-agent-muted-foreground"
|
|
138
|
+
<div className="text-[10px] text-agent-muted-foreground">{t('Total turns')}</div>
|
|
142
139
|
<div className="text-xs font-semibold text-agent-foreground">{summary.totals.turns}</div>
|
|
143
140
|
</div>
|
|
144
141
|
<div className="rounded-agent-md border border-agent-border bg-agent-canvas px-2.5 py-2">
|
|
145
|
-
<div className="text-[10px] text-agent-muted-foreground"
|
|
142
|
+
<div className="text-[10px] text-agent-muted-foreground">{t('Total tokens')}</div>
|
|
146
143
|
<div className="text-xs font-semibold text-agent-foreground">{formatTokens(summary.totals.totalTokens)}</div>
|
|
147
144
|
</div>
|
|
148
145
|
<div className="rounded-agent-md border border-agent-border bg-agent-canvas px-2.5 py-2">
|
|
149
|
-
<div className="text-[10px] text-agent-muted-foreground"
|
|
146
|
+
<div className="text-[10px] text-agent-muted-foreground">{t('Cache hits')}</div>
|
|
150
147
|
<div className="text-xs font-semibold text-agent-foreground">{formatTokens(summary.totals.cachedPromptTokens)}</div>
|
|
151
148
|
</div>
|
|
152
149
|
<div className="rounded-agent-md border border-agent-border bg-agent-canvas px-2.5 py-2">
|
|
153
|
-
<div className="text-[10px] text-agent-muted-foreground"
|
|
150
|
+
<div className="text-[10px] text-agent-muted-foreground">{t('Estimated cost')}</div>
|
|
154
151
|
<div className="text-xs font-semibold text-agent-foreground">{formatCost(summary.totals.costUsd)}</div>
|
|
155
152
|
</div>
|
|
156
153
|
</div>
|
|
@@ -160,11 +157,11 @@ export function UsagePanel() {
|
|
|
160
157
|
<table className="w-full text-[11px]">
|
|
161
158
|
<thead>
|
|
162
159
|
<tr className="bg-agent-muted/40 text-agent-muted-foreground">
|
|
163
|
-
<th className="text-left font-medium px-2.5 py-1.5"
|
|
164
|
-
<th className="text-right font-medium px-2.5 py-1.5"
|
|
165
|
-
<th className="text-right font-medium px-2.5 py-1.5"
|
|
166
|
-
<th className="text-right font-medium px-2.5 py-1.5"
|
|
167
|
-
<th className="text-right font-medium px-2.5 py-1.5"
|
|
160
|
+
<th className="text-left font-medium px-2.5 py-1.5">{t('Model')}</th>
|
|
161
|
+
<th className="text-right font-medium px-2.5 py-1.5">{t('Turns')}</th>
|
|
162
|
+
<th className="text-right font-medium px-2.5 py-1.5">{t('Input')}</th>
|
|
163
|
+
<th className="text-right font-medium px-2.5 py-1.5">{t('Output')}</th>
|
|
164
|
+
<th className="text-right font-medium px-2.5 py-1.5">{t('Cost')}</th>
|
|
168
165
|
</tr>
|
|
169
166
|
</thead>
|
|
170
167
|
<tbody>
|
|
@@ -184,7 +181,9 @@ export function UsagePanel() {
|
|
|
184
181
|
</div>
|
|
185
182
|
|
|
186
183
|
<p className="text-[10px] text-agent-muted-foreground">
|
|
187
|
-
|
|
184
|
+
{t(
|
|
185
|
+
'Cost is estimated from the framework price table and converted to RMB at about 1 USD = 7.2 CNY. Only models with a price are counted; "—" means the model has no price (local or unknown). Cache hits are the tokens served from the prompt cache.',
|
|
186
|
+
)}
|
|
188
187
|
</p>
|
|
189
188
|
</>
|
|
190
189
|
)}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { useCallback, useEffect, useState } from 'react';
|
|
2
2
|
import { LuLoaderCircle, LuSearch } from 'react-icons/lu';
|
|
3
|
-
import {
|
|
3
|
+
import { t } from '@/i18n';
|
|
4
|
+
import { getHostBridge, hasHostBridge } from '@/lib/host-bridge';
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Search backend settings. `ddg` registers `web_search` with no key.
|
|
@@ -32,11 +33,11 @@ export function WebSearchSettingsPanel() {
|
|
|
32
33
|
const [error, setError] = useState<string | null>(null);
|
|
33
34
|
|
|
34
35
|
const fetchSettings = useCallback(async () => {
|
|
35
|
-
if (!
|
|
36
|
+
if (!hasHostBridge()) return;
|
|
36
37
|
setLoading(true);
|
|
37
38
|
setError(null);
|
|
38
39
|
try {
|
|
39
|
-
const res = await
|
|
40
|
+
const res = await getHostBridge()!.localBackend.request<WebSearchSettingsWire | null>({
|
|
40
41
|
method: 'GET',
|
|
41
42
|
path: '/api/v2/local-settings/web-search',
|
|
42
43
|
});
|
|
@@ -54,13 +55,13 @@ export function WebSearchSettingsPanel() {
|
|
|
54
55
|
}, [fetchSettings]);
|
|
55
56
|
|
|
56
57
|
const handleSave = async () => {
|
|
57
|
-
if (!
|
|
58
|
+
if (!hasHostBridge()) return;
|
|
58
59
|
setSaving(true);
|
|
59
60
|
setError(null);
|
|
60
61
|
setStatus(null);
|
|
61
62
|
try {
|
|
62
63
|
const posted = provider;
|
|
63
|
-
const saved = await
|
|
64
|
+
const saved = await getHostBridge()!.localBackend.request<WebSearchSettingsWire>({
|
|
64
65
|
method: 'POST',
|
|
65
66
|
path: '/api/v2/local-settings/web-search',
|
|
66
67
|
body: { provider: posted, apiKey: apiKey.trim() },
|
|
@@ -71,11 +72,15 @@ export function WebSearchSettingsPanel() {
|
|
|
71
72
|
setProvider(nextProvider);
|
|
72
73
|
setApiKey(saved?.apiKey ?? '');
|
|
73
74
|
if (nextProvider === 'ddg') {
|
|
74
|
-
setStatus('
|
|
75
|
+
setStatus(t('Saved. After you restart the app, the sidecar registers free search (DuckDuckGo).'));
|
|
75
76
|
} else if (saved?.apiKey) {
|
|
76
|
-
setStatus('
|
|
77
|
+
setStatus(t('Saved. After you restart the app, the sidecar registers web_search.'));
|
|
77
78
|
} else {
|
|
78
|
-
setStatus(
|
|
79
|
+
setStatus(
|
|
80
|
+
t(
|
|
81
|
+
'Saved. Without a key the model has no search tool (OpenAI can use the chat credentials for hosted search).',
|
|
82
|
+
),
|
|
83
|
+
);
|
|
79
84
|
}
|
|
80
85
|
} catch (err) {
|
|
81
86
|
setError(err instanceof Error ? err.message : String(err));
|
|
@@ -89,25 +94,25 @@ export function WebSearchSettingsPanel() {
|
|
|
89
94
|
<div className="bg-agent-muted/30 border border-agent-border/60 rounded-agent-md p-2.5 space-y-2">
|
|
90
95
|
<h4 className="text-xs font-semibold text-agent-foreground flex items-center gap-1.5">
|
|
91
96
|
<LuSearch className="h-3.5 w-3.5 text-agent-muted-foreground" />
|
|
92
|
-
|
|
97
|
+
{t('Web search')}
|
|
93
98
|
</h4>
|
|
94
99
|
|
|
95
100
|
{loading ? (
|
|
96
101
|
<div className="flex items-center gap-2 py-2 text-xs text-agent-muted-foreground">
|
|
97
102
|
<LuLoaderCircle className="h-3.5 w-3.5 animate-spin" />
|
|
98
|
-
|
|
103
|
+
{t('Loading search settings...')}
|
|
99
104
|
</div>
|
|
100
105
|
) : (
|
|
101
106
|
<>
|
|
102
107
|
<div className="space-y-1.5">
|
|
103
108
|
<label className="text-[11px] font-medium text-agent-muted-foreground">
|
|
104
|
-
|
|
109
|
+
{t('Search backend')}
|
|
105
110
|
</label>
|
|
106
111
|
<div className="flex gap-2">
|
|
107
112
|
{(
|
|
108
113
|
[
|
|
109
|
-
{ value: 'ddg', label: '
|
|
110
|
-
{ value: 'tavily', label: 'Tavily', hint: '
|
|
114
|
+
{ value: 'ddg', label: 'Free', hint: 'DuckDuckGo, no key needed' },
|
|
115
|
+
{ value: 'tavily', label: 'Tavily', hint: 'Needs an API key, more reliable results' },
|
|
111
116
|
] as const
|
|
112
117
|
).map((opt) => (
|
|
113
118
|
<button
|
|
@@ -121,8 +126,8 @@ export function WebSearchSettingsPanel() {
|
|
|
121
126
|
: 'border-agent-border bg-agent-canvas hover:bg-agent-muted/40'
|
|
122
127
|
}`}
|
|
123
128
|
>
|
|
124
|
-
<div className="text-xs font-medium text-agent-foreground">{opt.label}</div>
|
|
125
|
-
<div className="text-[10px] text-agent-muted-foreground">{opt.hint}</div>
|
|
129
|
+
<div className="text-xs font-medium text-agent-foreground">{t(opt.label)}</div>
|
|
130
|
+
<div className="text-[10px] text-agent-muted-foreground">{t(opt.hint)}</div>
|
|
126
131
|
</button>
|
|
127
132
|
))}
|
|
128
133
|
</div>
|
|
@@ -137,7 +142,7 @@ export function WebSearchSettingsPanel() {
|
|
|
137
142
|
type="password"
|
|
138
143
|
value={apiKey}
|
|
139
144
|
onChange={(e) => setApiKey(e.target.value)}
|
|
140
|
-
placeholder=
|
|
145
|
+
placeholder={t('tvly-... (leave blank = do not register Tavily)')}
|
|
141
146
|
className="h-8 w-full rounded-agent-md border border-agent-border bg-agent-canvas px-3 font-mono text-[11px] text-agent-foreground focus:outline-none focus:ring-2 focus:ring-agent-foreground/30"
|
|
142
147
|
autoComplete="off"
|
|
143
148
|
/>
|
|
@@ -147,8 +152,12 @@ export function WebSearchSettingsPanel() {
|
|
|
147
152
|
<div className="flex items-center justify-between gap-2">
|
|
148
153
|
<p className="text-[10px] text-agent-muted-foreground">
|
|
149
154
|
{provider === 'ddg'
|
|
150
|
-
?
|
|
151
|
-
|
|
155
|
+
? t(
|
|
156
|
+
'The free backend uses the public DuckDuckGo search page and needs no key. Quality and reliability are lower than Tavily, and networks in mainland China may need a system proxy. Harbor evaluations still turn off network tools.',
|
|
157
|
+
)
|
|
158
|
+
: t(
|
|
159
|
+
'GLM / OpenRouter / DeepSeek need this key. OpenAI (api.openai.com) can use the chat credentials for hosted search with no extra setup. Harbor evaluations still turn off network tools.',
|
|
160
|
+
)}
|
|
152
161
|
</p>
|
|
153
162
|
<button
|
|
154
163
|
type="button"
|
|
@@ -160,7 +169,7 @@ export function WebSearchSettingsPanel() {
|
|
|
160
169
|
: 'bg-agent-foreground text-agent-canvas hover:opacity-90'
|
|
161
170
|
}`}
|
|
162
171
|
>
|
|
163
|
-
{saving ? <LuLoaderCircle className="h-3 w-3 animate-spin" /> : '
|
|
172
|
+
{saving ? <LuLoaderCircle className="h-3 w-3 animate-spin" /> : t('Save')}
|
|
164
173
|
</button>
|
|
165
174
|
</div>
|
|
166
175
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* 常用置顶、中文标签、以及 sidecar 未就绪时的本地回退。
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
import { t } from '@/i18n';
|
|
6
7
|
import type {
|
|
7
8
|
CatalogProviderDescriptor,
|
|
8
9
|
GatewayModelEntry,
|
|
@@ -42,14 +43,14 @@ export const VENDOR_LABELS: Record<string, string> = {
|
|
|
42
43
|
anthropic: 'Anthropic',
|
|
43
44
|
google: 'Google Gemini',
|
|
44
45
|
openrouter: 'OpenRouter',
|
|
45
|
-
'alibaba-cn': '
|
|
46
|
-
'moonshotai-cn': '
|
|
47
|
-
zhipuai: '
|
|
46
|
+
'alibaba-cn': 'Alibaba Cloud Model Studio',
|
|
47
|
+
'moonshotai-cn': 'Moonshot AI Kimi',
|
|
48
|
+
zhipuai: 'Zhipu GLM',
|
|
48
49
|
groq: 'Groq',
|
|
49
50
|
xai: 'xAI',
|
|
50
51
|
lmstudio: 'LM Studio',
|
|
51
|
-
ollama: 'Ollama (
|
|
52
|
-
custom: '
|
|
52
|
+
ollama: 'Ollama (local)',
|
|
53
|
+
custom: 'Custom OpenAI-compatible',
|
|
53
54
|
};
|
|
54
55
|
|
|
55
56
|
/** sidecar 未就绪时仍能选常用服务商。 */
|
|
@@ -155,7 +156,8 @@ const LOCAL_VENDORS: VendorOption[] = [
|
|
|
155
156
|
];
|
|
156
157
|
|
|
157
158
|
export function vendorLabel(id: string): string {
|
|
158
|
-
|
|
159
|
+
const label = VENDOR_LABELS[id];
|
|
160
|
+
return label ? t(label) : id;
|
|
159
161
|
}
|
|
160
162
|
|
|
161
163
|
export function llmProviderFromWireKind(wireKind: string): LlmProvider {
|
|
@@ -190,7 +192,10 @@ export function mergeVendorOptions(catalog: CatalogProviderDescriptor[]): Vendor
|
|
|
190
192
|
featured: featured.has(row.id),
|
|
191
193
|
}));
|
|
192
194
|
const seen = new Set(fromCatalog.map((v) => v.id));
|
|
193
|
-
const extras = LOCAL_VENDORS.filter((v) => !seen.has(v.id))
|
|
195
|
+
const extras = LOCAL_VENDORS.filter((v) => !seen.has(v.id)).map((v) => ({
|
|
196
|
+
...v,
|
|
197
|
+
label: vendorLabel(v.id),
|
|
198
|
+
}));
|
|
194
199
|
const featuredOrder = [...FEATURED_VENDOR_IDS, 'ollama', 'custom'];
|
|
195
200
|
const rank = (id: string) => {
|
|
196
201
|
const idx = featuredOrder.indexOf(id);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { t } from '@/i18n';
|
|
1
2
|
import type { GatewayModelEntry } from '@/lib/local-api';
|
|
2
3
|
|
|
3
4
|
/**
|
|
@@ -18,17 +19,17 @@ export interface ModelCapabilityChip {
|
|
|
18
19
|
const MODALITY_ORDER = ['image', 'pdf', 'audio', 'video'] as const;
|
|
19
20
|
|
|
20
21
|
const MODALITY_LABELS: Record<(typeof MODALITY_ORDER)[number], string> = {
|
|
21
|
-
image: '
|
|
22
|
+
image: 'Image',
|
|
22
23
|
pdf: 'PDF',
|
|
23
|
-
audio: '
|
|
24
|
-
video: '
|
|
24
|
+
audio: 'Audio',
|
|
25
|
+
video: 'Video',
|
|
25
26
|
};
|
|
26
27
|
|
|
27
28
|
const MODALITY_TITLES: Record<(typeof MODALITY_ORDER)[number], string> = {
|
|
28
|
-
image: '
|
|
29
|
-
pdf: '
|
|
30
|
-
audio: '
|
|
31
|
-
video: '
|
|
29
|
+
image: 'Supports image input',
|
|
30
|
+
pdf: 'Supports PDF input',
|
|
31
|
+
audio: 'Supports audio input',
|
|
32
|
+
video: 'Supports video input',
|
|
32
33
|
};
|
|
33
34
|
|
|
34
35
|
/** 上下文窗口:1_000_000 → 1M,131_072 → 131K。 */
|
|
@@ -45,8 +46,8 @@ export function formatContextWindow(window: number | null | undefined): string |
|
|
|
45
46
|
function unknownChip(): ModelCapabilityChip {
|
|
46
47
|
return {
|
|
47
48
|
key: 'unknown',
|
|
48
|
-
label: '
|
|
49
|
-
title: '
|
|
49
|
+
label: t('Unrecognized'),
|
|
50
|
+
title: t('The gateway catalog has no match for this id. Thinking levels and multimodal input are unverified.'),
|
|
50
51
|
kind: 'unknown',
|
|
51
52
|
};
|
|
52
53
|
}
|
|
@@ -68,8 +69,8 @@ export function modelCapabilityChips(
|
|
|
68
69
|
const joined = levels.join(' / ');
|
|
69
70
|
chips.push({
|
|
70
71
|
key: 'reasoning',
|
|
71
|
-
label: options?.detail ?
|
|
72
|
-
title:
|
|
72
|
+
label: options?.detail ? t('Thinking ({levels})', { levels: joined }) : t('Thinking'),
|
|
73
|
+
title: t('Supported thinking levels: {levels}', { levels: joined }),
|
|
73
74
|
kind: 'reasoning',
|
|
74
75
|
});
|
|
75
76
|
}
|
|
@@ -80,8 +81,8 @@ export function modelCapabilityChips(
|
|
|
80
81
|
seen.add(modality);
|
|
81
82
|
chips.push({
|
|
82
83
|
key: modality,
|
|
83
|
-
label: MODALITY_LABELS[modality],
|
|
84
|
-
title: MODALITY_TITLES[modality],
|
|
84
|
+
label: t(MODALITY_LABELS[modality]),
|
|
85
|
+
title: t(MODALITY_TITLES[modality]),
|
|
85
86
|
kind: 'modality',
|
|
86
87
|
});
|
|
87
88
|
}
|
|
@@ -92,7 +93,7 @@ export function modelCapabilityChips(
|
|
|
92
93
|
chips.push({
|
|
93
94
|
key: key,
|
|
94
95
|
label: modality,
|
|
95
|
-
title:
|
|
96
|
+
title: t('Input modality: {modality}', { modality }),
|
|
96
97
|
kind: 'modality',
|
|
97
98
|
});
|
|
98
99
|
}
|
|
@@ -102,7 +103,7 @@ export function modelCapabilityChips(
|
|
|
102
103
|
chips.push({
|
|
103
104
|
key: 'window',
|
|
104
105
|
label: windowLabel,
|
|
105
|
-
title:
|
|
106
|
+
title: t('Context window {window} tokens', { window: entry.window ?? '' }),
|
|
106
107
|
kind: 'window',
|
|
107
108
|
});
|
|
108
109
|
}
|
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
* 载荷之间的转换,不硬编码任何厂商参数。
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { t } from '@/i18n';
|
|
8
9
|
import type {
|
|
9
10
|
ProviderPresetDescriptor,
|
|
10
11
|
ProviderPresetOverride,
|
|
@@ -28,12 +29,12 @@ export function overrideFromDescriptor(d: ProviderPresetDescriptor): ProviderPre
|
|
|
28
29
|
/** 选择器选项标签:`host + 前缀*`,缺省键名退化为「未命名」。 */
|
|
29
30
|
export function descriptorLabel(d: ProviderPresetDescriptor): string {
|
|
30
31
|
const parts = [d.host, d.modelPrefix ? `${d.modelPrefix}*` : null].filter(Boolean);
|
|
31
|
-
return parts.join(' + ') || '
|
|
32
|
+
return parts.join(' + ') || t('(Unnamed preset)');
|
|
32
33
|
}
|
|
33
34
|
|
|
34
35
|
/** 预制参数的一行摘要,用于生效预览。 */
|
|
35
36
|
export function summarizePreset(p: ProviderPresetOverride | null): string {
|
|
36
|
-
if (!p) return '
|
|
37
|
+
if (!p) return t('No extra sampling parameters are sent');
|
|
37
38
|
const parts: string[] = [];
|
|
38
39
|
if (p.temperature != null) parts.push(`temperature ${p.temperature}`);
|
|
39
40
|
if (p.topP != null) parts.push(`top_p ${p.topP}`);
|
|
@@ -42,7 +43,9 @@ export function summarizePreset(p: ProviderPresetOverride | null): string {
|
|
|
42
43
|
if (p.extraBody) {
|
|
43
44
|
for (const [k, v] of Object.entries(p.extraBody)) parts.push(`${k} ${JSON.stringify(v)}`);
|
|
44
45
|
}
|
|
45
|
-
return parts.length > 0
|
|
46
|
+
return parts.length > 0
|
|
47
|
+
? parts.join(' · ')
|
|
48
|
+
: t('No extra sampling parameters are sent (the best request for this vendor omits sampling fields)');
|
|
46
49
|
}
|
|
47
50
|
|
|
48
51
|
export function overridesEqual(a: ProviderPresetOverride, b: ProviderPresetOverride): boolean {
|
|
@@ -6,11 +6,11 @@
|
|
|
6
6
|
{
|
|
7
7
|
"id": "local-assistant",
|
|
8
8
|
"slug": "local-assistant",
|
|
9
|
-
"name": "
|
|
9
|
+
"name": "Computer operator",
|
|
10
10
|
"icon": "Cpu",
|
|
11
11
|
"color": "#4f46e5",
|
|
12
|
-
"description": "
|
|
13
|
-
"rolePrompt": "
|
|
12
|
+
"description": "Default local assistant. It can use the shell, files, and MCP tools.",
|
|
13
|
+
"rolePrompt": "You are **Computer operator**, a local offline assistant. Answer clearly and in steps that can be carried out.",
|
|
14
14
|
"forbiddenPrompt": null,
|
|
15
15
|
"skillIds": [],
|
|
16
16
|
"toolPolicy": {
|
|
@@ -27,11 +27,11 @@
|
|
|
27
27
|
{
|
|
28
28
|
"id": "data-analyst",
|
|
29
29
|
"slug": "data-analyst",
|
|
30
|
-
"name": "
|
|
30
|
+
"name": "Data analyst",
|
|
31
31
|
"icon": "Activity",
|
|
32
32
|
"color": "#0ea5e9",
|
|
33
|
-
"description": "
|
|
34
|
-
"rolePrompt": "
|
|
33
|
+
"description": "Works on local CSV and log files: cleaning, stats, charts, and a summary.",
|
|
34
|
+
"rolePrompt": "You are **Data analyst**. You clean, summarize, and chart local data files.\nBe concise and lead with the conclusion.\n\nWorking rules:\n1) Inspect the file structure before changing it;\n2) Say what each step did;\n3) Present results as a table or bullets.",
|
|
35
35
|
"forbiddenPrompt": null,
|
|
36
36
|
"skillIds": [],
|
|
37
37
|
"toolPolicy": {
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
{
|
|
49
49
|
"id": "all-round-assistant",
|
|
50
50
|
"slug": "all-round-assistant",
|
|
51
|
-
"name": "
|
|
51
|
+
"name": "Assistant",
|
|
52
52
|
"icon": "🔮",
|
|
53
53
|
"color": "#a855f7",
|
|
54
|
-
"description": "
|
|
55
|
-
"rolePrompt": "
|
|
54
|
+
"description": "General agent. It loads every installed local skill and tool, with no extra trigger.",
|
|
55
|
+
"rolePrompt": "You are **Assistant**, with full access to every installed local skill.\nYou can run local commands (local_exec_shell), read and write files, and analyze data.\nEvery installed local skill and tool is already in your system prompt.\nCall the most direct local tool or script for the task.",
|
|
56
56
|
"forbiddenPrompt": null,
|
|
57
57
|
"skillIds": [],
|
|
58
58
|
"toolPolicy": {
|
|
@@ -72,7 +72,7 @@
|
|
|
72
72
|
"id": "8286e3c8-2d83-409e-930a-3e99b2004d41",
|
|
73
73
|
"projectId": null,
|
|
74
74
|
"userId": "local",
|
|
75
|
-
"title": "
|
|
75
|
+
"title": "Hello",
|
|
76
76
|
"agentId": "local-assistant",
|
|
77
77
|
"createdAt": "2026-07-01T10:05:58.604Z",
|
|
78
78
|
"updatedAt": "2026-07-01T10:06:06.736Z",
|
|
@@ -84,7 +84,7 @@
|
|
|
84
84
|
"id": "8b13a92e-4b3e-4bac-885f-879ddd9e4835",
|
|
85
85
|
"projectId": null,
|
|
86
86
|
"userId": "local",
|
|
87
|
-
"title": "
|
|
87
|
+
"title": "Enable the csv-data-tools skill",
|
|
88
88
|
"agentId": "all-round-assistant",
|
|
89
89
|
"createdAt": "2026-06-22T04:30:54.662Z",
|
|
90
90
|
"updatedAt": "2026-06-22T04:32:12.657Z",
|
|
@@ -96,7 +96,7 @@
|
|
|
96
96
|
"id": "d536659c-6492-424d-97e8-2f701a9ce5d8",
|
|
97
97
|
"projectId": null,
|
|
98
98
|
"userId": "local",
|
|
99
|
-
"title": "
|
|
99
|
+
"title": "New chat",
|
|
100
100
|
"agentId": "all-round-assistant",
|
|
101
101
|
"createdAt": "2026-06-22T04:19:52.607Z",
|
|
102
102
|
"updatedAt": "2026-06-22T04:20:00.151Z",
|
|
@@ -108,7 +108,7 @@
|
|
|
108
108
|
"id": "4845b527-c9c9-467c-9d72-5fc5f8763aa5",
|
|
109
109
|
"projectId": null,
|
|
110
110
|
"userId": "local",
|
|
111
|
-
"title": "
|
|
111
|
+
"title": "New chat",
|
|
112
112
|
"agentId": "all-round-assistant",
|
|
113
113
|
"createdAt": "2026-06-22T04:12:17.151Z",
|
|
114
114
|
"updatedAt": "2026-06-22T04:12:22.038Z",
|
|
@@ -120,7 +120,7 @@
|
|
|
120
120
|
"id": "405f5964-f01c-4ece-9c8f-02a73d539469",
|
|
121
121
|
"projectId": null,
|
|
122
122
|
"userId": "local",
|
|
123
|
-
"title": "
|
|
123
|
+
"title": "Enable the csv-data-tools skill",
|
|
124
124
|
"agentId": "all-round-assistant",
|
|
125
125
|
"createdAt": "2026-06-22T04:06:21.745Z",
|
|
126
126
|
"updatedAt": "2026-06-22T04:11:42.782Z",
|
|
@@ -132,7 +132,7 @@
|
|
|
132
132
|
"id": "19a1d8e7-7e9c-425b-8509-0fe9dd292971",
|
|
133
133
|
"projectId": null,
|
|
134
134
|
"userId": "local",
|
|
135
|
-
"title": "
|
|
135
|
+
"title": "Enable the csv-data-tools skill",
|
|
136
136
|
"agentId": "all-round-assistant",
|
|
137
137
|
"createdAt": "2026-06-22T04:01:21.158Z",
|
|
138
138
|
"updatedAt": "2026-06-22T04:05:08.801Z",
|
|
@@ -144,7 +144,7 @@
|
|
|
144
144
|
"id": "492c1a80-85b2-486a-b365-3d668c9ad7e6",
|
|
145
145
|
"projectId": null,
|
|
146
146
|
"userId": "local",
|
|
147
|
-
"title": "
|
|
147
|
+
"title": "New chat",
|
|
148
148
|
"agentId": "all-round-assistant",
|
|
149
149
|
"createdAt": "2026-06-22T03:44:47.381Z",
|
|
150
150
|
"updatedAt": "2026-06-22T03:44:54.479Z",
|
|
@@ -171,7 +171,7 @@
|
|
|
171
171
|
"id": "ea36a5de-e2e0-428b-b882-f5e67ca326b0",
|
|
172
172
|
"chatId": "8286e3c8-2d83-409e-930a-3e99b2004d41",
|
|
173
173
|
"role": "assistant",
|
|
174
|
-
"content": "
|
|
174
|
+
"content": "Hello. I am the **local assistant**, running on this computer.\n\nI can help with:\n\n**Local terminal**\n- Run shell commands and read or write files\n- Search a codebase and replace in bulk\n\n**Data**\n- Clean CSV and log files\n- Build a summary\n\nType what you want done.",
|
|
175
175
|
"createdAt": "2026-07-01T10:06:06.733Z",
|
|
176
176
|
"messageMetadata": "{\"traceId\":\"tr_269aa1b0f5244962b3279304d6f26323\",\"completionStatus\":\"completed\",\"trace\":{\"traceId\":\"tr_269aa1b0f5244962b3279304d6f26323\",\"sessionId\":\"8286e3c8-2d83-409e-930a-3e99b2004d41\",\"chatId\":\"8286e3c8-2d83-409e-930a-3e99b2004d41\",\"startedAtMs\":1782900364396,\"eventCount\":3,\"spanCount\":1,\"events\":[{\"name\":\"stream_start\",\"timestampMs\":1782900364396,\"data\":{\"path\":\"/api/v2/chats/8286e3c8-2d83-409e-930a-3e99b2004d41/run\",\"method\":\"POST\"}},{\"name\":\"completion_decision\",\"timestampMs\":1782900366733,\"data\":{\"status\":\"completed\",\"reason\":\"assistant produced final response with no pending tools\",\"confidence\":0.85,\"evidence\":[{\"textLength\":218,\"toolCalls\":[],\"toolErrorCount\":0}]}},{\"name\":\"stream_complete\",\"timestampMs\":1782900366733,\"data\":{\"usedTool\":false,\"completionStatus\":\"completed\",\"assistantLength\":218}}],\"spans\":[{\"name\":\"llm_call\",\"stepId\":\"step_1\",\"startMs\":1782900364396,\"endMs\":1782900366733,\"status\":\"ok\",\"data\":{\"turn\":0,\"model\":\"default\",\"tokensUsed\":{\"prompt\":8596,\"completion\":130,\"total\":8726},\"toolCalls\":0},\"durationMs\":2337}]},\"executedActions\":[]}"
|
|
177
177
|
},
|
|
@@ -179,7 +179,7 @@
|
|
|
179
179
|
"id": "ab1387a6-5c43-4d4b-986d-073d98b3f5e2",
|
|
180
180
|
"chatId": "8286e3c8-2d83-409e-930a-3e99b2004d41",
|
|
181
181
|
"role": "user",
|
|
182
|
-
"content": "
|
|
182
|
+
"content": "Hello",
|
|
183
183
|
"createdAt": "2026-07-01T10:06:04.392Z",
|
|
184
184
|
"messageMetadata": null
|
|
185
185
|
}
|
|
@@ -189,7 +189,7 @@
|
|
|
189
189
|
"id": "6599eb84-515c-4f58-bec8-0f37fcfbb234",
|
|
190
190
|
"chatId": "8b13a92e-4b3e-4bac-885f-879ddd9e4835",
|
|
191
191
|
"role": "assistant",
|
|
192
|
-
"content": "
|
|
192
|
+
"content": "I'll look at the modules in `csv-data-tools`.\n\n**`csv-data-tools`** is ready:\n\n---\n\n## csv-data-tools\n\n- `csv_stats` — column stats (rows, missing values, distribution)\n- `csv_filter` — filter and export\n- `csv_plot` — a quick chart\n\nSend a file path to start.",
|
|
193
193
|
"createdAt": "2026-06-22T04:32:11.576Z",
|
|
194
194
|
"messageMetadata": null
|
|
195
195
|
},
|
|
@@ -227,7 +227,7 @@
|
|
|
227
227
|
"id": "c8dc9e96-642b-4232-8c3c-c8abc32a615c",
|
|
228
228
|
"chatId": "405f5964-f01c-4ece-9c8f-02a73d539469",
|
|
229
229
|
"role": "assistant",
|
|
230
|
-
"content": "
|
|
230
|
+
"content": "I'll check the MCP tools for `csv-data-tools`.\n\nIt is not registered as its own MCP server. I'll scan local scripts and executables instead.\n\nIt is not installed. Import it from the skills settings.",
|
|
231
231
|
"createdAt": "2026-06-22T04:11:42.077Z",
|
|
232
232
|
"messageMetadata": null
|
|
233
233
|
},
|
|
@@ -245,7 +245,7 @@
|
|
|
245
245
|
"id": "cfb0f03c-7faa-4594-b29f-f4c11fb09105",
|
|
246
246
|
"chatId": "19a1d8e7-7e9c-425b-8509-0fe9dd292971",
|
|
247
247
|
"role": "assistant",
|
|
248
|
-
"content": "
|
|
248
|
+
"content": "The local model call failed: could not reach the OpenAI-compatible gateway (https://api.deepseek.com). Check baseUrl and the network. Original error: fetch failed",
|
|
249
249
|
"createdAt": "2026-06-22T04:05:06.751Z",
|
|
250
250
|
"messageMetadata": null
|
|
251
251
|
},
|
|
@@ -273,7 +273,7 @@
|
|
|
273
273
|
"id": "c5d6136b-eaa4-4cc7-878b-450099b7d226",
|
|
274
274
|
"chatId": "4e997d62-20ef-4937-a866-27778dee3a17",
|
|
275
275
|
"role": "assistant",
|
|
276
|
-
"content": "
|
|
276
|
+
"content": "I'll check the MCP tools for `csv-data-tools`.\n\nIt is not registered as its own MCP server. I'll scan local scripts and executables instead.\n\nIt is not installed. Import it from the skills settings.",
|
|
277
277
|
"createdAt": "2026-06-22T03:37:37.601Z",
|
|
278
278
|
"messageMetadata": null
|
|
279
279
|
},
|
|
@@ -289,7 +289,7 @@
|
|
|
289
289
|
"id": "fa89030e-5db1-4d04-9320-e796baeb89e2",
|
|
290
290
|
"chatId": "4e997d62-20ef-4937-a866-27778dee3a17",
|
|
291
291
|
"role": "assistant",
|
|
292
|
-
"content": "
|
|
292
|
+
"content": "Hello. I am the **local assistant**, running on this computer.\n\nI can help with:\n\n**Local terminal**\n- Run shell commands and read or write files\n- Search a codebase and replace in bulk\n\n**Data**\n- Clean CSV and log files\n- Build a summary\n\nType what you want done.",
|
|
293
293
|
"createdAt": "2026-06-22T03:34:48.778Z",
|
|
294
294
|
"messageMetadata": "{\"traceId\":\"tr_f53c2142bbd347b5888f9d50c5444478\",\"completionStatus\":\"completed\",\"trace\":{\"traceId\":\"tr_f53c2142bbd347b5888f9d50c5444478\",\"sessionId\":\"4e997d62-20ef-4937-a866-27778dee3a17\",\"chatId\":\"4e997d62-20ef-4937-a866-27778dee3a17\",\"startedAtMs\":1782099286405,\"eventCount\":3,\"spanCount\":1,\"events\":[{\"name\":\"stream_start\",\"timestampMs\":1782099286405,\"data\":{\"path\":\"/api/v2/chats/4e997d62-20ef-4937-a866-27778dee3a17/run\",\"method\":\"POST\"}},{\"name\":\"completion_decision\",\"timestampMs\":1782099288778,\"data\":{\"status\":\"completed\",\"reason\":\"assistant produced final response with no pending tools\",\"confidence\":0.85,\"evidence\":[{\"textLength\":224,\"toolCalls\":[],\"toolErrorCount\":0}]}},{\"name\":\"stream_complete\",\"timestampMs\":1782099288778,\"data\":{\"usedTool\":false,\"completionStatus\":\"completed\",\"assistantLength\":224}}],\"spans\":[{\"name\":\"llm_call\",\"stepId\":\"step_1\",\"startMs\":1782099286405,\"endMs\":1782099288777,\"status\":\"ok\",\"data\":{\"turn\":0,\"model\":\"default\",\"tokensUsed\":{\"prompt\":2234,\"completion\":130,\"total\":2364},\"toolCalls\":0},\"durationMs\":2372}]},\"executedActions\":[]}"
|
|
295
295
|
},
|
|
@@ -309,7 +309,7 @@
|
|
|
309
309
|
"baseUrl": "https://api.deepseek.com",
|
|
310
310
|
"apiKey": "",
|
|
311
311
|
"temperature": 0.3,
|
|
312
|
-
"systemPrompt": "
|
|
312
|
+
"systemPrompt": "You are a local assistant. Prefer advice that can be carried out, and call local tools when that helps."
|
|
313
313
|
},
|
|
314
314
|
"skills": []
|
|
315
315
|
}
|