@clappstore/connect 0.7.7 → 0.7.9
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/dist/agent-client.d.ts +6 -0
- package/dist/agent-client.d.ts.map +1 -1
- package/dist/agent-client.js +102 -13
- package/dist/agent-client.js.map +1 -1
- package/dist/auth.d.ts +18 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +248 -0
- package/dist/auth.js.map +1 -0
- package/dist/chat-handler.d.ts +52 -0
- package/dist/chat-handler.d.ts.map +1 -0
- package/dist/chat-handler.js +453 -0
- package/dist/chat-handler.js.map +1 -0
- package/dist/defaults.d.ts +1 -1
- package/dist/defaults.d.ts.map +1 -1
- package/dist/defaults.js +36 -23
- package/dist/defaults.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +53 -30
- package/dist/index.js.map +1 -1
- package/dist/server.d.ts +1 -0
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +88 -7
- package/dist/server.js.map +1 -1
- package/dist/settings-handler.d.ts +76 -0
- package/dist/settings-handler.d.ts.map +1 -0
- package/dist/settings-handler.js +848 -0
- package/dist/settings-handler.js.map +1 -0
- package/package.json +4 -8
- package/web-app/assets/{index-CEpgiIwf.js → index-CWzlxjUK.js} +86 -56
- package/web-app/assets/index-Cic64hbc.css +1 -0
- package/web-app/index.html +2 -2
- package/clapps/settings/README.md +0 -74
- package/clapps/settings/clapp.json +0 -25
- package/clapps/settings/components/ProviderEditor.tsx +0 -512
- package/clapps/settings/components/ProviderList.tsx +0 -300
- package/clapps/settings/components/SessionList.tsx +0 -189
- package/clapps/settings/handlers/settings-handler.js +0 -742
- package/clapps/settings/views/default.settings.view.md +0 -35
- package/clapps/settings/views/settings.app.md +0 -12
- package/web-app/assets/index-BsI5PEAv.css +0 -1
|
@@ -1,512 +0,0 @@
|
|
|
1
|
-
import { useState, useEffect } from "react";
|
|
2
|
-
import { useIntent } from "@clapps/renderer";
|
|
3
|
-
import { cn } from "@/lib/utils";
|
|
4
|
-
import { X, Eye, EyeOff, Loader2, ExternalLink } from "lucide-react";
|
|
5
|
-
import { Button } from "@/components/ui/button";
|
|
6
|
-
|
|
7
|
-
export type ProviderType = "anthropic" | "openai" | "kimi-coding";
|
|
8
|
-
export type AuthMode = "api-key" | "subscription";
|
|
9
|
-
|
|
10
|
-
interface ProviderConfig {
|
|
11
|
-
type: ProviderType;
|
|
12
|
-
name: string;
|
|
13
|
-
description: string;
|
|
14
|
-
authModes: {
|
|
15
|
-
mode: AuthMode;
|
|
16
|
-
label: string;
|
|
17
|
-
description: string;
|
|
18
|
-
fields: FieldConfig[];
|
|
19
|
-
helpUrl?: string;
|
|
20
|
-
}[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
interface FieldConfig {
|
|
24
|
-
name: string;
|
|
25
|
-
label: string;
|
|
26
|
-
type: "text" | "password" | "textarea";
|
|
27
|
-
placeholder?: string;
|
|
28
|
-
helpText?: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const PROVIDER_CONFIGS: ProviderConfig[] = [
|
|
32
|
-
{
|
|
33
|
-
type: "anthropic",
|
|
34
|
-
name: "Anthropic",
|
|
35
|
-
description: "Claude models via API or subscription",
|
|
36
|
-
authModes: [
|
|
37
|
-
{
|
|
38
|
-
mode: "api-key",
|
|
39
|
-
label: "API Key",
|
|
40
|
-
description: "Use your Anthropic API key (usage-based billing)",
|
|
41
|
-
helpUrl: "https://console.anthropic.com/settings/keys",
|
|
42
|
-
fields: [
|
|
43
|
-
{
|
|
44
|
-
name: "apiKey",
|
|
45
|
-
label: "API Key",
|
|
46
|
-
type: "password",
|
|
47
|
-
placeholder: "sk-ant-api03-...",
|
|
48
|
-
helpText: "Get your API key from the Anthropic Console",
|
|
49
|
-
},
|
|
50
|
-
],
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
mode: "subscription",
|
|
54
|
-
label: "Claude Subscription",
|
|
55
|
-
description: "Use your Claude Pro/Max subscription",
|
|
56
|
-
helpUrl: "https://docs.openclaw.ai/providers/anthropic#option-b-claude-setup-token",
|
|
57
|
-
fields: [
|
|
58
|
-
{
|
|
59
|
-
name: "setupToken",
|
|
60
|
-
label: "Setup Token",
|
|
61
|
-
type: "textarea",
|
|
62
|
-
placeholder: "Paste your setup token here...",
|
|
63
|
-
helpText: "Run 'claude setup-token' in your terminal to generate this",
|
|
64
|
-
},
|
|
65
|
-
],
|
|
66
|
-
},
|
|
67
|
-
],
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
type: "openai",
|
|
71
|
-
name: "OpenAI",
|
|
72
|
-
description: "GPT models via API or Codex subscription",
|
|
73
|
-
authModes: [
|
|
74
|
-
{
|
|
75
|
-
mode: "api-key",
|
|
76
|
-
label: "API Key",
|
|
77
|
-
description: "Use your OpenAI API key (usage-based billing)",
|
|
78
|
-
helpUrl: "https://platform.openai.com/api-keys",
|
|
79
|
-
fields: [
|
|
80
|
-
{
|
|
81
|
-
name: "apiKey",
|
|
82
|
-
label: "API Key",
|
|
83
|
-
type: "password",
|
|
84
|
-
placeholder: "sk-...",
|
|
85
|
-
helpText: "Get your API key from the OpenAI dashboard",
|
|
86
|
-
},
|
|
87
|
-
],
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
mode: "subscription",
|
|
91
|
-
label: "Codex Subscription",
|
|
92
|
-
description: "Use your ChatGPT Plus/Pro subscription via Codex",
|
|
93
|
-
helpUrl: "https://docs.openclaw.ai/providers/openai#option-b-openai-code-codex-subscription",
|
|
94
|
-
fields: [], // OAuth flow - no fields, just a button
|
|
95
|
-
},
|
|
96
|
-
],
|
|
97
|
-
},
|
|
98
|
-
{
|
|
99
|
-
type: "kimi-coding",
|
|
100
|
-
name: "Kimi Coding",
|
|
101
|
-
description: "Moonshot's Kimi K2 models for coding",
|
|
102
|
-
authModes: [
|
|
103
|
-
{
|
|
104
|
-
mode: "api-key",
|
|
105
|
-
label: "API Key",
|
|
106
|
-
description: "Use your Kimi Coding API key",
|
|
107
|
-
helpUrl: "https://platform.moonshot.cn/console/api-keys",
|
|
108
|
-
fields: [
|
|
109
|
-
{
|
|
110
|
-
name: "apiKey",
|
|
111
|
-
label: "API Key",
|
|
112
|
-
type: "password",
|
|
113
|
-
placeholder: "sk-...",
|
|
114
|
-
helpText: "Get your API key from the Moonshot platform",
|
|
115
|
-
},
|
|
116
|
-
],
|
|
117
|
-
},
|
|
118
|
-
],
|
|
119
|
-
},
|
|
120
|
-
];
|
|
121
|
-
|
|
122
|
-
interface ProviderEditorProps {
|
|
123
|
-
isOpen: boolean;
|
|
124
|
-
onClose: () => void;
|
|
125
|
-
editingProvider?: {
|
|
126
|
-
id: string;
|
|
127
|
-
name: string;
|
|
128
|
-
type: ProviderType;
|
|
129
|
-
mode: string;
|
|
130
|
-
authType?: "api-key" | "subscription" | "oauth";
|
|
131
|
-
maskedCredential?: string;
|
|
132
|
-
} | null;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
export function ProviderEditor({ isOpen, onClose, editingProvider }: ProviderEditorProps) {
|
|
136
|
-
const { emit } = useIntent();
|
|
137
|
-
|
|
138
|
-
const [step, setStep] = useState<"select" | "configure">("select");
|
|
139
|
-
const [selectedProvider, setSelectedProvider] = useState<ProviderType | null>(null);
|
|
140
|
-
const [selectedAuthMode, setSelectedAuthMode] = useState<AuthMode | null>(null);
|
|
141
|
-
const [customName, setCustomName] = useState("");
|
|
142
|
-
const [fieldValues, setFieldValues] = useState<Record<string, string>>({});
|
|
143
|
-
const [showPassword, setShowPassword] = useState<Record<string, boolean>>({});
|
|
144
|
-
const [isSaving, setIsSaving] = useState(false);
|
|
145
|
-
const [error, setError] = useState<string | null>(null);
|
|
146
|
-
|
|
147
|
-
// Reset state when modal opens/closes
|
|
148
|
-
useEffect(() => {
|
|
149
|
-
if (isOpen) {
|
|
150
|
-
if (editingProvider) {
|
|
151
|
-
// Editing existing provider
|
|
152
|
-
setSelectedProvider(editingProvider.type);
|
|
153
|
-
// Use authType if available, otherwise fall back to mode-based detection
|
|
154
|
-
const authMode: AuthMode = editingProvider.authType === "subscription" || editingProvider.authType === "oauth"
|
|
155
|
-
? "subscription"
|
|
156
|
-
: editingProvider.mode === "oauth"
|
|
157
|
-
? "subscription"
|
|
158
|
-
: "api-key";
|
|
159
|
-
setSelectedAuthMode(authMode);
|
|
160
|
-
setCustomName(editingProvider.name);
|
|
161
|
-
setStep("configure");
|
|
162
|
-
} else {
|
|
163
|
-
// Adding new provider
|
|
164
|
-
setStep("select");
|
|
165
|
-
setSelectedProvider(null);
|
|
166
|
-
setSelectedAuthMode(null);
|
|
167
|
-
setCustomName("");
|
|
168
|
-
}
|
|
169
|
-
setFieldValues({});
|
|
170
|
-
setShowPassword({});
|
|
171
|
-
setError(null);
|
|
172
|
-
setIsSaving(false);
|
|
173
|
-
}
|
|
174
|
-
}, [isOpen, editingProvider]);
|
|
175
|
-
|
|
176
|
-
const providerConfig = selectedProvider
|
|
177
|
-
? PROVIDER_CONFIGS.find(p => p.type === selectedProvider)
|
|
178
|
-
: null;
|
|
179
|
-
|
|
180
|
-
const authModeConfig = providerConfig && selectedAuthMode
|
|
181
|
-
? providerConfig.authModes.find(a => a.mode === selectedAuthMode)
|
|
182
|
-
: null;
|
|
183
|
-
|
|
184
|
-
const handleProviderSelect = (type: ProviderType) => {
|
|
185
|
-
setSelectedProvider(type);
|
|
186
|
-
const config = PROVIDER_CONFIGS.find(p => p.type === type);
|
|
187
|
-
if (config) {
|
|
188
|
-
// Auto-select first auth mode
|
|
189
|
-
setSelectedAuthMode(config.authModes[0].mode);
|
|
190
|
-
// Set default name
|
|
191
|
-
setCustomName(config.name);
|
|
192
|
-
}
|
|
193
|
-
setStep("configure");
|
|
194
|
-
};
|
|
195
|
-
|
|
196
|
-
const handleFieldChange = (fieldName: string, value: string) => {
|
|
197
|
-
setFieldValues(prev => ({ ...prev, [fieldName]: value }));
|
|
198
|
-
setError(null);
|
|
199
|
-
};
|
|
200
|
-
|
|
201
|
-
const togglePasswordVisibility = (fieldName: string) => {
|
|
202
|
-
setShowPassword(prev => ({ ...prev, [fieldName]: !prev[fieldName] }));
|
|
203
|
-
};
|
|
204
|
-
|
|
205
|
-
const handleSave = async () => {
|
|
206
|
-
if (!selectedProvider || !selectedAuthMode || !providerConfig) return;
|
|
207
|
-
|
|
208
|
-
// Validate required fields
|
|
209
|
-
if (authModeConfig?.fields.length) {
|
|
210
|
-
for (const field of authModeConfig.fields) {
|
|
211
|
-
if (!fieldValues[field.name]?.trim()) {
|
|
212
|
-
setError(`${field.label} is required`);
|
|
213
|
-
return;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
setIsSaving(true);
|
|
219
|
-
setError(null);
|
|
220
|
-
|
|
221
|
-
try {
|
|
222
|
-
// Determine the intent based on provider and auth mode
|
|
223
|
-
const intentName = getIntentName(selectedProvider, selectedAuthMode);
|
|
224
|
-
const payload: Record<string, unknown> = {
|
|
225
|
-
customName: customName.trim() || providerConfig.name,
|
|
226
|
-
profileId: editingProvider?.id,
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
// Add field values to payload
|
|
230
|
-
for (const field of authModeConfig?.fields ?? []) {
|
|
231
|
-
payload[field.name] = fieldValues[field.name]?.trim();
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
emit(intentName, payload);
|
|
235
|
-
|
|
236
|
-
// Wait a bit for the handler to process
|
|
237
|
-
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
238
|
-
|
|
239
|
-
onClose();
|
|
240
|
-
} catch (err) {
|
|
241
|
-
setError(err instanceof Error ? err.message : "Failed to save provider");
|
|
242
|
-
} finally {
|
|
243
|
-
setIsSaving(false);
|
|
244
|
-
}
|
|
245
|
-
};
|
|
246
|
-
|
|
247
|
-
const handleOAuthLogin = () => {
|
|
248
|
-
if (!selectedProvider) return;
|
|
249
|
-
|
|
250
|
-
setIsSaving(true);
|
|
251
|
-
emit("settings.startOAuth", {
|
|
252
|
-
provider: selectedProvider === "openai" ? "openai-codex" : selectedProvider,
|
|
253
|
-
customName: customName.trim() || providerConfig?.name,
|
|
254
|
-
});
|
|
255
|
-
|
|
256
|
-
// OAuth will redirect, so just show loading
|
|
257
|
-
};
|
|
258
|
-
|
|
259
|
-
const handleDelete = () => {
|
|
260
|
-
if (!editingProvider) return;
|
|
261
|
-
|
|
262
|
-
if (confirm(`Delete "${editingProvider.name}"? This cannot be undone.`)) {
|
|
263
|
-
emit("settings.deleteProvider", { profileId: editingProvider.id });
|
|
264
|
-
onClose();
|
|
265
|
-
}
|
|
266
|
-
};
|
|
267
|
-
|
|
268
|
-
if (!isOpen) return null;
|
|
269
|
-
|
|
270
|
-
return (
|
|
271
|
-
<div className="fixed inset-0 z-50 flex items-end sm:items-center justify-center">
|
|
272
|
-
{/* Backdrop */}
|
|
273
|
-
<div
|
|
274
|
-
className="absolute inset-0 bg-black/50 backdrop-blur-sm"
|
|
275
|
-
onClick={onClose}
|
|
276
|
-
/>
|
|
277
|
-
|
|
278
|
-
{/* Modal */}
|
|
279
|
-
<div className="relative w-full sm:max-w-md bg-background rounded-t-2xl sm:rounded-2xl shadow-xl max-h-[90vh] overflow-hidden flex flex-col animate-in slide-in-from-bottom-4 sm:slide-in-from-bottom-0 sm:zoom-in-95">
|
|
280
|
-
{/* Header */}
|
|
281
|
-
<div className="flex items-center justify-between px-4 py-3 border-b border-border shrink-0">
|
|
282
|
-
<h2 className="text-lg font-semibold">
|
|
283
|
-
{editingProvider ? "Edit Provider" : "Add Provider"}
|
|
284
|
-
</h2>
|
|
285
|
-
<button
|
|
286
|
-
onClick={onClose}
|
|
287
|
-
className="p-1 rounded-full hover:bg-muted transition-colors"
|
|
288
|
-
aria-label="Close"
|
|
289
|
-
>
|
|
290
|
-
<X className="h-5 w-5" />
|
|
291
|
-
</button>
|
|
292
|
-
</div>
|
|
293
|
-
|
|
294
|
-
{/* Content */}
|
|
295
|
-
<div className="flex-1 overflow-y-auto p-4">
|
|
296
|
-
{step === "select" && (
|
|
297
|
-
<div className="space-y-3">
|
|
298
|
-
<p className="text-sm text-muted-foreground mb-4">
|
|
299
|
-
Choose a provider to add:
|
|
300
|
-
</p>
|
|
301
|
-
{PROVIDER_CONFIGS.map(provider => (
|
|
302
|
-
<button
|
|
303
|
-
key={provider.type}
|
|
304
|
-
onClick={() => handleProviderSelect(provider.type)}
|
|
305
|
-
className="w-full text-left p-4 rounded-lg border border-border hover:border-primary hover:bg-muted/50 transition-colors"
|
|
306
|
-
>
|
|
307
|
-
<div className="font-medium">{provider.name}</div>
|
|
308
|
-
<div className="text-sm text-muted-foreground">{provider.description}</div>
|
|
309
|
-
</button>
|
|
310
|
-
))}
|
|
311
|
-
</div>
|
|
312
|
-
)}
|
|
313
|
-
|
|
314
|
-
{step === "configure" && providerConfig && (
|
|
315
|
-
<div className="space-y-4">
|
|
316
|
-
{/* Provider name input */}
|
|
317
|
-
<div className="space-y-2">
|
|
318
|
-
<label className="text-sm font-medium">Display Name</label>
|
|
319
|
-
<input
|
|
320
|
-
type="text"
|
|
321
|
-
value={customName}
|
|
322
|
-
onChange={(e) => setCustomName(e.target.value)}
|
|
323
|
-
placeholder={providerConfig.name}
|
|
324
|
-
className="w-full h-10 px-3 rounded-md border border-input bg-background text-sm focus:outline-none focus:ring-2 focus:ring-primary"
|
|
325
|
-
/>
|
|
326
|
-
<p className="text-xs text-muted-foreground">
|
|
327
|
-
Give this configuration a custom name (e.g., "Work Account", "Personal")
|
|
328
|
-
</p>
|
|
329
|
-
</div>
|
|
330
|
-
|
|
331
|
-
{/* Auth mode selector (if multiple options) */}
|
|
332
|
-
{providerConfig.authModes.length > 1 && (
|
|
333
|
-
<div className="space-y-2">
|
|
334
|
-
<label className="text-sm font-medium">Authentication Method</label>
|
|
335
|
-
<div className="grid grid-cols-2 gap-2">
|
|
336
|
-
{providerConfig.authModes.map(authMode => (
|
|
337
|
-
<button
|
|
338
|
-
key={authMode.mode}
|
|
339
|
-
onClick={() => setSelectedAuthMode(authMode.mode)}
|
|
340
|
-
className={cn(
|
|
341
|
-
"p-3 rounded-lg border text-left transition-colors",
|
|
342
|
-
selectedAuthMode === authMode.mode
|
|
343
|
-
? "border-primary bg-primary/10"
|
|
344
|
-
: "border-border hover:border-muted-foreground"
|
|
345
|
-
)}
|
|
346
|
-
>
|
|
347
|
-
<div className="text-sm font-medium">{authMode.label}</div>
|
|
348
|
-
<div className="text-xs text-muted-foreground mt-0.5">
|
|
349
|
-
{authMode.description}
|
|
350
|
-
</div>
|
|
351
|
-
</button>
|
|
352
|
-
))}
|
|
353
|
-
</div>
|
|
354
|
-
</div>
|
|
355
|
-
)}
|
|
356
|
-
|
|
357
|
-
{/* Auth fields */}
|
|
358
|
-
{authModeConfig && (
|
|
359
|
-
<div className="space-y-4">
|
|
360
|
-
{authModeConfig.fields.length > 0 ? (
|
|
361
|
-
authModeConfig.fields.map(field => (
|
|
362
|
-
<div key={field.name} className="space-y-2">
|
|
363
|
-
<label className="text-sm font-medium">{field.label}</label>
|
|
364
|
-
{/* Show current credential when editing */}
|
|
365
|
-
{editingProvider?.maskedCredential && !fieldValues[field.name] && (
|
|
366
|
-
<div className="text-xs text-muted-foreground bg-muted/50 px-2 py-1 rounded mb-1">
|
|
367
|
-
Current: {editingProvider.maskedCredential}
|
|
368
|
-
</div>
|
|
369
|
-
)}
|
|
370
|
-
{field.type === "textarea" ? (
|
|
371
|
-
<textarea
|
|
372
|
-
value={fieldValues[field.name] ?? ""}
|
|
373
|
-
onChange={(e) => handleFieldChange(field.name, e.target.value)}
|
|
374
|
-
placeholder={editingProvider ? "Enter new value to replace..." : field.placeholder}
|
|
375
|
-
rows={4}
|
|
376
|
-
className="w-full px-3 py-2 rounded-md border border-input bg-background text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary resize-none"
|
|
377
|
-
/>
|
|
378
|
-
) : (
|
|
379
|
-
<div className="relative">
|
|
380
|
-
<input
|
|
381
|
-
type={field.type === "password" && !showPassword[field.name] ? "password" : "text"}
|
|
382
|
-
value={fieldValues[field.name] ?? ""}
|
|
383
|
-
onChange={(e) => handleFieldChange(field.name, e.target.value)}
|
|
384
|
-
placeholder={editingProvider ? "Enter new value to replace..." : field.placeholder}
|
|
385
|
-
className="w-full h-10 px-3 pr-10 rounded-md border border-input bg-background text-sm font-mono focus:outline-none focus:ring-2 focus:ring-primary"
|
|
386
|
-
/>
|
|
387
|
-
{field.type === "password" && (
|
|
388
|
-
<button
|
|
389
|
-
type="button"
|
|
390
|
-
onClick={() => togglePasswordVisibility(field.name)}
|
|
391
|
-
className="absolute right-2 top-1/2 -translate-y-1/2 p-1 text-muted-foreground hover:text-foreground"
|
|
392
|
-
>
|
|
393
|
-
{showPassword[field.name] ? (
|
|
394
|
-
<EyeOff className="h-4 w-4" />
|
|
395
|
-
) : (
|
|
396
|
-
<Eye className="h-4 w-4" />
|
|
397
|
-
)}
|
|
398
|
-
</button>
|
|
399
|
-
)}
|
|
400
|
-
</div>
|
|
401
|
-
)}
|
|
402
|
-
{field.helpText && (
|
|
403
|
-
<p className="text-xs text-muted-foreground">{field.helpText}</p>
|
|
404
|
-
)}
|
|
405
|
-
</div>
|
|
406
|
-
))
|
|
407
|
-
) : (
|
|
408
|
-
/* OAuth flow - show connect button */
|
|
409
|
-
<div className="space-y-3">
|
|
410
|
-
<p className="text-sm text-muted-foreground">
|
|
411
|
-
Click the button below to sign in with your {providerConfig.name} account.
|
|
412
|
-
</p>
|
|
413
|
-
<Button
|
|
414
|
-
onClick={handleOAuthLogin}
|
|
415
|
-
disabled={isSaving}
|
|
416
|
-
className="w-full"
|
|
417
|
-
>
|
|
418
|
-
{isSaving ? (
|
|
419
|
-
<>
|
|
420
|
-
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
|
421
|
-
Connecting...
|
|
422
|
-
</>
|
|
423
|
-
) : (
|
|
424
|
-
`Sign in with ${providerConfig.name}`
|
|
425
|
-
)}
|
|
426
|
-
</Button>
|
|
427
|
-
</div>
|
|
428
|
-
)}
|
|
429
|
-
|
|
430
|
-
{/* Help link */}
|
|
431
|
-
{authModeConfig.helpUrl && (
|
|
432
|
-
<a
|
|
433
|
-
href={authModeConfig.helpUrl}
|
|
434
|
-
target="_blank"
|
|
435
|
-
rel="noopener noreferrer"
|
|
436
|
-
className="inline-flex items-center gap-1 text-xs text-primary hover:underline"
|
|
437
|
-
>
|
|
438
|
-
Learn more
|
|
439
|
-
<ExternalLink className="h-3 w-3" />
|
|
440
|
-
</a>
|
|
441
|
-
)}
|
|
442
|
-
</div>
|
|
443
|
-
)}
|
|
444
|
-
|
|
445
|
-
{/* Error message */}
|
|
446
|
-
{error && (
|
|
447
|
-
<div className="p-3 rounded-md bg-destructive/10 text-destructive text-sm">
|
|
448
|
-
{error}
|
|
449
|
-
</div>
|
|
450
|
-
)}
|
|
451
|
-
</div>
|
|
452
|
-
)}
|
|
453
|
-
</div>
|
|
454
|
-
|
|
455
|
-
{/* Footer */}
|
|
456
|
-
{step === "configure" && authModeConfig?.fields.length !== 0 && (
|
|
457
|
-
<div className="flex items-center gap-2 px-4 py-3 border-t border-border shrink-0">
|
|
458
|
-
{editingProvider && (
|
|
459
|
-
<Button
|
|
460
|
-
variant="destructive"
|
|
461
|
-
onClick={handleDelete}
|
|
462
|
-
disabled={isSaving}
|
|
463
|
-
className="mr-auto"
|
|
464
|
-
>
|
|
465
|
-
Delete
|
|
466
|
-
</Button>
|
|
467
|
-
)}
|
|
468
|
-
<Button
|
|
469
|
-
variant="outline"
|
|
470
|
-
onClick={() => editingProvider ? onClose() : setStep("select")}
|
|
471
|
-
disabled={isSaving}
|
|
472
|
-
>
|
|
473
|
-
{editingProvider ? "Cancel" : "Back"}
|
|
474
|
-
</Button>
|
|
475
|
-
<Button
|
|
476
|
-
onClick={handleSave}
|
|
477
|
-
disabled={isSaving}
|
|
478
|
-
>
|
|
479
|
-
{isSaving ? (
|
|
480
|
-
<>
|
|
481
|
-
<Loader2 className="h-4 w-4 animate-spin mr-2" />
|
|
482
|
-
Saving...
|
|
483
|
-
</>
|
|
484
|
-
) : (
|
|
485
|
-
editingProvider ? "Update" : "Add Provider"
|
|
486
|
-
)}
|
|
487
|
-
</Button>
|
|
488
|
-
</div>
|
|
489
|
-
)}
|
|
490
|
-
</div>
|
|
491
|
-
</div>
|
|
492
|
-
);
|
|
493
|
-
}
|
|
494
|
-
|
|
495
|
-
function getIntentName(provider: ProviderType, authMode: AuthMode): string {
|
|
496
|
-
const map: Record<string, Record<AuthMode, string>> = {
|
|
497
|
-
anthropic: {
|
|
498
|
-
"api-key": "settings.setAnthropicKey",
|
|
499
|
-
subscription: "settings.setClaudeToken",
|
|
500
|
-
},
|
|
501
|
-
openai: {
|
|
502
|
-
"api-key": "settings.setOpenAIKey",
|
|
503
|
-
subscription: "settings.startOAuth", // Handled separately
|
|
504
|
-
},
|
|
505
|
-
"kimi-coding": {
|
|
506
|
-
"api-key": "settings.setKimiCodingKey",
|
|
507
|
-
subscription: "settings.setKimiCodingKey", // No subscription mode
|
|
508
|
-
},
|
|
509
|
-
};
|
|
510
|
-
|
|
511
|
-
return map[provider]?.[authMode] ?? "settings.addProvider";
|
|
512
|
-
}
|