webui: fix tool selector toggle/counter, key tools by stable identity (#24065)
* webui: fix tool selector toggle/counter, key tools by stable identity Key the disabled set, counts and toggles by a stable per-tool key instead of bare function name, deduped from one canonical list. Per-tool checkboxes become presentational (single row handler, no nested button), category checkboxes drop the tristate (n/total carries partial). One getEnabledToolsForLLM keeps normalized MCP schemas and dedupes by name. * ui: use SvelteSet and SvelteMap for local tool collections to satisfy svelte/prefer-svelte-reactivity
This commit is contained in:
+1
-2
@@ -231,7 +231,7 @@
|
||||
<Collapsible.Content>
|
||||
<div class="flex flex-col gap-0.5 pl-4">
|
||||
{#each toolsPanel.activeGroups as group (group.label)}
|
||||
{@const { checked, indeterminate } = toolsPanel.getGroupCheckedState(group)}
|
||||
{@const checked = toolsPanel.isGroupChecked(group)}
|
||||
{@const enabledCount = toolsPanel.getEnabledToolCount(group)}
|
||||
{@const favicon = toolsPanel.getFavicon(group)}
|
||||
|
||||
@@ -259,7 +259,6 @@
|
||||
|
||||
<Checkbox
|
||||
{checked}
|
||||
{indeterminate}
|
||||
class="h-4 w-4 shrink-0"
|
||||
onclick={(e) => e.stopPropagation()}
|
||||
onCheckedChange={() => toolsPanel.toggleGroupByLabel(group.label)}
|
||||
|
||||
+23
-16
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { PencilRuler, ChevronDown, ChevronRight, Loader2, Info } from '@lucide/svelte';
|
||||
import { PencilRuler, ChevronDown, ChevronRight, Loader2, Info, Check } from '@lucide/svelte';
|
||||
import { Checkbox } from '$lib/components/ui/checkbox';
|
||||
import * as Collapsible from '$lib/components/ui/collapsible';
|
||||
import * as DropdownMenu from '$lib/components/ui/dropdown-menu';
|
||||
@@ -65,7 +65,7 @@
|
||||
<div class="max-h-80 overflow-y-auto p-2 pr-1">
|
||||
{#each toolsPanel.activeGroups as group (group.label)}
|
||||
{@const isExpanded = toolsPanel.expandedGroups.has(group.label)}
|
||||
{@const { checked, indeterminate } = toolsPanel.getGroupCheckedState(group)}
|
||||
{@const checked = toolsPanel.isGroupChecked(group)}
|
||||
{@const favicon = toolsPanel.getFavicon(group)}
|
||||
|
||||
<Collapsible.Root
|
||||
@@ -104,12 +104,14 @@
|
||||
|
||||
<Tooltip.Root>
|
||||
<Tooltip.Trigger>
|
||||
<Checkbox
|
||||
{checked}
|
||||
{indeterminate}
|
||||
onCheckedChange={() => toolsPanel.toggleGroupByLabel(group.label)}
|
||||
class="mr-2 h-4 w-4 shrink-0"
|
||||
/>
|
||||
{#snippet child({ props })}
|
||||
<Checkbox
|
||||
{...props}
|
||||
{checked}
|
||||
onCheckedChange={() => toolsPanel.toggleGroupByLabel(group.label)}
|
||||
class="mr-2 h-4 w-4 shrink-0"
|
||||
/>
|
||||
{/snippet}
|
||||
</Tooltip.Trigger>
|
||||
|
||||
<Tooltip.Content side="right">
|
||||
@@ -123,20 +125,25 @@
|
||||
|
||||
<Collapsible.Content>
|
||||
<div class="ml-4 flex flex-col gap-0.5 border-l border-border/50 pl-2">
|
||||
{#each group.tools as tool (tool.function.name)}
|
||||
{#each group.tools as entry (entry.key)}
|
||||
{@const enabled = toolsStore.isToolEnabled(entry.key)}
|
||||
<button
|
||||
type="button"
|
||||
class="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-sm transition-colors hover:bg-muted/50"
|
||||
onclick={() => toolsStore.toggleTool(tool.function.name)}
|
||||
onclick={() => toolsStore.toggleTool(entry.key)}
|
||||
>
|
||||
<Checkbox
|
||||
checked={toolsStore.isToolEnabled(tool.function.name)}
|
||||
onCheckedChange={() => toolsStore.toggleTool(tool.function.name)}
|
||||
class="h-4 w-4 shrink-0"
|
||||
/>
|
||||
<span
|
||||
data-slot="checkbox"
|
||||
data-state={enabled ? 'checked' : 'unchecked'}
|
||||
class="flex size-4 shrink-0 items-center justify-center rounded-[4px] border border-input data-[state=checked]:border-primary data-[state=checked]:bg-primary data-[state=checked]:text-primary-foreground"
|
||||
>
|
||||
{#if enabled}
|
||||
<Check class="size-3.5" />
|
||||
{/if}
|
||||
</span>
|
||||
|
||||
<span class="min-w-0 flex-1 truncate font-mono text-[12px]">
|
||||
{tool.function.name}
|
||||
{entry.definition.function.name}
|
||||
</span>
|
||||
</button>
|
||||
{/each}
|
||||
|
||||
@@ -62,13 +62,11 @@
|
||||
<span class="w-20 shrink-0 text-center">Always allow</span>
|
||||
</div>
|
||||
|
||||
{#each group.tools as tool (tool.function.name)}
|
||||
{@const toolName = tool.function.name}
|
||||
{@const isEnabled = toolsStore.isToolEnabled(toolName)}
|
||||
{@const permissionKey = toolsStore.getPermissionKey(toolName)}
|
||||
{@const isAlwaysAllowed = permissionKey
|
||||
? permissionsStore.hasTool(permissionKey)
|
||||
: false}
|
||||
{#each group.tools as entry (entry.key)}
|
||||
{@const toolName = entry.definition.function.name}
|
||||
{@const isEnabled = toolsStore.isToolEnabled(entry.key)}
|
||||
{@const permissionKey = entry.key}
|
||||
{@const isAlwaysAllowed = permissionsStore.hasTool(permissionKey)}
|
||||
|
||||
<div class="flex items-center gap-2 rounded px-2 py-1.5 text-sm hover:bg-muted/50">
|
||||
<TruncatedText text={toolName} class="flex-1" showTooltip={true} />
|
||||
@@ -76,7 +74,7 @@
|
||||
<div class="flex w-16 shrink-0 justify-center">
|
||||
<Checkbox
|
||||
checked={isEnabled}
|
||||
onCheckedChange={() => toolsStore.toggleTool(toolName)}
|
||||
onCheckedChange={() => toolsStore.toggleTool(entry.key)}
|
||||
class="h-4 w-4"
|
||||
/>
|
||||
</div>
|
||||
@@ -86,9 +84,9 @@
|
||||
checked={isAlwaysAllowed}
|
||||
onCheckedChange={() => {
|
||||
if (isAlwaysAllowed) {
|
||||
permissionsStore.revokeTool(permissionKey!);
|
||||
permissionsStore.revokeTool(permissionKey);
|
||||
} else {
|
||||
permissionsStore.allowTool(permissionKey!);
|
||||
permissionsStore.allowTool(permissionKey);
|
||||
}
|
||||
}}
|
||||
class="h-4 w-4"
|
||||
|
||||
Reference in New Issue
Block a user