@tradalab/lyra 2.6.0 → 3.0.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/dist/blocks/index.d.ts +222 -1
- package/dist/blocks/index.js +729 -15
- package/dist/blocks/index.js.map +1 -1
- package/dist/{chunk-HN3UZUCS.js → chunk-BTZYRWFX.js} +15 -34
- package/dist/chunk-BTZYRWFX.js.map +1 -0
- package/dist/{chunk-L6AHYXII.js → chunk-CV3UXQBT.js} +4 -60
- package/dist/chunk-CV3UXQBT.js.map +1 -0
- package/dist/chunk-DUWK53B5.js +642 -0
- package/dist/chunk-DUWK53B5.js.map +1 -0
- package/dist/chunk-FHM6VOPQ.js +72 -0
- package/dist/chunk-FHM6VOPQ.js.map +1 -0
- package/dist/{chunk-LZZ3UOPT.js → chunk-XJJQAQWJ.js} +24 -3
- package/dist/chunk-XJJQAQWJ.js.map +1 -0
- package/dist/data-table/index.js +2 -1
- package/dist/data-table/index.js.map +1 -1
- package/dist/shell/index.d.ts +8 -21
- package/dist/shell/index.js +31 -61
- package/dist/shell/index.js.map +1 -1
- package/dist/ui/index.d.ts +8 -1
- package/dist/ui/index.js +38 -389
- package/dist/ui/index.js.map +1 -1
- package/package.json +1 -1
- package/dist/chunk-HN3UZUCS.js.map +0 -1
- package/dist/chunk-L6AHYXII.js.map +0 -1
- package/dist/chunk-LZZ3UOPT.js.map +0 -1
- package/dist/chunk-VA6K3TP3.js +0 -281
- package/dist/chunk-VA6K3TP3.js.map +0 -1
package/dist/blocks/index.js
CHANGED
|
@@ -1,28 +1,29 @@
|
|
|
1
1
|
"use client";
|
|
2
|
-
import { Label, Combobox, ComboboxInput, ComboboxContent, ComboboxEmpty, ComboboxList, ComboboxItem } from '../chunk-
|
|
3
|
-
import {
|
|
2
|
+
import { Label, Combobox, ComboboxInput, ComboboxContent, ComboboxEmpty, ComboboxList, ComboboxItem, Spinner, Alert, AlertDescription, FieldGroup, FieldSet, FieldLegend, FieldDescription, Field, FieldContent, FieldLabel, Badge, Switch, FieldSeparator, Progress } from '../chunk-DUWK53B5.js';
|
|
3
|
+
import { NativeSelect, NativeSelectOption } from '../chunk-FHM6VOPQ.js';
|
|
4
|
+
import { Card, CardHeader, CardTitle, CardDescription, CardContent, CardFooter } from '../chunk-XJJQAQWJ.js';
|
|
4
5
|
import { Dialog, DialogOverlay, DialogContent, DialogTitle, DialogDescription } from '../chunk-XWGYXLIA.js';
|
|
5
6
|
import '../chunk-2HDU26FC.js';
|
|
6
|
-
import { Button } from '../chunk-VGOGWZCF.js';
|
|
7
|
+
import { Button, Input } from '../chunk-VGOGWZCF.js';
|
|
7
8
|
import { cn } from '../chunk-XH55BHUU.js';
|
|
8
|
-
import * as
|
|
9
|
+
import * as React5 from 'react';
|
|
9
10
|
import { createContext, useMemo, useState, useEffect, useContext, useCallback, useId } from 'react';
|
|
10
11
|
import { Slot } from 'radix-ui';
|
|
11
12
|
import { FormProvider, Controller, useFormContext } from 'react-hook-form';
|
|
12
|
-
import { jsx, jsxs } from 'react/jsx-runtime';
|
|
13
|
+
import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
|
|
13
14
|
import { useRouter, useSearchParams } from 'next/navigation';
|
|
14
|
-
import { ChevronRight, FolderOpen, Folder, File } from 'lucide-react';
|
|
15
|
+
import { ChevronRight, CheckIcon, CopyIcon, PlusIcon, FolderOpen, Folder, File } from 'lucide-react';
|
|
15
16
|
|
|
16
17
|
var Form = FormProvider;
|
|
17
|
-
var FormFieldContext =
|
|
18
|
+
var FormFieldContext = React5.createContext(null);
|
|
18
19
|
var FormField = ({
|
|
19
20
|
...props
|
|
20
21
|
}) => {
|
|
21
22
|
return /* @__PURE__ */ jsx(FormFieldContext.Provider, { value: { name: props.name }, children: /* @__PURE__ */ jsx(Controller, { ...props }) });
|
|
22
23
|
};
|
|
23
24
|
var useFormField = () => {
|
|
24
|
-
const fieldContext =
|
|
25
|
-
const itemContext =
|
|
25
|
+
const fieldContext = React5.useContext(FormFieldContext);
|
|
26
|
+
const itemContext = React5.useContext(FormItemContext);
|
|
26
27
|
const { getFieldState, formState } = useFormContext();
|
|
27
28
|
if (!fieldContext) {
|
|
28
29
|
throw new Error("useFormField should be used within <FormField>");
|
|
@@ -38,9 +39,9 @@ var useFormField = () => {
|
|
|
38
39
|
...fieldState
|
|
39
40
|
};
|
|
40
41
|
};
|
|
41
|
-
var FormItemContext =
|
|
42
|
+
var FormItemContext = React5.createContext(null);
|
|
42
43
|
function FormItem({ className, ...props }) {
|
|
43
|
-
const id =
|
|
44
|
+
const id = React5.useId();
|
|
44
45
|
return /* @__PURE__ */ jsx(FormItemContext.Provider, { value: { id }, children: /* @__PURE__ */ jsx("div", { "data-slot": "form-item", className: cn("grid gap-2", className), ...props }) });
|
|
45
46
|
}
|
|
46
47
|
function FormLabel({ className, ...props }) {
|
|
@@ -493,8 +494,8 @@ function TzPicker({
|
|
|
493
494
|
search: labels?.search ?? "Search timezone",
|
|
494
495
|
noMatch: labels?.noMatch ?? "No match"
|
|
495
496
|
};
|
|
496
|
-
const own =
|
|
497
|
-
const entries =
|
|
497
|
+
const own = React5.useMemo(() => browserTz(), []);
|
|
498
|
+
const entries = React5.useMemo(() => {
|
|
498
499
|
const zones = allZones();
|
|
499
500
|
const out = [];
|
|
500
501
|
if (allowInherit) out.push({ tz: "", name: l.inherit, offset: "" });
|
|
@@ -557,7 +558,7 @@ function OptionPicker({
|
|
|
557
558
|
}) {
|
|
558
559
|
const t = (k) => labels?.[k] ?? FALLBACK[k];
|
|
559
560
|
const placeholder = labels?.placeholder ?? FALLBACK.placeholder;
|
|
560
|
-
const entries =
|
|
561
|
+
const entries = React5.useMemo(() => {
|
|
561
562
|
const out = [{ id: "", label: placeholder, none: true }, ...options];
|
|
562
563
|
if (value !== "" && !options.some((o) => o.id === value)) {
|
|
563
564
|
out.push({ id: value, label: value });
|
|
@@ -587,7 +588,720 @@ function OptionPicker({
|
|
|
587
588
|
}
|
|
588
589
|
);
|
|
589
590
|
}
|
|
591
|
+
var FALLBACK2 = {
|
|
592
|
+
enable: "Allow MCP clients",
|
|
593
|
+
enableHint: "Let an AI agent such as Claude Desktop or Cursor use the tools this app offers.",
|
|
594
|
+
running: "Running",
|
|
595
|
+
stopped: "Off",
|
|
596
|
+
notOffered: "This build of the app offers no MCP tools.",
|
|
597
|
+
connect: "Connect a client",
|
|
598
|
+
connectHint: "Add this to the client's MCP configuration, then restart the client.",
|
|
599
|
+
copy: "Copy",
|
|
600
|
+
copied: "Copied",
|
|
601
|
+
copyConfig: "Copy configuration",
|
|
602
|
+
tools: "Tools",
|
|
603
|
+
toolsHint: "A tool turned off is hidden from every client.",
|
|
604
|
+
asksFirst: "Asks first",
|
|
605
|
+
asksFirstHint: "You confirm every call in a dialog.",
|
|
606
|
+
clients: "Allowed programs",
|
|
607
|
+
clientsHint: "A program is added when you allow it, the first time it runs a tool.",
|
|
608
|
+
clientsEmpty: "No program allowed yet.",
|
|
609
|
+
revoke: "Remove",
|
|
610
|
+
activity: "Recent calls",
|
|
611
|
+
activityEmpty: "Calls show up here while this screen is open.",
|
|
612
|
+
ok: "OK",
|
|
613
|
+
error: "Failed",
|
|
614
|
+
denied: "Denied",
|
|
615
|
+
auditLog: "Audit log",
|
|
616
|
+
auditLogHint: "Every call is written here, with credential-like arguments hidden.",
|
|
617
|
+
retry: "Retry"
|
|
618
|
+
};
|
|
619
|
+
var RECENT_CALLS = 20;
|
|
620
|
+
var OUTCOME_VARIANT = { ok: "secondary", error: "destructive", denied: "outline" };
|
|
621
|
+
function messageOf(err) {
|
|
622
|
+
return err instanceof Error ? err.message : String(err);
|
|
623
|
+
}
|
|
624
|
+
function baseName(path) {
|
|
625
|
+
return path.split(/[\\/]/).pop() || path;
|
|
626
|
+
}
|
|
627
|
+
function McpSettings({
|
|
628
|
+
api,
|
|
629
|
+
serverName,
|
|
630
|
+
copy,
|
|
631
|
+
labels,
|
|
632
|
+
locale,
|
|
633
|
+
className
|
|
634
|
+
}) {
|
|
635
|
+
const t = { ...FALLBACK2, ...labels };
|
|
636
|
+
const id = React5.useId();
|
|
637
|
+
const [status, setStatus] = React5.useState(null);
|
|
638
|
+
const [loadError, setLoadError] = React5.useState(null);
|
|
639
|
+
const [error, setError] = React5.useState(null);
|
|
640
|
+
const [busy, setBusy] = React5.useState(false);
|
|
641
|
+
const [copied, setCopied] = React5.useState(false);
|
|
642
|
+
const [calls, setCalls] = React5.useState([]);
|
|
643
|
+
const changes = React5.useRef(0);
|
|
644
|
+
const callKey = React5.useRef(0);
|
|
645
|
+
const load = React5.useCallback(() => {
|
|
646
|
+
setLoadError(null);
|
|
647
|
+
api.status().then(setStatus, (err) => setLoadError(messageOf(err)));
|
|
648
|
+
}, [api]);
|
|
649
|
+
React5.useEffect(load, [load]);
|
|
650
|
+
React5.useEffect(() => {
|
|
651
|
+
if (!api.onCall) return;
|
|
652
|
+
return api.onCall((call) => {
|
|
653
|
+
setCalls((prev) => [{ ...call, key: ++callKey.current, at: /* @__PURE__ */ new Date() }, ...prev].slice(0, RECENT_CALLS));
|
|
654
|
+
const started = changes.current;
|
|
655
|
+
api.status().then(
|
|
656
|
+
(next) => {
|
|
657
|
+
if (changes.current === started) setStatus(next);
|
|
658
|
+
},
|
|
659
|
+
// A refresh nobody asked for keeps the last answer on screen rather than raising an error.
|
|
660
|
+
() => {
|
|
661
|
+
}
|
|
662
|
+
);
|
|
663
|
+
});
|
|
664
|
+
}, [api]);
|
|
665
|
+
React5.useEffect(() => {
|
|
666
|
+
if (!copied) return;
|
|
667
|
+
const timer = setTimeout(() => setCopied(false), 2e3);
|
|
668
|
+
return () => clearTimeout(timer);
|
|
669
|
+
}, [copied]);
|
|
670
|
+
const change = async (action) => {
|
|
671
|
+
changes.current++;
|
|
672
|
+
setBusy(true);
|
|
673
|
+
setError(null);
|
|
674
|
+
try {
|
|
675
|
+
setStatus(await action());
|
|
676
|
+
} catch (err) {
|
|
677
|
+
setError(messageOf(err));
|
|
678
|
+
} finally {
|
|
679
|
+
changes.current++;
|
|
680
|
+
setBusy(false);
|
|
681
|
+
}
|
|
682
|
+
};
|
|
683
|
+
if (!status) {
|
|
684
|
+
if (!loadError) return /* @__PURE__ */ jsx(Spinner, { className });
|
|
685
|
+
return /* @__PURE__ */ jsx(Alert, { variant: "destructive", className, children: /* @__PURE__ */ jsxs(AlertDescription, { className: "flex items-center justify-between gap-3", children: [
|
|
686
|
+
/* @__PURE__ */ jsx("span", { children: loadError }),
|
|
687
|
+
/* @__PURE__ */ jsx(Button, { size: "xs", variant: "outline", onClick: load, children: t.retry })
|
|
688
|
+
] }) });
|
|
689
|
+
}
|
|
690
|
+
if (!status.offered) {
|
|
691
|
+
return /* @__PURE__ */ jsx(FieldGroup, { className, children: /* @__PURE__ */ jsxs(FieldSet, { className: "min-w-0", children: [
|
|
692
|
+
/* @__PURE__ */ jsx(FieldLegend, { children: t.enable }),
|
|
693
|
+
/* @__PURE__ */ jsx(FieldDescription, { children: t.notOffered })
|
|
694
|
+
] }) });
|
|
695
|
+
}
|
|
696
|
+
const snippet = JSON.stringify(
|
|
697
|
+
{ mcpServers: { [serverName]: { command: status.client.command, args: status.client.args } } },
|
|
698
|
+
null,
|
|
699
|
+
2
|
|
700
|
+
);
|
|
701
|
+
const day = (iso) => {
|
|
702
|
+
const d = new Date(iso);
|
|
703
|
+
return Number.isNaN(d.getTime()) ? iso : d.toLocaleDateString(locale);
|
|
704
|
+
};
|
|
705
|
+
return (
|
|
706
|
+
// min-w-0 on every FieldSet: a fieldset is min-content wide by default, so one long path or
|
|
707
|
+
// config line would widen the whole screen instead of truncating or scrolling.
|
|
708
|
+
/* @__PURE__ */ jsxs(FieldGroup, { className, children: [
|
|
709
|
+
error ? /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }) : null,
|
|
710
|
+
/* @__PURE__ */ jsx(FieldSet, { className: "min-w-0", children: /* @__PURE__ */ jsxs(Field, { orientation: "horizontal", children: [
|
|
711
|
+
/* @__PURE__ */ jsxs(FieldContent, { children: [
|
|
712
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
713
|
+
/* @__PURE__ */ jsx(FieldLabel, { htmlFor: `${id}-enable`, className: "text-sm", children: t.enable }),
|
|
714
|
+
/* @__PURE__ */ jsx(Badge, { variant: "outline", children: status.running ? t.running : t.stopped })
|
|
715
|
+
] }),
|
|
716
|
+
/* @__PURE__ */ jsx(FieldDescription, { children: t.enableHint })
|
|
717
|
+
] }),
|
|
718
|
+
/* @__PURE__ */ jsx(
|
|
719
|
+
Switch,
|
|
720
|
+
{
|
|
721
|
+
id: `${id}-enable`,
|
|
722
|
+
checked: status.enabled,
|
|
723
|
+
disabled: busy,
|
|
724
|
+
onCheckedChange: (on) => void change(() => api.enable(on))
|
|
725
|
+
}
|
|
726
|
+
)
|
|
727
|
+
] }) }),
|
|
728
|
+
/* @__PURE__ */ jsx(FieldSeparator, {}),
|
|
729
|
+
/* @__PURE__ */ jsxs(FieldSet, { className: "min-w-0", children: [
|
|
730
|
+
/* @__PURE__ */ jsx(FieldLegend, { children: t.connect }),
|
|
731
|
+
/* @__PURE__ */ jsx(FieldDescription, { children: t.connectHint }),
|
|
732
|
+
/* @__PURE__ */ jsxs("div", { className: "relative", children: [
|
|
733
|
+
/* @__PURE__ */ jsx("pre", { className: "bg-muted/50 overflow-x-auto border px-3 py-2 font-mono text-xs leading-relaxed", children: snippet }),
|
|
734
|
+
copy ? /* @__PURE__ */ jsxs(
|
|
735
|
+
Button,
|
|
736
|
+
{
|
|
737
|
+
size: "xs",
|
|
738
|
+
variant: "outline",
|
|
739
|
+
className: "bg-background absolute top-2 right-2",
|
|
740
|
+
"aria-label": t.copyConfig,
|
|
741
|
+
onClick: () => void copy(snippet).then(
|
|
742
|
+
() => setCopied(true),
|
|
743
|
+
(err) => setError(messageOf(err))
|
|
744
|
+
),
|
|
745
|
+
children: [
|
|
746
|
+
copied ? /* @__PURE__ */ jsx(CheckIcon, {}) : /* @__PURE__ */ jsx(CopyIcon, {}),
|
|
747
|
+
copied ? t.copied : t.copy
|
|
748
|
+
]
|
|
749
|
+
}
|
|
750
|
+
) : null
|
|
751
|
+
] })
|
|
752
|
+
] }),
|
|
753
|
+
/* @__PURE__ */ jsx(FieldSeparator, {}),
|
|
754
|
+
/* @__PURE__ */ jsxs(FieldSet, { className: "min-w-0", children: [
|
|
755
|
+
/* @__PURE__ */ jsx(FieldLegend, { children: t.tools }),
|
|
756
|
+
/* @__PURE__ */ jsx(FieldDescription, { children: t.toolsHint }),
|
|
757
|
+
status.tools.map((tool) => /* @__PURE__ */ jsxs(Field, { orientation: "horizontal", "data-disabled": !status.enabled, children: [
|
|
758
|
+
/* @__PURE__ */ jsxs(FieldContent, { children: [
|
|
759
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
760
|
+
/* @__PURE__ */ jsx(FieldLabel, { htmlFor: `${id}-tool-${tool.name}`, className: "font-mono text-xs", children: tool.name }),
|
|
761
|
+
tool.destructive ? /* @__PURE__ */ jsx(Badge, { variant: "destructive", title: t.asksFirstHint, children: t.asksFirst }) : null
|
|
762
|
+
] }),
|
|
763
|
+
/* @__PURE__ */ jsx(FieldDescription, { children: tool.description })
|
|
764
|
+
] }),
|
|
765
|
+
/* @__PURE__ */ jsx(
|
|
766
|
+
Switch,
|
|
767
|
+
{
|
|
768
|
+
id: `${id}-tool-${tool.name}`,
|
|
769
|
+
size: "sm",
|
|
770
|
+
checked: tool.enabled,
|
|
771
|
+
disabled: busy || !status.enabled,
|
|
772
|
+
onCheckedChange: (on) => void change(() => api.setTool(tool.name, on))
|
|
773
|
+
}
|
|
774
|
+
)
|
|
775
|
+
] }, tool.name))
|
|
776
|
+
] }),
|
|
777
|
+
/* @__PURE__ */ jsx(FieldSeparator, {}),
|
|
778
|
+
/* @__PURE__ */ jsxs(FieldSet, { className: "min-w-0", children: [
|
|
779
|
+
/* @__PURE__ */ jsx(FieldLegend, { children: t.clients }),
|
|
780
|
+
/* @__PURE__ */ jsx(FieldDescription, { children: t.clientsHint }),
|
|
781
|
+
status.clients.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: t.clientsEmpty }) : /* @__PURE__ */ jsx("ul", { className: "divide-y border", children: status.clients.map((client) => {
|
|
782
|
+
const who = client.name || baseName(client.path);
|
|
783
|
+
return /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 px-3 py-2", children: [
|
|
784
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
785
|
+
/* @__PURE__ */ jsx("div", { className: "truncate text-sm", children: who }),
|
|
786
|
+
/* @__PURE__ */ jsx(
|
|
787
|
+
"div",
|
|
788
|
+
{
|
|
789
|
+
className: "text-muted-foreground truncate text-left font-mono text-xs [direction:rtl]",
|
|
790
|
+
title: client.path,
|
|
791
|
+
children: /* @__PURE__ */ jsx("bdi", { children: client.path })
|
|
792
|
+
}
|
|
793
|
+
)
|
|
794
|
+
] }),
|
|
795
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs whitespace-nowrap", children: day(client.approved_at) }),
|
|
796
|
+
/* @__PURE__ */ jsx(
|
|
797
|
+
Button,
|
|
798
|
+
{
|
|
799
|
+
size: "xs",
|
|
800
|
+
variant: "ghost",
|
|
801
|
+
"aria-label": `${t.revoke} ${who}`,
|
|
802
|
+
disabled: busy,
|
|
803
|
+
onClick: () => void change(() => api.revokeClient(client.path)),
|
|
804
|
+
children: t.revoke
|
|
805
|
+
}
|
|
806
|
+
)
|
|
807
|
+
] }, client.path);
|
|
808
|
+
}) })
|
|
809
|
+
] }),
|
|
810
|
+
api.onCall ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
811
|
+
/* @__PURE__ */ jsx(FieldSeparator, {}),
|
|
812
|
+
/* @__PURE__ */ jsxs(FieldSet, { className: "min-w-0", children: [
|
|
813
|
+
/* @__PURE__ */ jsx(FieldLegend, { children: t.activity }),
|
|
814
|
+
calls.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: t.activityEmpty }) : /* @__PURE__ */ jsx("ul", { "aria-label": t.activity, className: "divide-y border", children: calls.map((call) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 px-3 py-1.5 text-xs", title: call.error, children: [
|
|
815
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground tabular-nums", children: call.at.toLocaleTimeString(locale) }),
|
|
816
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate font-mono", children: call.tool }),
|
|
817
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground max-w-40 truncate", title: call.client_path, children: call.client || baseName(call.client_path) }),
|
|
818
|
+
/* @__PURE__ */ jsx(Badge, { variant: OUTCOME_VARIANT[call.outcome], children: t[call.outcome] }),
|
|
819
|
+
/* @__PURE__ */ jsxs("span", { className: "text-muted-foreground w-14 text-right tabular-nums", children: [
|
|
820
|
+
call.ms,
|
|
821
|
+
" ms"
|
|
822
|
+
] })
|
|
823
|
+
] }, call.key)) })
|
|
824
|
+
] })
|
|
825
|
+
] }) : null,
|
|
826
|
+
/* @__PURE__ */ jsx(FieldSeparator, {}),
|
|
827
|
+
/* @__PURE__ */ jsxs(FieldSet, { className: "min-w-0", children: [
|
|
828
|
+
/* @__PURE__ */ jsx(FieldLegend, { children: t.auditLog }),
|
|
829
|
+
/* @__PURE__ */ jsx(FieldDescription, { children: t.auditLogHint }),
|
|
830
|
+
/* @__PURE__ */ jsx(
|
|
831
|
+
"code",
|
|
832
|
+
{
|
|
833
|
+
className: "bg-muted/50 block truncate border px-3 py-2 text-left font-mono text-xs [direction:rtl]",
|
|
834
|
+
title: status.audit_log,
|
|
835
|
+
children: /* @__PURE__ */ jsx("bdi", { children: status.audit_log })
|
|
836
|
+
}
|
|
837
|
+
)
|
|
838
|
+
] })
|
|
839
|
+
] })
|
|
840
|
+
);
|
|
841
|
+
}
|
|
842
|
+
var FALLBACK3 = {
|
|
843
|
+
slots: "Models for this app",
|
|
844
|
+
slotsHint: "Each feature uses the provider and model chosen here.",
|
|
845
|
+
notSet: "Not set",
|
|
846
|
+
model: "Model",
|
|
847
|
+
modelHint: "Type a name or pick one the provider has.",
|
|
848
|
+
localOnly: "This machine only",
|
|
849
|
+
localOnlyHint: "What this feature reads never leaves the computer.",
|
|
850
|
+
download: "Download",
|
|
851
|
+
notDownloaded: "Not on this computer yet",
|
|
852
|
+
cancel: "Cancel",
|
|
853
|
+
providers: "Providers",
|
|
854
|
+
providersHint: "Services and programs that run models.",
|
|
855
|
+
providersEmpty: "No provider yet.",
|
|
856
|
+
add: "Add provider",
|
|
857
|
+
edit: "Edit",
|
|
858
|
+
remove: "Remove",
|
|
859
|
+
removeConfirm: "Remove",
|
|
860
|
+
keep: "Keep",
|
|
861
|
+
byApp: "run by the app",
|
|
862
|
+
thisMachine: "this machine",
|
|
863
|
+
remote: "remote",
|
|
864
|
+
keySaved: "key saved",
|
|
865
|
+
driver: "Type",
|
|
866
|
+
id: "Name",
|
|
867
|
+
idHint: "Lowercase letters, digits, dot, dash and underscore.",
|
|
868
|
+
save: "Save",
|
|
869
|
+
keyKeep: "Saved; leave empty to keep it",
|
|
870
|
+
keyRemove: "Remove key",
|
|
871
|
+
running: "Running on this computer",
|
|
872
|
+
addFound: "Add",
|
|
873
|
+
retry: "Retry"
|
|
874
|
+
};
|
|
875
|
+
var CAPABILITY = {
|
|
876
|
+
chat: "chat",
|
|
877
|
+
embed: "embeddings",
|
|
878
|
+
vision: "vision",
|
|
879
|
+
audio: "audio",
|
|
880
|
+
tools: "tools",
|
|
881
|
+
transcribe: "speech to text"
|
|
882
|
+
};
|
|
883
|
+
function messageOf2(err) {
|
|
884
|
+
return err instanceof Error ? err.message : String(err);
|
|
885
|
+
}
|
|
886
|
+
function hostOf(url) {
|
|
887
|
+
if (!url) return "";
|
|
888
|
+
try {
|
|
889
|
+
const u = new URL(url);
|
|
890
|
+
return u.host + (u.pathname === "/" ? "" : u.pathname);
|
|
891
|
+
} catch {
|
|
892
|
+
return url;
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
function sizeOf(bytes) {
|
|
896
|
+
if (bytes >= 1 << 30) return `${(bytes / (1 << 30)).toFixed(1)} GB`;
|
|
897
|
+
if (bytes >= 1 << 20) return `${Math.round(bytes / (1 << 20))} MB`;
|
|
898
|
+
return `${Math.ceil(bytes / 1024)} KB`;
|
|
899
|
+
}
|
|
900
|
+
function serves(slot, p) {
|
|
901
|
+
return slot.needs.every((c) => p.capabilities.includes(c)) && (!slot.local_only || p.local);
|
|
902
|
+
}
|
|
903
|
+
var pullKey = (provider, model) => `${provider}/${model}`;
|
|
904
|
+
function LlmSettings({
|
|
905
|
+
api,
|
|
906
|
+
labels,
|
|
907
|
+
className
|
|
908
|
+
}) {
|
|
909
|
+
const t = { ...FALLBACK3, ...labels };
|
|
910
|
+
const cap = (c) => labels?.capabilities?.[c] ?? CAPABILITY[c] ?? c;
|
|
911
|
+
const id = React5.useId();
|
|
912
|
+
const [state, setState] = React5.useState(null);
|
|
913
|
+
const [drivers, setDrivers] = React5.useState([]);
|
|
914
|
+
const [found, setFound] = React5.useState([]);
|
|
915
|
+
const [loadError, setLoadError] = React5.useState(null);
|
|
916
|
+
const [error, setError] = React5.useState(null);
|
|
917
|
+
const [busy, setBusy] = React5.useState(false);
|
|
918
|
+
const [draft, setDraft] = React5.useState(null);
|
|
919
|
+
const [draftError, setDraftError] = React5.useState(null);
|
|
920
|
+
const [removing, setRemoving] = React5.useState(null);
|
|
921
|
+
const [models, setModels] = React5.useState({});
|
|
922
|
+
const [typed, setTyped] = React5.useState({});
|
|
923
|
+
const [pulls, setPulls] = React5.useState({});
|
|
924
|
+
const load = React5.useCallback(() => {
|
|
925
|
+
setLoadError(null);
|
|
926
|
+
Promise.all([api.state(), api.drivers()]).then(
|
|
927
|
+
([s, d]) => {
|
|
928
|
+
setState(s);
|
|
929
|
+
setDrivers(d);
|
|
930
|
+
},
|
|
931
|
+
(err) => setLoadError(messageOf2(err))
|
|
932
|
+
);
|
|
933
|
+
api.detect?.().then(setFound, () => {
|
|
934
|
+
});
|
|
935
|
+
}, [api]);
|
|
936
|
+
React5.useEffect(load, [load]);
|
|
937
|
+
React5.useEffect(() => {
|
|
938
|
+
if (!api.onPull) return;
|
|
939
|
+
return api.onPull((p) => setPulls((prev) => ({ ...prev, [pullKey(p.provider, p.model)]: p })));
|
|
940
|
+
}, [api]);
|
|
941
|
+
const listModels = React5.useCallback(
|
|
942
|
+
(provider2) => {
|
|
943
|
+
api.models(provider2).then(
|
|
944
|
+
(ms) => setModels((prev) => ({ ...prev, [provider2]: ms })),
|
|
945
|
+
// A provider that cannot list its models still takes a typed name.
|
|
946
|
+
() => setModels((prev) => ({ ...prev, [provider2]: [] }))
|
|
947
|
+
);
|
|
948
|
+
},
|
|
949
|
+
[api]
|
|
950
|
+
);
|
|
951
|
+
const bound = React5.useMemo(() => new Set((state?.slots ?? []).map((s) => s.provider).filter(Boolean)), [state]);
|
|
952
|
+
React5.useEffect(() => {
|
|
953
|
+
for (const p of bound) if (!(p in models)) listModels(p);
|
|
954
|
+
}, [bound, models, listModels]);
|
|
955
|
+
const change = async (action) => {
|
|
956
|
+
setBusy(true);
|
|
957
|
+
setError(null);
|
|
958
|
+
try {
|
|
959
|
+
setState(await action());
|
|
960
|
+
} catch (err) {
|
|
961
|
+
setError(messageOf2(err));
|
|
962
|
+
} finally {
|
|
963
|
+
setBusy(false);
|
|
964
|
+
}
|
|
965
|
+
};
|
|
966
|
+
if (!state) {
|
|
967
|
+
if (!loadError) return /* @__PURE__ */ jsx(Spinner, { className });
|
|
968
|
+
return /* @__PURE__ */ jsx(Alert, { variant: "destructive", className, children: /* @__PURE__ */ jsxs(AlertDescription, { className: "flex items-center justify-between gap-3", children: [
|
|
969
|
+
/* @__PURE__ */ jsx("span", { children: loadError }),
|
|
970
|
+
/* @__PURE__ */ jsx(Button, { size: "xs", variant: "outline", onClick: load, children: t.retry })
|
|
971
|
+
] }) });
|
|
972
|
+
}
|
|
973
|
+
const provider = (pid) => state.providers.find((p) => p.id === pid);
|
|
974
|
+
const saveDraft = async () => {
|
|
975
|
+
if (!draft) return;
|
|
976
|
+
const d = drivers.find((x) => x.name === draft.driver);
|
|
977
|
+
const values = {};
|
|
978
|
+
const secrets = {};
|
|
979
|
+
for (const f of d?.fields ?? []) {
|
|
980
|
+
if (f.kind !== "secret") {
|
|
981
|
+
if (draft.values[f.key]) values[f.key] = draft.values[f.key];
|
|
982
|
+
} else if (f.key in draft.secrets) {
|
|
983
|
+
secrets[f.key] = draft.secrets[f.key];
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
setBusy(true);
|
|
987
|
+
setDraftError(null);
|
|
988
|
+
try {
|
|
989
|
+
setState(await api.saveProvider({ id: draft.id, driver: draft.driver, values, secrets }));
|
|
990
|
+
setModels((prev) => {
|
|
991
|
+
const next = { ...prev };
|
|
992
|
+
delete next[draft.id];
|
|
993
|
+
return next;
|
|
994
|
+
});
|
|
995
|
+
setDraft(null);
|
|
996
|
+
} catch (err) {
|
|
997
|
+
setDraftError(messageOf2(err));
|
|
998
|
+
} finally {
|
|
999
|
+
setBusy(false);
|
|
1000
|
+
}
|
|
1001
|
+
};
|
|
1002
|
+
const startDraft = (next) => {
|
|
1003
|
+
setDraft(next);
|
|
1004
|
+
setDraftError(null);
|
|
1005
|
+
setRemoving(null);
|
|
1006
|
+
};
|
|
1007
|
+
const newDraft = (driver, values = {}, name = driver) => {
|
|
1008
|
+
const taken = new Set(state.providers.map((p) => p.id));
|
|
1009
|
+
let pid = name;
|
|
1010
|
+
for (let i = 2; taken.has(pid); i++) pid = `${name}-${i}`;
|
|
1011
|
+
return { driver, id: pid, values, secrets: {} };
|
|
1012
|
+
};
|
|
1013
|
+
const form = (d) => {
|
|
1014
|
+
const spec = drivers.find((x) => x.name === d.driver);
|
|
1015
|
+
const set = (patch) => setDraft({ ...d, ...patch });
|
|
1016
|
+
return /* @__PURE__ */ jsxs("div", { className: "bg-muted/30 flex flex-col gap-3 border px-3 py-3", "data-testid": "provider-form", children: [
|
|
1017
|
+
draftError ? /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: draftError }) }) : null,
|
|
1018
|
+
d.existing ? null : /* @__PURE__ */ jsxs("div", { className: "grid gap-3 sm:grid-cols-2", children: [
|
|
1019
|
+
/* @__PURE__ */ jsxs(Field, { children: [
|
|
1020
|
+
/* @__PURE__ */ jsx(FieldLabel, { htmlFor: `${id}-driver`, children: t.driver }),
|
|
1021
|
+
/* @__PURE__ */ jsx(NativeSelect, { id: `${id}-driver`, value: d.driver, onChange: (e) => set(newDraft(e.target.value)), children: drivers.map((x) => /* @__PURE__ */ jsx(NativeSelectOption, { value: x.name, children: x.name }, x.name)) })
|
|
1022
|
+
] }),
|
|
1023
|
+
/* @__PURE__ */ jsxs(Field, { children: [
|
|
1024
|
+
/* @__PURE__ */ jsx(FieldLabel, { htmlFor: `${id}-id`, children: t.id }),
|
|
1025
|
+
/* @__PURE__ */ jsx(Input, { id: `${id}-id`, value: d.id, spellCheck: false, onChange: (e) => set({ id: e.target.value.toLowerCase() }) }),
|
|
1026
|
+
/* @__PURE__ */ jsx(FieldDescription, { children: t.idHint })
|
|
1027
|
+
] })
|
|
1028
|
+
] }),
|
|
1029
|
+
spec?.fields.map((f) => {
|
|
1030
|
+
const fid = `${id}-f-${f.key}`;
|
|
1031
|
+
if (f.kind === "bool") {
|
|
1032
|
+
const on = (d.values[f.key] ?? f.default ?? "false") === "true";
|
|
1033
|
+
return /* @__PURE__ */ jsxs(Field, { orientation: "horizontal", children: [
|
|
1034
|
+
/* @__PURE__ */ jsxs(FieldContent, { children: [
|
|
1035
|
+
/* @__PURE__ */ jsx(FieldLabel, { htmlFor: fid, children: f.label }),
|
|
1036
|
+
f.description ? /* @__PURE__ */ jsx(FieldDescription, { children: f.description }) : null
|
|
1037
|
+
] }),
|
|
1038
|
+
/* @__PURE__ */ jsx(Switch, { id: fid, size: "sm", checked: on, onCheckedChange: (v) => set({ values: { ...d.values, [f.key]: v ? "true" : "false" } }) })
|
|
1039
|
+
] }, f.key);
|
|
1040
|
+
}
|
|
1041
|
+
if (f.kind === "secret") {
|
|
1042
|
+
const saved = d.existing?.secrets.includes(f.key) ?? false;
|
|
1043
|
+
const cleared = d.secrets[f.key] === "" && saved;
|
|
1044
|
+
return /* @__PURE__ */ jsxs(Field, { children: [
|
|
1045
|
+
/* @__PURE__ */ jsx(FieldLabel, { htmlFor: fid, children: f.label }),
|
|
1046
|
+
/* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
|
|
1047
|
+
/* @__PURE__ */ jsx(
|
|
1048
|
+
Input,
|
|
1049
|
+
{
|
|
1050
|
+
id: fid,
|
|
1051
|
+
type: "password",
|
|
1052
|
+
autoComplete: "off",
|
|
1053
|
+
placeholder: saved && !cleared ? t.keyKeep : void 0,
|
|
1054
|
+
value: d.secrets[f.key] ?? "",
|
|
1055
|
+
onChange: (e) => {
|
|
1056
|
+
const secrets = { ...d.secrets, [f.key]: e.target.value };
|
|
1057
|
+
if (e.target.value === "" && !cleared) delete secrets[f.key];
|
|
1058
|
+
set({ secrets });
|
|
1059
|
+
}
|
|
1060
|
+
}
|
|
1061
|
+
),
|
|
1062
|
+
saved && !cleared ? /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => set({ secrets: { ...d.secrets, [f.key]: "" } }), children: t.keyRemove }) : null
|
|
1063
|
+
] }),
|
|
1064
|
+
f.description ? /* @__PURE__ */ jsx(FieldDescription, { children: f.description }) : null
|
|
1065
|
+
] }, f.key);
|
|
1066
|
+
}
|
|
1067
|
+
return /* @__PURE__ */ jsxs(Field, { children: [
|
|
1068
|
+
/* @__PURE__ */ jsx(FieldLabel, { htmlFor: fid, children: f.label }),
|
|
1069
|
+
/* @__PURE__ */ jsx(
|
|
1070
|
+
Input,
|
|
1071
|
+
{
|
|
1072
|
+
id: fid,
|
|
1073
|
+
type: f.kind === "url" ? "url" : "text",
|
|
1074
|
+
spellCheck: false,
|
|
1075
|
+
placeholder: f.default,
|
|
1076
|
+
required: f.required,
|
|
1077
|
+
value: d.values[f.key] ?? "",
|
|
1078
|
+
onChange: (e) => set({ values: { ...d.values, [f.key]: e.target.value } })
|
|
1079
|
+
}
|
|
1080
|
+
),
|
|
1081
|
+
f.description ? /* @__PURE__ */ jsx(FieldDescription, { children: f.description }) : null
|
|
1082
|
+
] }, f.key);
|
|
1083
|
+
}),
|
|
1084
|
+
/* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
|
|
1085
|
+
/* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", disabled: busy, onClick: () => setDraft(null), children: t.cancel }),
|
|
1086
|
+
/* @__PURE__ */ jsx(Button, { size: "sm", disabled: busy || !d.id, onClick: () => void saveDraft(), children: t.save })
|
|
1087
|
+
] })
|
|
1088
|
+
] });
|
|
1089
|
+
};
|
|
1090
|
+
const unused = found.filter((f) => !state.providers.some((p) => p.values.base_url === f.base_url));
|
|
1091
|
+
return (
|
|
1092
|
+
// min-w-0 on every FieldSet: a fieldset is min-content wide by default, so one long URL
|
|
1093
|
+
// would widen the whole screen instead of truncating.
|
|
1094
|
+
/* @__PURE__ */ jsxs(FieldGroup, { className, children: [
|
|
1095
|
+
error ? /* @__PURE__ */ jsx(Alert, { variant: "destructive", children: /* @__PURE__ */ jsx(AlertDescription, { children: error }) }) : null,
|
|
1096
|
+
/* @__PURE__ */ jsxs(FieldSet, { className: "min-w-0", children: [
|
|
1097
|
+
/* @__PURE__ */ jsx(FieldLegend, { children: t.slots }),
|
|
1098
|
+
/* @__PURE__ */ jsx(FieldDescription, { children: t.slotsHint }),
|
|
1099
|
+
/* @__PURE__ */ jsx("ul", { className: "divide-y border", children: state.slots.map((slot) => {
|
|
1100
|
+
const current = provider(slot.provider);
|
|
1101
|
+
const choices = state.providers.filter((p) => p.id === slot.provider || serves(slot, p));
|
|
1102
|
+
const known = slot.provider ? models[slot.provider] : void 0;
|
|
1103
|
+
const draftModel = typed[slot.name] ?? slot.model ?? "";
|
|
1104
|
+
const chosen = slot.model ?? "";
|
|
1105
|
+
const pull = current ? pulls[pullKey(current.id, chosen)] : void 0;
|
|
1106
|
+
const pulling = pull !== void 0;
|
|
1107
|
+
const missing = known !== void 0 && chosen !== "" && !known.some((m) => m.id === chosen);
|
|
1108
|
+
const commit = () => {
|
|
1109
|
+
if (!slot.provider || draftModel === (slot.model ?? "")) return;
|
|
1110
|
+
void change(() => api.bind(slot.name, slot.provider, draftModel));
|
|
1111
|
+
};
|
|
1112
|
+
return /* @__PURE__ */ jsxs("li", { className: "flex flex-col gap-2 px-3 py-3", "data-testid": `slot-${slot.name}`, children: [
|
|
1113
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start gap-x-6 gap-y-2", children: [
|
|
1114
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-40 flex-1", children: [
|
|
1115
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1116
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: slot.label }),
|
|
1117
|
+
slot.local_only ? /* @__PURE__ */ jsx(Badge, { variant: "outline", title: t.localOnlyHint, children: t.localOnly }) : null
|
|
1118
|
+
] }),
|
|
1119
|
+
slot.description ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: slot.description }) : null
|
|
1120
|
+
] }),
|
|
1121
|
+
/* @__PURE__ */ jsxs("div", { className: "flex w-full gap-2 sm:w-auto", children: [
|
|
1122
|
+
/* @__PURE__ */ jsxs(
|
|
1123
|
+
NativeSelect,
|
|
1124
|
+
{
|
|
1125
|
+
"aria-label": `${slot.label}: ${t.providers}`,
|
|
1126
|
+
className: "sm:w-40",
|
|
1127
|
+
value: slot.provider ?? "",
|
|
1128
|
+
disabled: busy,
|
|
1129
|
+
onChange: (e) => {
|
|
1130
|
+
const next = e.target.value;
|
|
1131
|
+
setTyped((prev) => {
|
|
1132
|
+
const rest = { ...prev };
|
|
1133
|
+
delete rest[slot.name];
|
|
1134
|
+
return rest;
|
|
1135
|
+
});
|
|
1136
|
+
void change(() => next ? api.bind(slot.name, next, "") : api.unbind(slot.name));
|
|
1137
|
+
},
|
|
1138
|
+
children: [
|
|
1139
|
+
/* @__PURE__ */ jsx(NativeSelectOption, { value: "", children: t.notSet }),
|
|
1140
|
+
choices.map((p) => /* @__PURE__ */ jsx(NativeSelectOption, { value: p.id, children: p.id }, p.id))
|
|
1141
|
+
]
|
|
1142
|
+
}
|
|
1143
|
+
),
|
|
1144
|
+
/* @__PURE__ */ jsx(
|
|
1145
|
+
Input,
|
|
1146
|
+
{
|
|
1147
|
+
"aria-label": `${slot.label}: ${t.model}`,
|
|
1148
|
+
title: t.modelHint,
|
|
1149
|
+
className: "sm:w-56 [&::-webkit-calendar-picker-indicator]:opacity-30",
|
|
1150
|
+
list: `${id}-models-${slot.name}`,
|
|
1151
|
+
spellCheck: false,
|
|
1152
|
+
placeholder: t.model,
|
|
1153
|
+
disabled: busy || !slot.provider,
|
|
1154
|
+
value: draftModel,
|
|
1155
|
+
onChange: (e) => setTyped((prev) => ({ ...prev, [slot.name]: e.target.value })),
|
|
1156
|
+
onBlur: commit,
|
|
1157
|
+
onKeyDown: (e) => {
|
|
1158
|
+
if (e.key === "Enter") commit();
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
),
|
|
1162
|
+
/* @__PURE__ */ jsx("datalist", { id: `${id}-models-${slot.name}`, children: known?.map((m) => /* @__PURE__ */ jsx("option", { value: m.id, children: [m.parameter_size, m.quantization, m.size ? sizeOf(m.size) : ""].filter(Boolean).join(" \xB7 ") }, m.id)) })
|
|
1163
|
+
] })
|
|
1164
|
+
] }),
|
|
1165
|
+
slot.error ? /* @__PURE__ */ jsx("p", { className: "text-destructive text-xs", children: slot.error }) : null,
|
|
1166
|
+
current?.manages && api.pull && missing && !pulling ? /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-3", children: [
|
|
1167
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: t.notDownloaded }),
|
|
1168
|
+
/* @__PURE__ */ jsxs(
|
|
1169
|
+
Button,
|
|
1170
|
+
{
|
|
1171
|
+
size: "xs",
|
|
1172
|
+
variant: "outline",
|
|
1173
|
+
disabled: busy,
|
|
1174
|
+
onClick: () => {
|
|
1175
|
+
const key = pullKey(current.id, chosen);
|
|
1176
|
+
const end = () => setPulls((prev) => {
|
|
1177
|
+
const rest = { ...prev };
|
|
1178
|
+
delete rest[key];
|
|
1179
|
+
return rest;
|
|
1180
|
+
});
|
|
1181
|
+
setPulls((prev) => ({ ...prev, [key]: { provider: current.id, model: chosen, status: "", done: 0, total: 0 } }));
|
|
1182
|
+
api.pull?.(current.id, chosen).then(
|
|
1183
|
+
(done) => {
|
|
1184
|
+
end();
|
|
1185
|
+
if (done) listModels(current.id);
|
|
1186
|
+
},
|
|
1187
|
+
(err) => {
|
|
1188
|
+
end();
|
|
1189
|
+
setError(messageOf2(err));
|
|
1190
|
+
}
|
|
1191
|
+
);
|
|
1192
|
+
},
|
|
1193
|
+
children: [
|
|
1194
|
+
t.download,
|
|
1195
|
+
" ",
|
|
1196
|
+
chosen
|
|
1197
|
+
]
|
|
1198
|
+
}
|
|
1199
|
+
)
|
|
1200
|
+
] }) : null,
|
|
1201
|
+
pulling && current ? /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", "data-testid": `pull-${slot.name}`, children: [
|
|
1202
|
+
/* @__PURE__ */ jsx(Progress, { className: "flex-1", value: pull.total > 0 ? pull.done / pull.total * 100 : 0 }),
|
|
1203
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground w-28 text-right text-xs tabular-nums", children: pull.total > 0 ? `${sizeOf(pull.done)} / ${sizeOf(pull.total)}` : pull.status }),
|
|
1204
|
+
api.cancelPull ? /* @__PURE__ */ jsx(Button, { size: "xs", variant: "ghost", onClick: () => void api.cancelPull?.(current.id, pull.model), children: t.cancel }) : null
|
|
1205
|
+
] }) : null
|
|
1206
|
+
] }, slot.name);
|
|
1207
|
+
}) })
|
|
1208
|
+
] }),
|
|
1209
|
+
/* @__PURE__ */ jsx(FieldSeparator, {}),
|
|
1210
|
+
/* @__PURE__ */ jsxs(FieldSet, { className: "min-w-0", children: [
|
|
1211
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start justify-between gap-3", children: [
|
|
1212
|
+
/* @__PURE__ */ jsxs("div", { children: [
|
|
1213
|
+
/* @__PURE__ */ jsx(FieldLegend, { children: t.providers }),
|
|
1214
|
+
/* @__PURE__ */ jsx(FieldDescription, { children: t.providersHint })
|
|
1215
|
+
] }),
|
|
1216
|
+
/* @__PURE__ */ jsxs(
|
|
1217
|
+
Button,
|
|
1218
|
+
{
|
|
1219
|
+
size: "sm",
|
|
1220
|
+
variant: "outline",
|
|
1221
|
+
disabled: busy || drivers.length === 0,
|
|
1222
|
+
onClick: () => startDraft(newDraft(drivers[0]?.name ?? "")),
|
|
1223
|
+
children: [
|
|
1224
|
+
/* @__PURE__ */ jsx(PlusIcon, {}),
|
|
1225
|
+
t.add
|
|
1226
|
+
]
|
|
1227
|
+
}
|
|
1228
|
+
)
|
|
1229
|
+
] }),
|
|
1230
|
+
draft && !draft.existing ? form(draft) : null,
|
|
1231
|
+
state.providers.length === 0 ? /* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: t.providersEmpty }) : /* @__PURE__ */ jsx("ul", { className: "divide-y border", children: state.providers.map(
|
|
1232
|
+
(p) => draft?.existing?.id === p.id ? /* @__PURE__ */ jsx("li", { className: "p-0", children: form(draft) }, p.id) : /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 px-3 py-2", "data-testid": `provider-${p.id}`, children: [
|
|
1233
|
+
/* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
|
|
1234
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
|
|
1235
|
+
/* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium", children: p.id }),
|
|
1236
|
+
p.driver !== p.id ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: p.driver ?? t.byApp }) : null
|
|
1237
|
+
] }),
|
|
1238
|
+
/* @__PURE__ */ jsxs("div", { className: "text-muted-foreground flex flex-wrap items-center gap-x-2 text-xs [&>*:not(:first-child)]:before:mr-2 [&>*:not(:first-child)]:before:content-['\xB7']", children: [
|
|
1239
|
+
p.values.base_url ? /* @__PURE__ */ jsx("span", { className: "max-w-64 truncate font-mono", title: p.values.base_url, children: hostOf(p.values.base_url) }) : null,
|
|
1240
|
+
/* @__PURE__ */ jsx("span", { children: p.local ? t.thisMachine : t.remote }),
|
|
1241
|
+
p.secrets.length > 0 ? /* @__PURE__ */ jsx("span", { children: t.keySaved }) : null,
|
|
1242
|
+
p.capabilities.length > 0 ? /* @__PURE__ */ jsx("span", { children: p.capabilities.map(cap).join(", ") }) : null
|
|
1243
|
+
] }),
|
|
1244
|
+
p.error ? /* @__PURE__ */ jsx("p", { className: "text-destructive text-xs", children: p.error }) : null
|
|
1245
|
+
] }),
|
|
1246
|
+
p.editable ? removing === p.id ? /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1247
|
+
/* @__PURE__ */ jsx(Button, { size: "xs", variant: "ghost", onClick: () => setRemoving(null), children: t.keep }),
|
|
1248
|
+
/* @__PURE__ */ jsxs(
|
|
1249
|
+
Button,
|
|
1250
|
+
{
|
|
1251
|
+
size: "xs",
|
|
1252
|
+
variant: "destructive",
|
|
1253
|
+
disabled: busy,
|
|
1254
|
+
onClick: () => {
|
|
1255
|
+
setRemoving(null);
|
|
1256
|
+
void change(() => api.deleteProvider(p.id));
|
|
1257
|
+
},
|
|
1258
|
+
children: [
|
|
1259
|
+
t.removeConfirm,
|
|
1260
|
+
" ",
|
|
1261
|
+
p.id
|
|
1262
|
+
]
|
|
1263
|
+
}
|
|
1264
|
+
)
|
|
1265
|
+
] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1266
|
+
/* @__PURE__ */ jsx(
|
|
1267
|
+
Button,
|
|
1268
|
+
{
|
|
1269
|
+
size: "xs",
|
|
1270
|
+
variant: "ghost",
|
|
1271
|
+
"aria-label": `${t.edit} ${p.id}`,
|
|
1272
|
+
disabled: busy,
|
|
1273
|
+
onClick: () => startDraft({ existing: p, driver: p.driver ?? "", id: p.id, values: { ...p.values }, secrets: {} }),
|
|
1274
|
+
children: t.edit
|
|
1275
|
+
}
|
|
1276
|
+
),
|
|
1277
|
+
/* @__PURE__ */ jsx(Button, { size: "xs", variant: "ghost", "aria-label": `${t.remove} ${p.id}`, disabled: busy, onClick: () => setRemoving(p.id), children: t.remove })
|
|
1278
|
+
] }) : null
|
|
1279
|
+
] }, p.id)
|
|
1280
|
+
) }),
|
|
1281
|
+
unused.length > 0 ? /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-1", children: [
|
|
1282
|
+
/* @__PURE__ */ jsx("p", { className: "text-muted-foreground text-xs", children: t.running }),
|
|
1283
|
+
/* @__PURE__ */ jsx("ul", { className: "divide-y border border-dashed", children: unused.map((f) => /* @__PURE__ */ jsxs("li", { className: "flex items-center gap-3 px-3 py-2", children: [
|
|
1284
|
+
/* @__PURE__ */ jsx("span", { className: "text-sm", children: f.name }),
|
|
1285
|
+
f.version ? /* @__PURE__ */ jsx("span", { className: "text-muted-foreground text-xs", children: f.version }) : null,
|
|
1286
|
+
/* @__PURE__ */ jsx("span", { className: "text-muted-foreground min-w-0 flex-1 truncate font-mono text-xs", children: f.base_url }),
|
|
1287
|
+
/* @__PURE__ */ jsx(
|
|
1288
|
+
Button,
|
|
1289
|
+
{
|
|
1290
|
+
size: "xs",
|
|
1291
|
+
variant: "outline",
|
|
1292
|
+
"aria-label": `${t.addFound} ${f.name}`,
|
|
1293
|
+
disabled: busy || !drivers.some((d) => d.name === f.driver),
|
|
1294
|
+
onClick: () => startDraft(newDraft(f.driver, { base_url: f.base_url }, f.name)),
|
|
1295
|
+
children: t.addFound
|
|
1296
|
+
}
|
|
1297
|
+
)
|
|
1298
|
+
] }, f.base_url)) })
|
|
1299
|
+
] }) : null
|
|
1300
|
+
] })
|
|
1301
|
+
] })
|
|
1302
|
+
);
|
|
1303
|
+
}
|
|
590
1304
|
|
|
591
|
-
export { ConfirmContext, ConfirmDialog, ConfirmProvider, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, OptionPicker, Panel, PanelCard, TreeExpander, TreeIcon, TreeLabel, TreeLines, TreeNode, TreeNodeContent, TreeNodeTrigger, TreeProvider, TreeView, TzPicker, useConfirm, useFormField };
|
|
1305
|
+
export { ConfirmContext, ConfirmDialog, ConfirmProvider, Form, FormControl, FormDescription, FormField, FormItem, FormLabel, FormMessage, LlmSettings, McpSettings, OptionPicker, Panel, PanelCard, TreeExpander, TreeIcon, TreeLabel, TreeLines, TreeNode, TreeNodeContent, TreeNodeTrigger, TreeProvider, TreeView, TzPicker, useConfirm, useFormField };
|
|
592
1306
|
//# sourceMappingURL=index.js.map
|
|
593
1307
|
//# sourceMappingURL=index.js.map
|