ui: Sidebar Conversations Bulk Action + Improved Settings logic/UI (#25815)

* feat: WIP

* feat: Replace conversation rename flow with unified AlertDialog component

* feat: Add radio group component and consolidate title generation settings

* refactor: Remove JS Sandbox global toggle and migrate legacy user state

* chore: Formatting

* refactor: Cleanup

Co-authored-by: Aleksander Grygier <aleksander.grygier@gmail.com>

* refactor: Cleanup

* refactor: Marquee selection hook

* feat: UI improvements

* refactor: Bulk db operations

* fix: optimize bulk conversation deletion to handle ancestor chains

* refactor: remove pairedKey mechanism from settings system

* fix: remove redundant onclick handler from dialog cancel button

* chore: pin @lucide/svelte to exact version

* feat: Run JavaScript tool disabled by default

* fix: correct active conversation deletion tracking in bulk delete

* feat: improve shift-key multi-selection support in sidebar via keyboard

* refactor: Retrieve JS Tool enabling via Developer Settings

* nits: sync, dialog wording, cycle guard, and lockfile follow-ups

- Restore titleGenerationUseLLM registry entry so it syncs across devices again
- Mention fork cascade in the bulk delete confirmation dialog
- Clear newParent on cycle guard break so children never point at a deleted conversation
- Align @lucide/svelte in package-lock.json with the exact pin in package.json

---------

Co-authored-by: Pascal <admin@serveurperso.com>
This commit is contained in:
Aleksander Grygier
2026-07-20 23:40:08 +02:00
committed by GitHub
co-authored by Pascal
parent 91d2fc3875
commit 2beefef688
31 changed files with 1529 additions and 426 deletions
@@ -4,6 +4,7 @@
import { Checkbox } from '$lib/components/ui/checkbox';
import { Input } from '$lib/components/ui/input';
import Label from '$lib/components/ui/label/label.svelte';
import * as RadioGroup from '$lib/components/ui/radio-group';
import * as Select from '$lib/components/ui/select';
import { Textarea } from '$lib/components/ui/textarea';
import { SETTING_CONFIG_INFO, SETTINGS_KEYS } from '$lib/constants';
@@ -84,10 +85,7 @@
type={field.isPositiveInteger ? 'number' : 'text'}
{...field.isPositiveInteger ? { min: '1', step: '1' } : {}}
value={currentValue}
oninput={(e) => {
// Update local config immediately for real-time badge feedback
onConfigChange(field.key, e.currentTarget.value);
}}
oninput={(e) => onConfigChange(field.key, e.currentTarget.value)}
placeholder={currentModelParams[field.key] != null
? `Default: ${normalizeFloatingPoint(currentModelParams[field.key])}`
: ''}
@@ -236,6 +234,52 @@
{field.help || SETTING_CONFIG_INFO[field.key]}
</p>
{/if}
{:else if field.type === SettingsFieldType.RADIO && field.radioOptions}
{@const radioOptions = field.radioOptions}
{@const currentMode =
radioOptions.find((o: { key: string }) => Boolean(localConfig[o.key]))?.value ??
radioOptions[0].value}
<Label class="flex items-center gap-1.5 text-sm font-medium mb-4">
{field.label}
{#if field.isExperimental}
<FlaskConical class="h-3.5 w-3.5 text-muted-foreground" />
{/if}
</Label>
<RadioGroup.Root
class="gap-4"
value={currentMode}
onValueChange={(value) => {
for (const opt of radioOptions) {
onConfigChange(opt.key, opt.value === value);
}
}}
>
{#each radioOptions as opt (opt.value)}
{@const itemId = `${field.key}-${opt.value}`}
<div class="flex items-center gap-2">
<RadioGroup.Item value={opt.value} id={itemId} />
<Label
for={itemId}
class="flex cursor-pointer items-center gap-1.5 text-sm font-normal"
>
{opt.label}
{#if opt.isExperimental}
<FlaskConical class="h-3.5 w-3.5 text-muted-foreground" />
{/if}
</Label>
</div>
{/each}
</RadioGroup.Root>
{#if field.help || SETTING_CONFIG_INFO[field.key]}
<p class="text-xs text-muted-foreground">
{field.help || SETTING_CONFIG_INFO[field.key]}
</p>
{/if}
{:else if field.type === SettingsFieldType.CHECKBOX}
<div class="flex items-start space-x-3">
<Checkbox
@@ -7,6 +7,8 @@
import { toolsStore } from '$lib/stores/tools.svelte';
import { permissionsStore } from '$lib/stores/permissions.svelte';
import { mcpStore } from '$lib/stores/mcp.svelte';
import { getBuiltinToolUi } from '$lib/constants/built-in-tools';
import { ToolSource } from '$lib/enums/tools.enums';
import { SvelteSet } from 'svelte/reactivity';
let expandedGroups = new SvelteSet<string>();
@@ -69,12 +71,23 @@
{#each group.tools as entry (entry.key)}
{@const toolName = entry.definition.function.name}
{@const builtinUi =
entry.source === ToolSource.BUILTIN || entry.source === ToolSource.FRONTEND
? getBuiltinToolUi(toolName)
: null}
{@const displayLabel = builtinUi?.label ?? toolName}
{@const IconComponent = builtinUi?.icon ?? null}
{@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} />
<span class="flex min-w-0 flex-1 items-center gap-1.5">
{#if IconComponent}
<IconComponent class={ICON_CLASS_DEFAULT} />
{/if}
<TruncatedText text={displayLabel} class="min-w-0" showTooltip={true} />
</span>
<div class="flex w-16 shrink-0 justify-center">
<Checkbox