1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
<script lang="ts">
import type { NamedAgent } from "../logic/types";
import type { CardController } from "./controller.svelte";
import {
modelsForProvider,
providerLabel,
providerOptions,
fullModelFromSelection,
} from "../logic/options";
import { draftValidity } from "../logic/auto-save";
import { saveStatusLabel } from "../logic/auto-save";
let {
models,
agent,
controller,
otherNames,
}: {
/** Available models (`<provider>/<model>`) — the source of the provider/model dropdowns. */
models: readonly string[];
/** The persisted agent this card edits, or null for a new draft. */
agent: NamedAgent | null;
/** The card's auto-save controller (draft + status + save lifecycle). */
controller: CardController;
/** The OTHER agents' names (excludes this card) for the uniqueness hint. */
otherNames: readonly string[];
} = $props();
const providers = $derived(providerOptions(models));
const providerModels = $derived(modelsForProvider(models, controller.draft.provider));
// The full model string a provider+model selection maps to (for the "unavailable"
// fallback when the saved model leaves the catalog).
const fullModel = $derived(
fullModelFromSelection(controller.draft.provider, controller.draft.model),
);
const validity = $derived(draftValidity(controller.draft, otherNames));
const statusLabel = $derived(saveStatusLabel(controller.save.status));
// Whether the current provider+model actually resolves inside the catalog.
const selectedResolves = $derived(
controller.draft.provider !== "" &&
modelsForProvider(models, controller.draft.provider).includes(controller.draft.model),
);
</script>
<div class="flex flex-col gap-1 rounded-box bg-base-200 p-3">
<span class="text-[10px] uppercase opacity-60">Name</span>
<input
type="text"
class="input input-bordered input-sm w-full"
placeholder="Enter a name…"
aria-label="Named agent name"
value={controller.draft.name}
oninput={(e) => controller.setName(e.currentTarget.value)}
/>
<span class="mt-1 text-[10px] uppercase opacity-60">Provider</span>
<select
class="select select-bordered select-sm w-full"
aria-label="Named agent provider"
value={controller.draft.provider}
onchange={(e) => controller.setProvider(e.currentTarget.value)}
>
{#if controller.draft.provider === ""}
<option value="" disabled>Select provider…</option>
{/if}
{#each providers as provider (provider)}
<option value={provider}>{providerLabel(provider)}</option>
{/each}
</select>
<span class="mt-1 text-[10px] uppercase opacity-60">Model</span>
<select
class="select select-bordered select-sm w-full"
aria-label="Named agent model"
value={controller.draft.model}
disabled={controller.draft.provider === ""}
onchange={(e) => controller.setModel(e.currentTarget.value)}
>
{#if controller.draft.provider === ""}
<option value="" disabled>Select provider first</option>
{:else if controller.draft.model === ""}
<option value="" disabled>Select a model…</option>
{/if}
{#each providerModels as model (model)}
<option value={model}>{model}</option>
{/each}
{#if !selectedResolves && controller.draft.model !== ""}
<!-- The saved selection is no longer in the live catalog (its provider/
credential was removed) — keep showing it as unavailable rather than
silently rewriting it. -->
<option value={controller.draft.model} disabled>
{fullModel} (unavailable)
</option>
{/if}
</select>
{#if !validity.usable && validity.message !== null}
<p class="mt-1 text-xs text-warning" role="status">{validity.message}</p>
{/if}
<div class="mt-2 flex items-center justify-between gap-2">
<span
class="text-xs"
class:text-error={controller.save.status === "error" && controller.save.error !== null}
class:opacity-60={controller.save.status !== "saving" && controller.save.status !== "error"}
role="status"
>
{statusLabel}{#if controller.save.error !== null}: {controller.save.error}{/if}
</span>
<button
type="button"
class="btn btn-ghost btn-xs text-error"
aria-label="Remove named agent"
disabled={controller.saving}
onclick={() => controller.remove()}
>
Remove
</button>
</div>
</div>
|