@clappstore/connect 0.7.6 → 0.7.8
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 +109 -18
- 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.map +1 -1
- package/dist/defaults.js +16 -11
- 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 -760
- 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,300 +0,0 @@
|
|
|
1
|
-
import { useEffect, useMemo, useState } from "react";
|
|
2
|
-
import { useClappState, useIntent } from "@clapps/renderer";
|
|
3
|
-
import { cn } from "@/lib/utils";
|
|
4
|
-
import { Check, Loader2, Pencil, Plus, ChevronDown } from "lucide-react";
|
|
5
|
-
import { ProviderEditor, type ProviderType } from "./ProviderEditor";
|
|
6
|
-
|
|
7
|
-
interface ModelOption {
|
|
8
|
-
id: string;
|
|
9
|
-
label: string;
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
interface Provider {
|
|
13
|
-
id: string;
|
|
14
|
-
name: string;
|
|
15
|
-
configured: boolean;
|
|
16
|
-
mode: string;
|
|
17
|
-
authType?: "api-key" | "subscription" | "oauth";
|
|
18
|
-
maskedCredential: string;
|
|
19
|
-
active?: boolean;
|
|
20
|
-
models?: ModelOption[];
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function getProviderIdentifier(profileId: string): string {
|
|
24
|
-
const [, identifier] = profileId.split(":");
|
|
25
|
-
return identifier || profileId;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function providerOptionLabel(provider: Provider): string {
|
|
29
|
-
return `${provider.name} · ${getProviderIdentifier(provider.id)}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
interface ProviderListProps {
|
|
33
|
-
data?: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function ProviderList({ data = "configuredProviders" }: ProviderListProps) {
|
|
37
|
-
const providers = useClappState<Provider[]>(data) ?? [];
|
|
38
|
-
const activeModel = useClappState<string>("active.model") ?? "";
|
|
39
|
-
const { emit } = useIntent();
|
|
40
|
-
|
|
41
|
-
const activeProvider = providers.find((p) => p.active) ?? providers[0];
|
|
42
|
-
|
|
43
|
-
const [selectedProviderId, setSelectedProviderId] = useState<string>(activeProvider?.id ?? "");
|
|
44
|
-
const [selectedModelId, setSelectedModelId] = useState<string>(activeModel ?? "");
|
|
45
|
-
const [isSaving, setIsSaving] = useState(false);
|
|
46
|
-
const [isSlow, setIsSlow] = useState(false);
|
|
47
|
-
|
|
48
|
-
// Editor modal state
|
|
49
|
-
const [isEditorOpen, setIsEditorOpen] = useState(false);
|
|
50
|
-
const [editingProvider, setEditingProvider] = useState<{
|
|
51
|
-
id: string;
|
|
52
|
-
name: string;
|
|
53
|
-
type: ProviderType;
|
|
54
|
-
mode: string;
|
|
55
|
-
} | null>(null);
|
|
56
|
-
|
|
57
|
-
const isDirty =
|
|
58
|
-
selectedProviderId !== (activeProvider?.id ?? "") ||
|
|
59
|
-
selectedModelId !== (activeModel ?? "");
|
|
60
|
-
|
|
61
|
-
useEffect(() => {
|
|
62
|
-
if (!activeProvider) return;
|
|
63
|
-
// Only hydrate from backend when there's no local selection yet, or right after save.
|
|
64
|
-
// This prevents polling/state refresh from clobbering in-progress user selections.
|
|
65
|
-
if (!selectedProviderId || isSaving) {
|
|
66
|
-
setSelectedProviderId(activeProvider.id);
|
|
67
|
-
}
|
|
68
|
-
}, [activeProvider, isSaving, selectedProviderId]);
|
|
69
|
-
|
|
70
|
-
useEffect(() => {
|
|
71
|
-
if (!activeModel) return;
|
|
72
|
-
// Keep local selection stable while user is editing.
|
|
73
|
-
if (!selectedModelId || isSaving || !isDirty) {
|
|
74
|
-
setSelectedModelId(activeModel);
|
|
75
|
-
}
|
|
76
|
-
}, [activeModel, isSaving, selectedModelId, isDirty]);
|
|
77
|
-
|
|
78
|
-
const availableModels = useMemo(() => {
|
|
79
|
-
const provider = providers.find((p) => p.id === selectedProviderId);
|
|
80
|
-
return provider?.models ?? [];
|
|
81
|
-
}, [providers, selectedProviderId]);
|
|
82
|
-
|
|
83
|
-
useEffect(() => {
|
|
84
|
-
if (!availableModels.length) return;
|
|
85
|
-
|
|
86
|
-
const selectedStillValid = availableModels.some((m) => m.id === selectedModelId);
|
|
87
|
-
if (!selectedStillValid) {
|
|
88
|
-
setSelectedModelId(availableModels[0].id);
|
|
89
|
-
}
|
|
90
|
-
}, [availableModels, selectedModelId]);
|
|
91
|
-
|
|
92
|
-
const hasChange = Boolean(selectedModelId && selectedModelId !== activeModel);
|
|
93
|
-
|
|
94
|
-
useEffect(() => {
|
|
95
|
-
if (!isSaving) return;
|
|
96
|
-
|
|
97
|
-
if (activeModel === selectedModelId && activeModel.length > 0) {
|
|
98
|
-
setIsSaving(false);
|
|
99
|
-
setIsSlow(false);
|
|
100
|
-
}
|
|
101
|
-
}, [activeModel, selectedModelId, isSaving]);
|
|
102
|
-
|
|
103
|
-
useEffect(() => {
|
|
104
|
-
if (!isSaving) return;
|
|
105
|
-
|
|
106
|
-
const slowTimer = setTimeout(() => setIsSlow(true), 10000);
|
|
107
|
-
return () => clearTimeout(slowTimer);
|
|
108
|
-
}, [isSaving]);
|
|
109
|
-
|
|
110
|
-
const providerForSelection = providers.find((p) => p.id === selectedProviderId);
|
|
111
|
-
|
|
112
|
-
const onProviderChange = (providerId: string) => {
|
|
113
|
-
if (isSaving) return;
|
|
114
|
-
setSelectedProviderId(providerId);
|
|
115
|
-
|
|
116
|
-
const provider = providers.find((p) => p.id === providerId);
|
|
117
|
-
const firstModel = provider?.models?.[0]?.id;
|
|
118
|
-
if (firstModel) {
|
|
119
|
-
setSelectedModelId(firstModel);
|
|
120
|
-
} else {
|
|
121
|
-
setSelectedModelId("");
|
|
122
|
-
}
|
|
123
|
-
};
|
|
124
|
-
|
|
125
|
-
const onSave = () => {
|
|
126
|
-
if (!hasChange || !selectedModelId || isSaving) return;
|
|
127
|
-
setIsSaving(true);
|
|
128
|
-
setIsSlow(false);
|
|
129
|
-
emit("settings.setActiveModel", { model: selectedModelId });
|
|
130
|
-
};
|
|
131
|
-
|
|
132
|
-
const openAddProvider = () => {
|
|
133
|
-
setEditingProvider(null);
|
|
134
|
-
setIsEditorOpen(true);
|
|
135
|
-
};
|
|
136
|
-
|
|
137
|
-
const openEditProvider = (provider: Provider) => {
|
|
138
|
-
// Extract provider type from profile ID (format: "provider:suffix")
|
|
139
|
-
const providerKey = provider.id.split(":")[0];
|
|
140
|
-
|
|
141
|
-
// Map provider key to ProviderType
|
|
142
|
-
const typeMap: Record<string, ProviderType> = {
|
|
143
|
-
anthropic: "anthropic",
|
|
144
|
-
openai: "openai",
|
|
145
|
-
"openai-codex": "openai",
|
|
146
|
-
"kimi-coding": "kimi-coding",
|
|
147
|
-
moonshot: "kimi-coding",
|
|
148
|
-
};
|
|
149
|
-
|
|
150
|
-
setEditingProvider({
|
|
151
|
-
id: provider.id,
|
|
152
|
-
name: provider.name,
|
|
153
|
-
type: typeMap[providerKey] ?? "anthropic",
|
|
154
|
-
mode: provider.mode,
|
|
155
|
-
authType: provider.authType,
|
|
156
|
-
maskedCredential: provider.maskedCredential,
|
|
157
|
-
});
|
|
158
|
-
setIsEditorOpen(true);
|
|
159
|
-
};
|
|
160
|
-
|
|
161
|
-
const closeEditor = () => {
|
|
162
|
-
setIsEditorOpen(false);
|
|
163
|
-
setEditingProvider(null);
|
|
164
|
-
};
|
|
165
|
-
|
|
166
|
-
return (
|
|
167
|
-
<>
|
|
168
|
-
<div className="flex flex-col gap-3">
|
|
169
|
-
<div className="space-y-1">
|
|
170
|
-
<p className="text-xs uppercase tracking-wide text-muted-foreground">System Default</p>
|
|
171
|
-
<p className="text-sm font-medium">Default model for new sessions</p>
|
|
172
|
-
</div>
|
|
173
|
-
|
|
174
|
-
{/* Provider selector with edit button */}
|
|
175
|
-
<div className="grid grid-cols-1 gap-2">
|
|
176
|
-
<label className="text-xs text-muted-foreground">Provider</label>
|
|
177
|
-
<div className="relative flex gap-2">
|
|
178
|
-
<div className="relative flex-1">
|
|
179
|
-
<select
|
|
180
|
-
value={selectedProviderId}
|
|
181
|
-
onChange={(e) => onProviderChange(e.target.value)}
|
|
182
|
-
disabled={isSaving || providers.length === 0}
|
|
183
|
-
className="h-10 w-full appearance-none rounded-md border border-input bg-background pl-3 pr-10 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary disabled:opacity-70"
|
|
184
|
-
>
|
|
185
|
-
{providers.length === 0 ? (
|
|
186
|
-
<option value="">No providers configured</option>
|
|
187
|
-
) : (
|
|
188
|
-
providers.map((provider) => (
|
|
189
|
-
<option key={provider.id} value={provider.id}>
|
|
190
|
-
{providerOptionLabel(provider)}
|
|
191
|
-
</option>
|
|
192
|
-
))
|
|
193
|
-
)}
|
|
194
|
-
</select>
|
|
195
|
-
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
|
|
196
|
-
</div>
|
|
197
|
-
|
|
198
|
-
{/* Edit button */}
|
|
199
|
-
{providerForSelection && (
|
|
200
|
-
<button
|
|
201
|
-
type="button"
|
|
202
|
-
onClick={() => openEditProvider(providerForSelection)}
|
|
203
|
-
disabled={isSaving}
|
|
204
|
-
className={cn(
|
|
205
|
-
"flex items-center justify-center h-10 w-10 rounded-md border border-input bg-background",
|
|
206
|
-
"hover:bg-muted transition-colors",
|
|
207
|
-
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
208
|
-
)}
|
|
209
|
-
title={`Edit ${providerForSelection.name}`}
|
|
210
|
-
>
|
|
211
|
-
<Pencil className="h-4 w-4" />
|
|
212
|
-
</button>
|
|
213
|
-
)}
|
|
214
|
-
</div>
|
|
215
|
-
</div>
|
|
216
|
-
|
|
217
|
-
{/* Model selector */}
|
|
218
|
-
<div className="grid grid-cols-1 gap-2">
|
|
219
|
-
<label className="text-xs text-muted-foreground">Model</label>
|
|
220
|
-
<div className="relative">
|
|
221
|
-
<select
|
|
222
|
-
value={selectedModelId}
|
|
223
|
-
onChange={(e) => setSelectedModelId(e.target.value)}
|
|
224
|
-
disabled={isSaving || availableModels.length === 0}
|
|
225
|
-
className="h-10 w-full appearance-none rounded-md border border-input bg-background pl-3 pr-10 text-sm text-foreground focus:outline-none focus:ring-2 focus:ring-primary disabled:opacity-70"
|
|
226
|
-
>
|
|
227
|
-
{availableModels.length === 0 ? (
|
|
228
|
-
<option value="">No models available</option>
|
|
229
|
-
) : (
|
|
230
|
-
availableModels.map((model) => (
|
|
231
|
-
<option key={model.id} value={model.id}>
|
|
232
|
-
{model.label}
|
|
233
|
-
</option>
|
|
234
|
-
))
|
|
235
|
-
)}
|
|
236
|
-
</select>
|
|
237
|
-
<ChevronDown className="absolute right-3 top-1/2 -translate-y-1/2 h-4 w-4 text-muted-foreground pointer-events-none" />
|
|
238
|
-
</div>
|
|
239
|
-
</div>
|
|
240
|
-
|
|
241
|
-
{/* Credential info */}
|
|
242
|
-
{providerForSelection && (
|
|
243
|
-
<div className="rounded-md border border-border bg-muted/30 px-3 py-2 text-xs text-muted-foreground">
|
|
244
|
-
<div>{providerOptionLabel(providerForSelection)}: {providerForSelection.maskedCredential ?? "No credential"}</div>
|
|
245
|
-
<div className="opacity-70">id: {providerForSelection.id}</div>
|
|
246
|
-
</div>
|
|
247
|
-
)}
|
|
248
|
-
|
|
249
|
-
{/* Save button */}
|
|
250
|
-
<button
|
|
251
|
-
type="button"
|
|
252
|
-
onClick={onSave}
|
|
253
|
-
disabled={!hasChange || !selectedModelId || isSaving}
|
|
254
|
-
className={cn(
|
|
255
|
-
"inline-flex h-10 items-center justify-center gap-2 rounded-md border px-4 text-sm font-medium transition-colors",
|
|
256
|
-
"border-primary bg-primary text-primary-foreground hover:bg-primary/90",
|
|
257
|
-
"disabled:cursor-not-allowed disabled:opacity-60"
|
|
258
|
-
)}
|
|
259
|
-
>
|
|
260
|
-
{isSaving ? (
|
|
261
|
-
<>
|
|
262
|
-
<Loader2 className="h-4 w-4 animate-spin" />
|
|
263
|
-
{isSlow ? "Still applying…" : "Updating model…"}
|
|
264
|
-
</>
|
|
265
|
-
) : hasChange ? (
|
|
266
|
-
"Save changes"
|
|
267
|
-
) : (
|
|
268
|
-
<>
|
|
269
|
-
<Check className="h-4 w-4" />
|
|
270
|
-
Up to date
|
|
271
|
-
</>
|
|
272
|
-
)}
|
|
273
|
-
</button>
|
|
274
|
-
|
|
275
|
-
{/* Add provider button */}
|
|
276
|
-
<div className="pt-2 border-t border-border">
|
|
277
|
-
<button
|
|
278
|
-
type="button"
|
|
279
|
-
onClick={openAddProvider}
|
|
280
|
-
className={cn(
|
|
281
|
-
"inline-flex h-10 w-full items-center justify-center gap-2 rounded-md border border-dashed border-border",
|
|
282
|
-
"text-sm font-medium text-muted-foreground",
|
|
283
|
-
"hover:border-primary hover:text-foreground hover:bg-muted/50 transition-colors"
|
|
284
|
-
)}
|
|
285
|
-
>
|
|
286
|
-
<Plus className="h-4 w-4" />
|
|
287
|
-
Add Provider
|
|
288
|
-
</button>
|
|
289
|
-
</div>
|
|
290
|
-
</div>
|
|
291
|
-
|
|
292
|
-
{/* Provider editor modal */}
|
|
293
|
-
<ProviderEditor
|
|
294
|
-
isOpen={isEditorOpen}
|
|
295
|
-
onClose={closeEditor}
|
|
296
|
-
editingProvider={editingProvider}
|
|
297
|
-
/>
|
|
298
|
-
</>
|
|
299
|
-
);
|
|
300
|
-
}
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
import { useEffect, useState } from "react";
|
|
2
|
-
import { useClappState, useIntent } from "@clapps/renderer";
|
|
3
|
-
import { cn } from "@/lib/utils";
|
|
4
|
-
import { RefreshCw, RotateCcw, Check, AlertTriangle, Loader2 } from "lucide-react";
|
|
5
|
-
|
|
6
|
-
interface Session {
|
|
7
|
-
key: string;
|
|
8
|
-
label: string;
|
|
9
|
-
model: string | null;
|
|
10
|
-
modelLabel: string;
|
|
11
|
-
isOverride: boolean;
|
|
12
|
-
lastUpdated?: string;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface SessionsState {
|
|
16
|
-
sessions: Session[];
|
|
17
|
-
globalModel: string | null;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export function SessionList() {
|
|
21
|
-
// Sessions data is now included directly in the settings state
|
|
22
|
-
const sessionsData = useClappState<SessionsState>("sessions");
|
|
23
|
-
const sessions = sessionsData?.sessions ?? [];
|
|
24
|
-
const globalModel = sessionsData?.globalModel;
|
|
25
|
-
|
|
26
|
-
const { emit } = useIntent();
|
|
27
|
-
|
|
28
|
-
const [isRefreshing, setIsRefreshing] = useState(false);
|
|
29
|
-
const [resettingSession, setResettingSession] = useState<string | null>(null);
|
|
30
|
-
const [applyingToAll, setApplyingToAll] = useState(false);
|
|
31
|
-
|
|
32
|
-
// Request initial session list on mount
|
|
33
|
-
useEffect(() => {
|
|
34
|
-
emit("settings.listSessions", {});
|
|
35
|
-
}, [emit]);
|
|
36
|
-
|
|
37
|
-
const handleRefresh = () => {
|
|
38
|
-
setIsRefreshing(true);
|
|
39
|
-
emit("settings.listSessions", {});
|
|
40
|
-
setTimeout(() => setIsRefreshing(false), 1500);
|
|
41
|
-
};
|
|
42
|
-
|
|
43
|
-
const handleResetSession = (sessionKey: string) => {
|
|
44
|
-
setResettingSession(sessionKey);
|
|
45
|
-
emit("settings.resetSessionModel", { sessionKey });
|
|
46
|
-
setTimeout(() => setResettingSession(null), 2000);
|
|
47
|
-
};
|
|
48
|
-
|
|
49
|
-
const handleApplyToAll = () => {
|
|
50
|
-
setApplyingToAll(true);
|
|
51
|
-
emit("settings.applyDefaultToAll", {});
|
|
52
|
-
setTimeout(() => setApplyingToAll(false), 3000);
|
|
53
|
-
};
|
|
54
|
-
|
|
55
|
-
const overrideCount = sessions.filter(s => s.isOverride).length;
|
|
56
|
-
|
|
57
|
-
if (sessions.length === 0) {
|
|
58
|
-
return (
|
|
59
|
-
<div className="flex flex-col gap-3">
|
|
60
|
-
<div className="flex items-center justify-between">
|
|
61
|
-
<div className="space-y-1">
|
|
62
|
-
<p className="text-xs uppercase tracking-wide text-muted-foreground">Active Sessions</p>
|
|
63
|
-
</div>
|
|
64
|
-
<button
|
|
65
|
-
onClick={handleRefresh}
|
|
66
|
-
disabled={isRefreshing}
|
|
67
|
-
className="p-1.5 rounded-md hover:bg-muted transition-colors disabled:opacity-50"
|
|
68
|
-
title="Refresh sessions"
|
|
69
|
-
>
|
|
70
|
-
<RefreshCw className={cn("h-4 w-4", isRefreshing && "animate-spin")} />
|
|
71
|
-
</button>
|
|
72
|
-
</div>
|
|
73
|
-
<div className="text-sm text-muted-foreground py-4 text-center border border-dashed border-border rounded-md">
|
|
74
|
-
No active sessions found
|
|
75
|
-
</div>
|
|
76
|
-
</div>
|
|
77
|
-
);
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
return (
|
|
81
|
-
<div className="flex flex-col gap-3">
|
|
82
|
-
{/* Header */}
|
|
83
|
-
<div className="flex items-center justify-between">
|
|
84
|
-
<div className="space-y-1">
|
|
85
|
-
<p className="text-xs uppercase tracking-wide text-muted-foreground">Active Sessions</p>
|
|
86
|
-
<p className="text-sm text-muted-foreground">
|
|
87
|
-
{sessions.length} session{sessions.length !== 1 ? "s" : ""}
|
|
88
|
-
{overrideCount > 0 && (
|
|
89
|
-
<span className="text-amber-500 ml-1">
|
|
90
|
-
• {overrideCount} override{overrideCount !== 1 ? "s" : ""}
|
|
91
|
-
</span>
|
|
92
|
-
)}
|
|
93
|
-
</p>
|
|
94
|
-
</div>
|
|
95
|
-
<button
|
|
96
|
-
onClick={handleRefresh}
|
|
97
|
-
disabled={isRefreshing}
|
|
98
|
-
className="p-1.5 rounded-md hover:bg-muted transition-colors disabled:opacity-50"
|
|
99
|
-
title="Refresh sessions"
|
|
100
|
-
>
|
|
101
|
-
<RefreshCw className={cn("h-4 w-4", isRefreshing && "animate-spin")} />
|
|
102
|
-
</button>
|
|
103
|
-
</div>
|
|
104
|
-
|
|
105
|
-
{/* Session list */}
|
|
106
|
-
<div className="flex flex-col gap-2">
|
|
107
|
-
{sessions.map((session) => (
|
|
108
|
-
<div
|
|
109
|
-
key={session.key}
|
|
110
|
-
className={cn(
|
|
111
|
-
"flex items-start justify-between gap-2 p-3 rounded-lg border",
|
|
112
|
-
session.isOverride
|
|
113
|
-
? "border-amber-500/50 bg-amber-500/5"
|
|
114
|
-
: "border-border bg-muted/30"
|
|
115
|
-
)}
|
|
116
|
-
>
|
|
117
|
-
<div className="flex-1 min-w-0">
|
|
118
|
-
<div className="flex items-center gap-2">
|
|
119
|
-
{session.isOverride ? (
|
|
120
|
-
<AlertTriangle className="h-4 w-4 text-amber-500 shrink-0" />
|
|
121
|
-
) : (
|
|
122
|
-
<Check className="h-4 w-4 text-green-500 shrink-0" />
|
|
123
|
-
)}
|
|
124
|
-
<span className="text-sm font-medium truncate">
|
|
125
|
-
{session.label}
|
|
126
|
-
</span>
|
|
127
|
-
</div>
|
|
128
|
-
<div className="mt-1 ml-6">
|
|
129
|
-
<span className="text-xs text-muted-foreground">
|
|
130
|
-
{session.modelLabel}
|
|
131
|
-
</span>
|
|
132
|
-
{session.isOverride && (
|
|
133
|
-
<span className="text-xs text-amber-500 ml-2">
|
|
134
|
-
(override)
|
|
135
|
-
</span>
|
|
136
|
-
)}
|
|
137
|
-
</div>
|
|
138
|
-
</div>
|
|
139
|
-
|
|
140
|
-
{session.isOverride && (
|
|
141
|
-
<button
|
|
142
|
-
onClick={() => handleResetSession(session.key)}
|
|
143
|
-
disabled={resettingSession === session.key}
|
|
144
|
-
className={cn(
|
|
145
|
-
"shrink-0 px-2 py-1 text-xs rounded-md border border-border",
|
|
146
|
-
"hover:bg-muted transition-colors",
|
|
147
|
-
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
148
|
-
)}
|
|
149
|
-
title="Reset to system default"
|
|
150
|
-
>
|
|
151
|
-
{resettingSession === session.key ? (
|
|
152
|
-
<Loader2 className="h-3 w-3 animate-spin" />
|
|
153
|
-
) : (
|
|
154
|
-
<span className="flex items-center gap-1">
|
|
155
|
-
<RotateCcw className="h-3 w-3" />
|
|
156
|
-
Reset
|
|
157
|
-
</span>
|
|
158
|
-
)}
|
|
159
|
-
</button>
|
|
160
|
-
)}
|
|
161
|
-
</div>
|
|
162
|
-
))}
|
|
163
|
-
</div>
|
|
164
|
-
|
|
165
|
-
{/* Apply to all button */}
|
|
166
|
-
{overrideCount > 0 && (
|
|
167
|
-
<button
|
|
168
|
-
onClick={handleApplyToAll}
|
|
169
|
-
disabled={applyingToAll}
|
|
170
|
-
className={cn(
|
|
171
|
-
"w-full py-2 px-4 text-sm font-medium rounded-md",
|
|
172
|
-
"border border-amber-500/50 text-amber-600 dark:text-amber-400",
|
|
173
|
-
"hover:bg-amber-500/10 transition-colors",
|
|
174
|
-
"disabled:opacity-50 disabled:cursor-not-allowed"
|
|
175
|
-
)}
|
|
176
|
-
>
|
|
177
|
-
{applyingToAll ? (
|
|
178
|
-
<span className="flex items-center justify-center gap-2">
|
|
179
|
-
<Loader2 className="h-4 w-4 animate-spin" />
|
|
180
|
-
Applying to all sessions...
|
|
181
|
-
</span>
|
|
182
|
-
) : (
|
|
183
|
-
`Apply default to all ${overrideCount} override${overrideCount !== 1 ? "s" : ""}`
|
|
184
|
-
)}
|
|
185
|
-
</button>
|
|
186
|
-
)}
|
|
187
|
-
</div>
|
|
188
|
-
);
|
|
189
|
-
}
|