From 3f3628e4a02b367f6fc04f84e96a9893ae511528 Mon Sep 17 00:00:00 2001 From: ChickenPige0n <2336983354@qq.com> Date: Sun, 19 Jul 2026 14:32:01 +0800 Subject: [PATCH] feat(hub): add SearchableSelectField component and update RoleCard to use it --- .../src/lib/components/RoleCard.svelte | 22 +++- .../components/SearchableSelectField.svelte | 102 ++++++++++++++++++ hub/admin-web/src/routes/app.css | 30 ++++-- 3 files changed, 143 insertions(+), 11 deletions(-) create mode 100644 hub/admin-web/src/lib/components/SearchableSelectField.svelte diff --git a/hub/admin-web/src/lib/components/RoleCard.svelte b/hub/admin-web/src/lib/components/RoleCard.svelte index c24326a..6a6fb76 100644 --- a/hub/admin-web/src/lib/components/RoleCard.svelte +++ b/hub/admin-web/src/lib/components/RoleCard.svelte @@ -5,6 +5,7 @@ import { fmtDate } from '$lib/format'; import { TOOL_OPTIONS } from '$lib/constants'; import SelectField from '$lib/components/SelectField.svelte'; + import SearchableSelectField from '$lib/components/SearchableSelectField.svelte'; import CheckboxControl from '$lib/components/CheckboxControl.svelte'; import Icon from '$lib/components/Icon.svelte'; import { toastError, toastSuccess } from '$lib/toast'; @@ -60,10 +61,15 @@ {} as Record, ); - const modelItems = $derived([ - { value: '', label: '(使用平台默认模型)' }, - ...models.map((m) => ({ value: m.id, label: `${m.label}(${m.id})` })), - ]); + const modelItems = $derived.by(() => { + const fromCatalog = models.map((m) => ({ value: m.id, label: `${m.label}(${m.id})` })); + const items = [{ value: '', label: '(使用平台默认模型)' }, ...fromCatalog]; + // Keep a previously saved model selectable even if it left the live catalog. + if (defaultModel !== '' && !items.some((item) => item.value === defaultModel)) { + items.push({ value: defaultModel, label: `${defaultModel}(当前已存,不在目录中)` }); + } + return items; + }); const skillItems = $derived(skills.map((s) => ({ value: s.name, label: s.name }))); @@ -160,7 +166,13 @@

默认模型

- +
diff --git a/hub/admin-web/src/lib/components/SearchableSelectField.svelte b/hub/admin-web/src/lib/components/SearchableSelectField.svelte new file mode 100644 index 0000000..edc73dd --- /dev/null +++ b/hub/admin-web/src/lib/components/SearchableSelectField.svelte @@ -0,0 +1,102 @@ + + + { + value = next; + onchange?.(next); + }} + onOpenChangeComplete={(open) => { + if (!open) searchValue = ''; + }} +> +
+ { + searchValue = e.currentTarget.value; + }} + /> + + + +
+ + + + {#each filteredItems as item (item.value)} + + {#snippet children({ selected })} + {item.label} + {#if selected} + + {/if} + {/snippet} + + {:else} +
{emptyText}
+ {/each} +
+
+
+
diff --git a/hub/admin-web/src/routes/app.css b/hub/admin-web/src/routes/app.css index 30c4fc0..d7cafd9 100644 --- a/hub/admin-web/src/routes/app.css +++ b/hub/admin-web/src/routes/app.css @@ -406,7 +406,8 @@ resize: vertical; } - .saas-select-trigger { + .saas-select-trigger, + .saas-combobox-input { display: inline-flex; width: 100%; align-items: center; @@ -425,14 +426,25 @@ text-align: left; } + .saas-combobox-input { + cursor: text; + padding-right: 2.25rem; + } + + .saas-combobox-input::placeholder { + color: var(--color-surface-500); + } + .saas-select-trigger:focus-visible, - .saas-select-trigger[data-state='open'] { + .saas-select-trigger[data-state='open'], + .saas-combobox-input:focus { border-color: var(--color-primary-600); box-shadow: inset 0 0 0 1px var(--color-primary-600); } .saas-select-trigger:disabled, - .saas-select-trigger[data-disabled] { + .saas-select-trigger[data-disabled], + .saas-combobox-input:disabled { cursor: not-allowed; opacity: 0.55; } @@ -443,9 +455,15 @@ .saas-select-content { z-index: 70; - max-height: min(18rem, var(--bits-select-content-available-height, 18rem)); - width: var(--bits-select-anchor-width); - min-width: var(--bits-select-anchor-width); + max-height: min( + 18rem, + var( + --bits-combobox-content-available-height, + var(--bits-select-content-available-height, 18rem) + ) + ); + width: var(--bits-combobox-anchor-width, var(--bits-select-anchor-width)); + min-width: var(--bits-combobox-anchor-width, var(--bits-select-anchor-width)); overflow: hidden; border-radius: 0; border: 1px solid var(--color-surface-400);