@nastechai/agent 0.16.0 → 0.17.0
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/eslint.config.js +23 -0
- package/index.html +24 -0
- package/package.json +54 -26
- package/package.json.bak +89 -0
- package/package.json.pub +88 -0
- package/src/App.tsx +1173 -0
- package/src/components/AuthWidget.tsx +150 -0
- package/src/components/AutoField.tsx +206 -0
- package/src/components/Backdrop.tsx +93 -0
- package/src/components/ChatSidebar.tsx +394 -0
- package/src/components/DeleteConfirmDialog.tsx +40 -0
- package/src/components/LanguageSwitcher.tsx +186 -0
- package/src/components/Markdown.tsx +383 -0
- package/src/components/ModelInfoCard.tsx +112 -0
- package/src/components/ModelPickerDialog.tsx +470 -0
- package/src/components/OAuthLoginModal.tsx +374 -0
- package/src/components/OAuthProvidersCard.tsx +287 -0
- package/src/components/PlatformsCard.tsx +97 -0
- package/src/components/ScheduleBuilder.tsx +273 -0
- package/src/components/SidebarFooter.tsx +42 -0
- package/src/components/SidebarStatusStrip.tsx +72 -0
- package/src/components/SlashPopover.tsx +171 -0
- package/src/components/ThemeSwitcher.tsx +243 -0
- package/src/components/ToolCall.tsx +228 -0
- package/src/components/ToolsetConfigDrawer.tsx +448 -0
- package/src/contexts/PageHeaderProvider.tsx +139 -0
- package/src/contexts/SystemActions.tsx +120 -0
- package/src/contexts/page-header-context.ts +12 -0
- package/src/contexts/system-actions-context.ts +18 -0
- package/src/contexts/usePageHeader.ts +10 -0
- package/src/contexts/useSystemActions.ts +15 -0
- package/src/hooks/useModalBehavior.ts +44 -0
- package/src/hooks/useSidebarStatus.ts +27 -0
- package/src/i18n/af.ts +702 -0
- package/src/i18n/context.tsx +123 -0
- package/src/i18n/de.ts +701 -0
- package/src/i18n/en.ts +708 -0
- package/src/i18n/es.ts +701 -0
- package/src/i18n/fr.ts +701 -0
- package/src/i18n/ga.ts +702 -0
- package/src/i18n/hu.ts +702 -0
- package/src/i18n/index.ts +2 -0
- package/src/i18n/it.ts +701 -0
- package/src/i18n/ja.ts +702 -0
- package/src/i18n/ko.ts +702 -0
- package/src/i18n/pt.ts +702 -0
- package/src/i18n/ru.ts +702 -0
- package/src/i18n/tr.ts +702 -0
- package/src/i18n/types.ts +710 -0
- package/src/i18n/uk.ts +702 -0
- package/src/i18n/zh-hant.ts +702 -0
- package/src/i18n/zh.ts +698 -0
- package/src/index.css +274 -0
- package/src/lib/api.ts +1585 -0
- package/src/lib/dashboard-flags.ts +15 -0
- package/src/lib/format.ts +9 -0
- package/src/lib/fuzzy.ts +192 -0
- package/src/lib/gatewayClient.ts +253 -0
- package/src/lib/nested.ts +23 -0
- package/src/lib/resolve-page-title.ts +41 -0
- package/src/lib/schedule.ts +382 -0
- package/src/lib/slashExec.ts +163 -0
- package/src/lib/utils.ts +35 -0
- package/src/main.tsx +25 -0
- package/src/pages/AnalyticsPage.tsx +601 -0
- package/src/pages/ChannelsPage.tsx +772 -0
- package/src/pages/ChatPage.tsx +889 -0
- package/src/pages/ConfigPage.tsx +660 -0
- package/src/pages/CronPage.tsx +524 -0
- package/src/pages/DocsPage.tsx +69 -0
- package/src/pages/EnvPage.tsx +918 -0
- package/src/pages/LogsPage.tsx +246 -0
- package/src/pages/McpPage.tsx +757 -0
- package/src/pages/ModelsPage.tsx +994 -0
- package/src/pages/PairingPage.tsx +276 -0
- package/src/pages/PluginsPage.tsx +580 -0
- package/src/pages/ProfilesPage.tsx +559 -0
- package/src/pages/SessionsPage.tsx +936 -0
- package/src/pages/SkillsPage.tsx +557 -0
- package/src/pages/SystemPage.tsx +1259 -0
- package/src/pages/WebhooksPage.tsx +483 -0
- package/src/plugins/PluginPage.tsx +64 -0
- package/src/plugins/index.ts +6 -0
- package/src/plugins/registry.ts +151 -0
- package/src/plugins/sdk.d.ts +160 -0
- package/src/plugins/slots.ts +199 -0
- package/src/plugins/types.ts +37 -0
- package/src/plugins/usePlugins.ts +133 -0
- package/src/themes/context.tsx +443 -0
- package/src/themes/fonts.ts +160 -0
- package/src/themes/index.ts +3 -0
- package/src/themes/presets.ts +477 -0
- package/src/themes/types.ts +187 -0
- package/tsconfig.app.json +34 -0
- package/tsconfig.json +7 -0
- package/tsconfig.node.json +26 -0
- package/vite.config.ts +124 -0
- package/vite.config.ts.timestamp-1780999102396-af6b77b30ebd8.mjs +105 -0
|
@@ -0,0 +1,580 @@
|
|
|
1
|
+
import { useCallback, useEffect, useState } from "react";
|
|
2
|
+
import { ExternalLink, RefreshCw, Trash2, Eye, EyeOff } from "lucide-react";
|
|
3
|
+
import type { Translations } from "@/i18n/types";
|
|
4
|
+
import { Link } from "react-router-dom";
|
|
5
|
+
import { api } from "@/lib/api";
|
|
6
|
+
import type { HubAgentPluginRow, PluginsHubResponse } from "@/lib/api";
|
|
7
|
+
import { Button } from "@nastechai/ui/ui/components/button";
|
|
8
|
+
import { Badge } from "@nastechai/ui/ui/components/badge";
|
|
9
|
+
import { Select, SelectOption } from "@nastechai/ui/ui/components/select";
|
|
10
|
+
import { Switch } from "@nastechai/ui/ui/components/switch";
|
|
11
|
+
import { Spinner } from "@nastechai/ui/ui/components/spinner";
|
|
12
|
+
import { CommandBlock } from "@nastechai/ui/ui/components/command-block";
|
|
13
|
+
import { Card, CardContent, CardHeader, CardTitle } from "@nastechai/ui/ui/components/card";
|
|
14
|
+
import { ConfirmDialog } from "@nastechai/ui/ui/components/confirm-dialog";
|
|
15
|
+
import { Input } from "@nastechai/ui/ui/components/input";
|
|
16
|
+
import { Label } from "@nastechai/ui/ui/components/label";
|
|
17
|
+
import { useToast } from "@nastechai/ui/hooks/use-toast";
|
|
18
|
+
import { Toast } from "@nastechai/ui/ui/components/toast";
|
|
19
|
+
import { useI18n } from "@/i18n";
|
|
20
|
+
import { PluginSlot } from "@/plugins";
|
|
21
|
+
import { cn } from "@/lib/utils";
|
|
22
|
+
import { usePageHeader } from "@/contexts/usePageHeader";
|
|
23
|
+
|
|
24
|
+
/** Select value for built-in memory (`config` uses empty string). Never use `""` — UI Select maps empty value to an empty label. */
|
|
25
|
+
const MEMORY_PROVIDER_BUILTIN = "__nastech_memory_builtin__";
|
|
26
|
+
|
|
27
|
+
export default function PluginsPage() {
|
|
28
|
+
const [hub, setHub] = useState<PluginsHubResponse | null>(null);
|
|
29
|
+
const [loading, setLoading] = useState(true);
|
|
30
|
+
const [installId, setInstallId] = useState("");
|
|
31
|
+
const [installForce, setInstallForce] = useState(false);
|
|
32
|
+
const [installEnable, setInstallEnable] = useState(true);
|
|
33
|
+
const [installBusy, setInstallBusy] = useState(false);
|
|
34
|
+
const [rescanBusy, setRescanBusy] = useState(false);
|
|
35
|
+
const [memorySel, setMemorySel] = useState(MEMORY_PROVIDER_BUILTIN);
|
|
36
|
+
const [contextSel, setContextSel] = useState("compressor");
|
|
37
|
+
const [providerBusy, setProviderBusy] = useState(false);
|
|
38
|
+
const [rowBusy, setRowBusy] = useState<string | null>(null);
|
|
39
|
+
|
|
40
|
+
const { toast, showToast } = useToast();
|
|
41
|
+
const { t } = useI18n();
|
|
42
|
+
const { setAfterTitle } = usePageHeader();
|
|
43
|
+
|
|
44
|
+
const loadHub = useCallback(() => {
|
|
45
|
+
return api
|
|
46
|
+
.getPluginsHub()
|
|
47
|
+
.then((h) => {
|
|
48
|
+
setHub(h);
|
|
49
|
+
const p = h.providers;
|
|
50
|
+
setMemorySel(p.memory_provider ? p.memory_provider : MEMORY_PROVIDER_BUILTIN);
|
|
51
|
+
setContextSel(p.context_engine || "compressor");
|
|
52
|
+
})
|
|
53
|
+
.catch(() => showToast(t.common.loading, "error"));
|
|
54
|
+
}, [showToast, t.common.loading]);
|
|
55
|
+
|
|
56
|
+
useEffect(() => {
|
|
57
|
+
setLoading(true);
|
|
58
|
+
void loadHub().finally(() => setLoading(false));
|
|
59
|
+
}, [loadHub]);
|
|
60
|
+
|
|
61
|
+
useEffect(() => {
|
|
62
|
+
setAfterTitle(
|
|
63
|
+
<Button
|
|
64
|
+
ghost
|
|
65
|
+
size="icon"
|
|
66
|
+
className="shrink-0 text-muted-foreground hover:text-foreground"
|
|
67
|
+
disabled={loading || rescanBusy}
|
|
68
|
+
onClick={() => void onRescan()}
|
|
69
|
+
aria-label={t.pluginsPage.refreshDashboard}
|
|
70
|
+
>
|
|
71
|
+
{rescanBusy ? <Spinner /> : <RefreshCw />}
|
|
72
|
+
</Button>,
|
|
73
|
+
);
|
|
74
|
+
return () => setAfterTitle(null);
|
|
75
|
+
}, [loading, rescanBusy, setAfterTitle, t.pluginsPage.refreshDashboard]);
|
|
76
|
+
|
|
77
|
+
const onInstall = async () => {
|
|
78
|
+
const id = installId.trim();
|
|
79
|
+
if (!id) {
|
|
80
|
+
showToast(t.pluginsPage.installHint, "error");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
setInstallBusy(true);
|
|
84
|
+
try {
|
|
85
|
+
const r = await api.installAgentPlugin({
|
|
86
|
+
identifier: id,
|
|
87
|
+
force: installForce,
|
|
88
|
+
enable: installEnable,
|
|
89
|
+
});
|
|
90
|
+
showToast(`${r.plugin_name ?? id} installed`, "success");
|
|
91
|
+
if ((r.warnings?.length ?? 0) > 0) showToast(r.warnings!.join(" "), "error");
|
|
92
|
+
if ((r.missing_env?.length ?? 0) > 0)
|
|
93
|
+
showToast(`${t.pluginsPage.missingEnvWarn} ${r.missing_env!.join(", ")}`, "error");
|
|
94
|
+
setInstallId("");
|
|
95
|
+
await loadHub();
|
|
96
|
+
} catch (e) {
|
|
97
|
+
showToast(e instanceof Error ? e.message : "Install failed", "error");
|
|
98
|
+
} finally {
|
|
99
|
+
setInstallBusy(false);
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
const onRescan = async () => {
|
|
104
|
+
setRescanBusy(true);
|
|
105
|
+
try {
|
|
106
|
+
const rc = await api.rescanPlugins();
|
|
107
|
+
showToast(
|
|
108
|
+
`${t.pluginsPage.refreshDashboard} (${rc.count})`,
|
|
109
|
+
"success",
|
|
110
|
+
);
|
|
111
|
+
await loadHub();
|
|
112
|
+
} catch (e) {
|
|
113
|
+
showToast(e instanceof Error ? e.message : "Rescan failed", "error");
|
|
114
|
+
} finally {
|
|
115
|
+
setRescanBusy(false);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const onSaveProviders = async () => {
|
|
120
|
+
setProviderBusy(true);
|
|
121
|
+
try {
|
|
122
|
+
await api.savePluginProviders({
|
|
123
|
+
memory_provider:
|
|
124
|
+
memorySel === MEMORY_PROVIDER_BUILTIN ? "" : memorySel,
|
|
125
|
+
context_engine: contextSel,
|
|
126
|
+
});
|
|
127
|
+
showToast(t.pluginsPage.savedProviders, "success");
|
|
128
|
+
await loadHub();
|
|
129
|
+
} catch (e) {
|
|
130
|
+
showToast(e instanceof Error ? e.message : "Save failed", "error");
|
|
131
|
+
} finally {
|
|
132
|
+
setProviderBusy(false);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
const setRuntimeLoading = async (name: string, fn: () => Promise<unknown>) => {
|
|
137
|
+
setRowBusy(name);
|
|
138
|
+
try {
|
|
139
|
+
await fn();
|
|
140
|
+
await loadHub();
|
|
141
|
+
} catch (e) {
|
|
142
|
+
showToast(e instanceof Error ? e.message : "Failed", "error");
|
|
143
|
+
} finally {
|
|
144
|
+
setRowBusy(null);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
|
|
148
|
+
const rows = hub?.plugins ?? [];
|
|
149
|
+
const providers = hub?.providers;
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<div className="flex flex-col gap-4">
|
|
153
|
+
<PluginSlot name="plugins:top" />
|
|
154
|
+
|
|
155
|
+
<div className={cn("flex w-full flex-col gap-8")}>
|
|
156
|
+
|
|
157
|
+
{providers && (
|
|
158
|
+
<Card>
|
|
159
|
+
<CardHeader>
|
|
160
|
+
<CardTitle>{t.pluginsPage.providersHeading}</CardTitle>
|
|
161
|
+
<p className="text-xs tracking-[0.08em] text-text-tertiary">
|
|
162
|
+
{t.pluginsPage.providersHint}
|
|
163
|
+
</p>
|
|
164
|
+
</CardHeader>
|
|
165
|
+
|
|
166
|
+
<CardContent className="flex flex-col gap-6">
|
|
167
|
+
|
|
168
|
+
<div className="grid gap-6 sm:grid-cols-2 max-w-full">
|
|
169
|
+
<div className="grid gap-2 min-w-0">
|
|
170
|
+
<Label htmlFor="mem-provider">{t.pluginsPage.memoryProviderLabel}</Label>
|
|
171
|
+
|
|
172
|
+
<Select
|
|
173
|
+
id="mem-provider"
|
|
174
|
+
className="w-full"
|
|
175
|
+
value={memorySel}
|
|
176
|
+
onValueChange={setMemorySel}
|
|
177
|
+
>
|
|
178
|
+
<SelectOption value={MEMORY_PROVIDER_BUILTIN}>
|
|
179
|
+
{`(${t.pluginsPage.providerDefaults})`}
|
|
180
|
+
</SelectOption>
|
|
181
|
+
|
|
182
|
+
{providers.memory_options.map((o) => (
|
|
183
|
+
<SelectOption key={o.name} value={o.name}>
|
|
184
|
+
{o.name}
|
|
185
|
+
</SelectOption>
|
|
186
|
+
))}
|
|
187
|
+
</Select>
|
|
188
|
+
</div>
|
|
189
|
+
|
|
190
|
+
<div className="grid gap-2 min-w-0">
|
|
191
|
+
<Label htmlFor="ctx-engine">{t.pluginsPage.contextEngineLabel}</Label>
|
|
192
|
+
|
|
193
|
+
<Select
|
|
194
|
+
id="ctx-engine"
|
|
195
|
+
className="w-full"
|
|
196
|
+
value={contextSel}
|
|
197
|
+
onValueChange={setContextSel}
|
|
198
|
+
>
|
|
199
|
+
<SelectOption value="compressor">compressor</SelectOption>
|
|
200
|
+
|
|
201
|
+
{providers.context_options
|
|
202
|
+
.filter((o) => o.name !== "compressor")
|
|
203
|
+
.map((o) => (
|
|
204
|
+
<SelectOption key={o.name} value={o.name}>
|
|
205
|
+
{o.name}
|
|
206
|
+
</SelectOption>
|
|
207
|
+
))}
|
|
208
|
+
</Select>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
|
|
212
|
+
<Button
|
|
213
|
+
className="w-fit uppercase"
|
|
214
|
+
size="sm"
|
|
215
|
+
disabled={providerBusy}
|
|
216
|
+
onClick={() => void onSaveProviders()}
|
|
217
|
+
prefix={providerBusy ? <Spinner /> : undefined}
|
|
218
|
+
>
|
|
219
|
+
{t.common.save}
|
|
220
|
+
</Button>
|
|
221
|
+
</CardContent>
|
|
222
|
+
</Card>
|
|
223
|
+
)}
|
|
224
|
+
|
|
225
|
+
<Card>
|
|
226
|
+
<CardHeader>
|
|
227
|
+
<CardTitle>{t.pluginsPage.installHeading}</CardTitle>
|
|
228
|
+
<p className="text-xs tracking-[0.08em] text-text-tertiary">
|
|
229
|
+
{t.pluginsPage.installHint}
|
|
230
|
+
</p>
|
|
231
|
+
</CardHeader>
|
|
232
|
+
|
|
233
|
+
|
|
234
|
+
<CardContent className="flex flex-col gap-4">
|
|
235
|
+
|
|
236
|
+
<div className="flex flex-col gap-2">
|
|
237
|
+
|
|
238
|
+
<Label htmlFor="install-url">{t.pluginsPage.identifierLabel}</Label>
|
|
239
|
+
|
|
240
|
+
<Input
|
|
241
|
+
className="font-mono-ui lowercase"
|
|
242
|
+
id="install-url"
|
|
243
|
+
placeholder="owner/repo or https://..."
|
|
244
|
+
spellCheck={false}
|
|
245
|
+
value={installId}
|
|
246
|
+
onChange={(e) => setInstallId(e.target.value)}
|
|
247
|
+
/>
|
|
248
|
+
</div>
|
|
249
|
+
|
|
250
|
+
|
|
251
|
+
<div className="flex flex-wrap items-center gap-8">
|
|
252
|
+
|
|
253
|
+
<div className="flex items-center gap-3">
|
|
254
|
+
|
|
255
|
+
<Switch checked={installForce} onCheckedChange={setInstallForce} />
|
|
256
|
+
|
|
257
|
+
<span className="text-xs tracking-[0.06em] text-text-secondary">
|
|
258
|
+
{t.pluginsPage.forceReinstall}
|
|
259
|
+
</span>
|
|
260
|
+
</div>
|
|
261
|
+
|
|
262
|
+
<div className="flex items-center gap-3">
|
|
263
|
+
|
|
264
|
+
<Switch checked={installEnable} onCheckedChange={setInstallEnable} />
|
|
265
|
+
|
|
266
|
+
<span className="text-xs tracking-[0.06em] text-text-secondary">
|
|
267
|
+
{t.pluginsPage.enableAfterInstall}
|
|
268
|
+
</span>
|
|
269
|
+
</div>
|
|
270
|
+
</div>
|
|
271
|
+
|
|
272
|
+
<Button
|
|
273
|
+
className="w-fit uppercase"
|
|
274
|
+
size="sm"
|
|
275
|
+
disabled={installBusy}
|
|
276
|
+
onClick={() => void onInstall()}
|
|
277
|
+
prefix={installBusy ? <Spinner /> : undefined}
|
|
278
|
+
>
|
|
279
|
+
{t.pluginsPage.installBtn}
|
|
280
|
+
</Button>
|
|
281
|
+
|
|
282
|
+
<p className="text-xs tracking-[0.06em] text-text-tertiary">
|
|
283
|
+
{t.pluginsPage.rescanHint}
|
|
284
|
+
</p>
|
|
285
|
+
|
|
286
|
+
<p className="text-xs tracking-[0.06em] text-text-tertiary">
|
|
287
|
+
{t.pluginsPage.removeHint}
|
|
288
|
+
</p>
|
|
289
|
+
</CardContent>
|
|
290
|
+
</Card>
|
|
291
|
+
|
|
292
|
+
<div className="flex flex-col gap-3">
|
|
293
|
+
|
|
294
|
+
<h3 className="font-mondwest text-display text-xs tracking-[0.12em] text-text-secondary">
|
|
295
|
+
{t.pluginsPage.pluginListHeading}
|
|
296
|
+
</h3>
|
|
297
|
+
|
|
298
|
+
{loading ? (
|
|
299
|
+
|
|
300
|
+
<div className="flex items-center gap-2 py-8 text-xs text-text-tertiary">
|
|
301
|
+
|
|
302
|
+
<Spinner />
|
|
303
|
+
<span>{t.common.loading}</span>
|
|
304
|
+
</div>
|
|
305
|
+
) : rows.length === 0 ? (
|
|
306
|
+
|
|
307
|
+
<p className="text-xs text-text-tertiary">{t.common.noResults}</p>
|
|
308
|
+
) : (
|
|
309
|
+
|
|
310
|
+
<ul className="flex flex-col gap-3">
|
|
311
|
+
|
|
312
|
+
{rows.map((row: HubAgentPluginRow) => (
|
|
313
|
+
|
|
314
|
+
<li key={row.name}>
|
|
315
|
+
|
|
316
|
+
|
|
317
|
+
<PluginRowCard
|
|
318
|
+
{...{ row, rowBusy, setRuntimeLoading, showToast, t }}
|
|
319
|
+
/>
|
|
320
|
+
|
|
321
|
+
</li>
|
|
322
|
+
))}
|
|
323
|
+
</ul>
|
|
324
|
+
)}
|
|
325
|
+
</div>
|
|
326
|
+
|
|
327
|
+
{(hub?.orphan_dashboard_plugins?.length ?? 0) > 0 ? (
|
|
328
|
+
|
|
329
|
+
|
|
330
|
+
<div className="flex flex-col gap-3 opacity-95">
|
|
331
|
+
|
|
332
|
+
<h3 className="font-mondwest text-display text-xs tracking-[0.12em] text-text-secondary">
|
|
333
|
+
{t.pluginsPage.orphanHeading}
|
|
334
|
+
</h3>
|
|
335
|
+
|
|
336
|
+
<ul className="flex flex-col gap-2 rounded border border-current/15 p-4">
|
|
337
|
+
|
|
338
|
+
{hub!.orphan_dashboard_plugins.map((m) => (
|
|
339
|
+
|
|
340
|
+
<li className="text-xs text-text-secondary" key={m.name}>
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
{m.label ?? m.name} — {m.description || m.tab?.path}
|
|
344
|
+
|
|
345
|
+
|
|
346
|
+
{!m.tab?.hidden ? (
|
|
347
|
+
|
|
348
|
+
|
|
349
|
+
<Link className="ml-3 inline-flex items-center gap-1 underline" to={m.tab.path}>
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
<ExternalLink className="h-3 w-3 opacity-65" />
|
|
353
|
+
|
|
354
|
+
{t.pluginsPage.openTab}
|
|
355
|
+
</Link>
|
|
356
|
+
) : null}
|
|
357
|
+
</li>
|
|
358
|
+
))}
|
|
359
|
+
</ul>
|
|
360
|
+
</div>
|
|
361
|
+
) : null}
|
|
362
|
+
</div>
|
|
363
|
+
|
|
364
|
+
<Toast toast={toast} />
|
|
365
|
+
<PluginSlot name="plugins:bottom" />
|
|
366
|
+
</div>
|
|
367
|
+
);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
interface PluginRowCardProps {
|
|
371
|
+
|
|
372
|
+
row: HubAgentPluginRow;
|
|
373
|
+
rowBusy: string | null;
|
|
374
|
+
setRuntimeLoading: (
|
|
375
|
+
name: string,
|
|
376
|
+
fn: () => Promise<unknown>,
|
|
377
|
+
) => Promise<void>;
|
|
378
|
+
|
|
379
|
+
showToast: (msg: string, variant: "success" | "error") => void;
|
|
380
|
+
t: Translations;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
function PluginRowCard(props: PluginRowCardProps) {
|
|
384
|
+
const {
|
|
385
|
+
row,
|
|
386
|
+
rowBusy,
|
|
387
|
+
setRuntimeLoading,
|
|
388
|
+
showToast,
|
|
389
|
+
t,
|
|
390
|
+
} = props;
|
|
391
|
+
|
|
392
|
+
const dm = row.dashboard_manifest;
|
|
393
|
+
|
|
394
|
+
const tabPath = dm?.tab && !dm.tab.hidden ? dm.tab.override ?? dm.tab.path : null;
|
|
395
|
+
|
|
396
|
+
const busy = rowBusy === row.name;
|
|
397
|
+
const [confirmRemove, setConfirmRemove] = useState(false);
|
|
398
|
+
|
|
399
|
+
const badgeTone =
|
|
400
|
+
row.runtime_status === "enabled"
|
|
401
|
+
? "success"
|
|
402
|
+
: row.runtime_status === "disabled"
|
|
403
|
+
? "destructive"
|
|
404
|
+
: "outline";
|
|
405
|
+
|
|
406
|
+
return (
|
|
407
|
+
|
|
408
|
+
<Card className={cn(busy ? "opacity-70" : undefined)}>
|
|
409
|
+
|
|
410
|
+
|
|
411
|
+
<CardContent className="flex flex-col gap-4 px-6 py-4">
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
<div className="flex flex-wrap items-start justify-between gap-4">
|
|
415
|
+
|
|
416
|
+
<div className="flex min-w-0 flex-1 flex-wrap items-center gap-3">
|
|
417
|
+
|
|
418
|
+
<span className="truncate font-semibold">{row.name}</span>
|
|
419
|
+
|
|
420
|
+
<Badge tone="outline">
|
|
421
|
+
{t.pluginsPage.sourceBadge}: {row.source}
|
|
422
|
+
</Badge>
|
|
423
|
+
|
|
424
|
+
<Badge tone="outline">v{row.version || "—"}</Badge>
|
|
425
|
+
|
|
426
|
+
<Badge tone={badgeTone}>{row.runtime_status}</Badge>
|
|
427
|
+
|
|
428
|
+
{row.auth_required ? (
|
|
429
|
+
<Badge tone="destructive">{t.pluginsPage.authRequired}</Badge>
|
|
430
|
+
) : null}
|
|
431
|
+
</div>
|
|
432
|
+
|
|
433
|
+
<div className="flex flex-wrap items-center gap-2 shrink-0">
|
|
434
|
+
{row.runtime_status === "enabled" ? (
|
|
435
|
+
<Button
|
|
436
|
+
disabled={busy}
|
|
437
|
+
ghost
|
|
438
|
+
size="sm"
|
|
439
|
+
onClick={() => {
|
|
440
|
+
void setRuntimeLoading(row.name, async () => {
|
|
441
|
+
await api.disableAgentPlugin(row.name);
|
|
442
|
+
showToast(t.pluginsPage.disableRuntime, "success");
|
|
443
|
+
});
|
|
444
|
+
}}
|
|
445
|
+
>
|
|
446
|
+
{t.pluginsPage.disableRuntime}
|
|
447
|
+
</Button>
|
|
448
|
+
) : (
|
|
449
|
+
<Button
|
|
450
|
+
disabled={busy}
|
|
451
|
+
ghost
|
|
452
|
+
size="sm"
|
|
453
|
+
onClick={() => {
|
|
454
|
+
void setRuntimeLoading(row.name, async () => {
|
|
455
|
+
await api.enableAgentPlugin(row.name);
|
|
456
|
+
showToast(t.pluginsPage.enableRuntime, "success");
|
|
457
|
+
});
|
|
458
|
+
}}
|
|
459
|
+
>
|
|
460
|
+
{t.pluginsPage.enableRuntime}
|
|
461
|
+
</Button>
|
|
462
|
+
)}
|
|
463
|
+
|
|
464
|
+
{tabPath ? (
|
|
465
|
+
|
|
466
|
+
<Link
|
|
467
|
+
className={cn(
|
|
468
|
+
"inline-flex items-center rounded-none px-3 py-1.5",
|
|
469
|
+
"border border-current/25 hover:bg-current/10",
|
|
470
|
+
"font-mondwest text-display text-xs tracking-[0.1em]",
|
|
471
|
+
)}
|
|
472
|
+
to={tabPath}
|
|
473
|
+
>
|
|
474
|
+
{t.pluginsPage.openTab}
|
|
475
|
+
</Link>
|
|
476
|
+
) : null}
|
|
477
|
+
|
|
478
|
+
{row.can_update_git ? (
|
|
479
|
+
|
|
480
|
+
<Button
|
|
481
|
+
disabled={busy}
|
|
482
|
+
ghost
|
|
483
|
+
size="sm"
|
|
484
|
+
onClick={() => {
|
|
485
|
+
void setRuntimeLoading(row.name, async () => {
|
|
486
|
+
await api.updateAgentPlugin(row.name);
|
|
487
|
+
showToast(t.pluginsPage.updateGit, "success");
|
|
488
|
+
});
|
|
489
|
+
}}
|
|
490
|
+
>
|
|
491
|
+
{busy ? <Spinner /> : null}
|
|
492
|
+
{t.pluginsPage.updateGit}
|
|
493
|
+
</Button>
|
|
494
|
+
) : null}
|
|
495
|
+
|
|
496
|
+
{row.has_dashboard_manifest ? (
|
|
497
|
+
<Button
|
|
498
|
+
disabled={busy}
|
|
499
|
+
ghost
|
|
500
|
+
size="sm"
|
|
501
|
+
title={row.user_hidden ? t.pluginsPage.showInSidebar : t.pluginsPage.hideFromSidebar}
|
|
502
|
+
onClick={() => {
|
|
503
|
+
void setRuntimeLoading(row.name, async () => {
|
|
504
|
+
await api.setPluginVisibility(row.name, !row.user_hidden);
|
|
505
|
+
});
|
|
506
|
+
}}
|
|
507
|
+
>
|
|
508
|
+
{row.user_hidden ? (
|
|
509
|
+
<EyeOff className="h-3.5 w-3.5" />
|
|
510
|
+
) : (
|
|
511
|
+
<Eye className="h-3.5 w-3.5" />
|
|
512
|
+
)}
|
|
513
|
+
{row.user_hidden ? t.pluginsPage.showInSidebar : t.pluginsPage.hideFromSidebar}
|
|
514
|
+
</Button>
|
|
515
|
+
) : null}
|
|
516
|
+
|
|
517
|
+
{row.can_remove ? (
|
|
518
|
+
|
|
519
|
+
|
|
520
|
+
<Button
|
|
521
|
+
destructive
|
|
522
|
+
disabled={busy}
|
|
523
|
+
ghost
|
|
524
|
+
size="sm"
|
|
525
|
+
onClick={() => setConfirmRemove(true)}
|
|
526
|
+
>
|
|
527
|
+
|
|
528
|
+
{busy ? <Spinner /> : <Trash2 className="h-3.5 w-3.5" />}
|
|
529
|
+
</Button>
|
|
530
|
+
) : null}
|
|
531
|
+
</div>
|
|
532
|
+
</div>
|
|
533
|
+
|
|
534
|
+
{row.description ? (
|
|
535
|
+
<p className="min-w-0 w-full text-xs tracking-[0.06em] text-text-secondary break-words">
|
|
536
|
+
{row.description}
|
|
537
|
+
</p>
|
|
538
|
+
) : null}
|
|
539
|
+
|
|
540
|
+
{dm?.slots?.length ? (
|
|
541
|
+
|
|
542
|
+
<p className="text-xs tracking-[0.05em] text-text-tertiary">
|
|
543
|
+
{t.pluginsPage.dashboardSlots}: {dm.slots.join(", ")}
|
|
544
|
+
</p>
|
|
545
|
+
) : null}
|
|
546
|
+
|
|
547
|
+
{row.auth_required ? (
|
|
548
|
+
<CommandBlock
|
|
549
|
+
label={t.pluginsPage.authRequiredHint}
|
|
550
|
+
code={row.auth_command}
|
|
551
|
+
/>
|
|
552
|
+
) : null}
|
|
553
|
+
|
|
554
|
+
{!row.has_dashboard_manifest && !dm ? (
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
<p className="text-xs italic text-text-disabled">
|
|
558
|
+
{t.pluginsPage.noDashboardTab}
|
|
559
|
+
</p>
|
|
560
|
+
) : null}
|
|
561
|
+
</CardContent>
|
|
562
|
+
|
|
563
|
+
<ConfirmDialog
|
|
564
|
+
open={confirmRemove}
|
|
565
|
+
onCancel={() => setConfirmRemove(false)}
|
|
566
|
+
onConfirm={() => {
|
|
567
|
+
setConfirmRemove(false);
|
|
568
|
+
void setRuntimeLoading(row.name, async () => {
|
|
569
|
+
await api.removeAgentPlugin(row.name);
|
|
570
|
+
showToast(`${row.name} removed`, "success");
|
|
571
|
+
});
|
|
572
|
+
}}
|
|
573
|
+
title={t.pluginsPage.removeConfirm}
|
|
574
|
+
description={`This will remove the "${row.name}" plugin from your agent.`}
|
|
575
|
+
destructive
|
|
576
|
+
confirmLabel={t.common.delete}
|
|
577
|
+
/>
|
|
578
|
+
</Card>
|
|
579
|
+
);
|
|
580
|
+
}
|