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:
co-authored by
Pascal
parent
91d2fc3875
commit
2beefef688
@@ -0,0 +1,85 @@
|
||||
<script lang="ts">
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
import { Input } from '$lib/components/ui/input';
|
||||
import { Pencil } from '@lucide/svelte';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
currentTitle: string;
|
||||
value: string;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
let {
|
||||
open = $bindable(),
|
||||
currentTitle,
|
||||
value = $bindable(''),
|
||||
onConfirm,
|
||||
onCancel
|
||||
}: Props = $props();
|
||||
|
||||
let inputRef = $state<HTMLInputElement | null>(null);
|
||||
|
||||
const canSubmit = $derived(value.trim().length > 0 && value.trim() !== currentTitle.trim());
|
||||
|
||||
$effect(() => {
|
||||
if (open) {
|
||||
value = currentTitle;
|
||||
queueMicrotask(() => {
|
||||
inputRef?.focus();
|
||||
inputRef?.select();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
function handleOpenChange(newOpen: boolean) {
|
||||
if (!newOpen) {
|
||||
onCancel();
|
||||
}
|
||||
}
|
||||
|
||||
function handleSubmit(event: Event) {
|
||||
event.preventDefault();
|
||||
if (!canSubmit) return;
|
||||
value = value.trim();
|
||||
onConfirm();
|
||||
}
|
||||
</script>
|
||||
|
||||
<AlertDialog.Root bind:open onOpenChange={handleOpenChange}>
|
||||
<AlertDialog.Content>
|
||||
<AlertDialog.Header>
|
||||
<AlertDialog.Title class="flex items-center gap-2">
|
||||
<Pencil class="h-5 w-5" />
|
||||
Rename conversation
|
||||
</AlertDialog.Title>
|
||||
|
||||
<AlertDialog.Description>Choose a new title for this conversation.</AlertDialog.Description>
|
||||
</AlertDialog.Header>
|
||||
|
||||
<form onsubmit={handleSubmit} class="space-y-2 pt-2 pb-4">
|
||||
<label for="conversation-rename-input" class="text-sm font-medium text-muted-foreground">
|
||||
Conversation title
|
||||
</label>
|
||||
|
||||
<Input
|
||||
id="conversation-rename-input"
|
||||
bind:ref={inputRef}
|
||||
bind:value
|
||||
placeholder="Conversation title"
|
||||
maxlength={200}
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
spellcheck={false}
|
||||
/>
|
||||
</form>
|
||||
|
||||
<AlertDialog.Footer>
|
||||
<AlertDialog.Cancel>Cancel</AlertDialog.Cancel>
|
||||
|
||||
<Button type="button" onclick={handleSubmit} disabled={!canSubmit}>Save</Button>
|
||||
</AlertDialog.Footer>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
@@ -37,9 +37,9 @@
|
||||
|
||||
<Dialog.Root bind:open>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay class="z-[1000000]" />
|
||||
<Dialog.Overlay class="z-1000000" />
|
||||
|
||||
<Dialog.Content class="z-[1000001] max-w-2xl">
|
||||
<Dialog.Content class="z-1000001 max-w-2xl">
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>
|
||||
Select Conversations to {mode === 'export' ? 'Export' : 'Import'}
|
||||
@@ -58,6 +58,7 @@
|
||||
|
||||
<ConversationSelection
|
||||
bind:this={conversationSelectionRef}
|
||||
isOpen={open}
|
||||
{conversations}
|
||||
{messageCountMap}
|
||||
{mode}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
<script lang="ts">
|
||||
import * as AlertDialog from '$lib/components/ui/alert-dialog';
|
||||
import { Button } from '$lib/components/ui/button';
|
||||
|
||||
interface Props {
|
||||
open: boolean;
|
||||
currentTitle: string;
|
||||
newTitle: string;
|
||||
onConfirm: () => void;
|
||||
onCancel: () => void;
|
||||
}
|
||||
|
||||
let { open = $bindable(), currentTitle, newTitle, onConfirm, onCancel }: Props = $props();
|
||||
</script>
|
||||
|
||||
<AlertDialog.Root bind:open>
|
||||
<AlertDialog.Content>
|
||||
<AlertDialog.Header>
|
||||
<AlertDialog.Title>Update Conversation Title?</AlertDialog.Title>
|
||||
|
||||
<AlertDialog.Description>
|
||||
Do you want to update the conversation title to match the first message content?
|
||||
</AlertDialog.Description>
|
||||
</AlertDialog.Header>
|
||||
|
||||
<div class="space-y-4 pt-2 pb-6">
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm font-medium text-muted-foreground">Current title:</p>
|
||||
|
||||
<p class="rounded-md bg-muted/50 p-3 text-sm font-medium">{currentTitle}</p>
|
||||
</div>
|
||||
|
||||
<div class="space-y-2">
|
||||
<p class="text-sm font-medium text-muted-foreground">New title would be:</p>
|
||||
|
||||
<p class="rounded-md bg-muted/50 p-3 text-sm font-medium">{newTitle}</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<AlertDialog.Footer>
|
||||
<Button variant="outline" onclick={onCancel}>Keep Current Title</Button>
|
||||
|
||||
<Button onclick={onConfirm}>Update Title</Button>
|
||||
</AlertDialog.Footer>
|
||||
</AlertDialog.Content>
|
||||
</AlertDialog.Root>
|
||||
@@ -68,6 +68,7 @@
|
||||
|
||||
<AlertDialog.Footer>
|
||||
<AlertDialog.Cancel onclick={onCancel}>Cancel</AlertDialog.Cancel>
|
||||
|
||||
<AlertDialog.Action
|
||||
onclick={onConfirm}
|
||||
class="bg-destructive text-white hover:bg-destructive/80"
|
||||
|
||||
@@ -92,33 +92,36 @@ export { default as DialogExportSettings } from './DialogExportSettings.svelte';
|
||||
export { default as DialogConfirmation } from './DialogConfirmation.svelte';
|
||||
|
||||
/**
|
||||
* **DialogConversationTitleUpdate** - Conversation rename confirmation
|
||||
* **DialogConversationRename** - Rename a conversation
|
||||
*
|
||||
* Confirmation dialog shown when editing the first user message in a conversation.
|
||||
* Asks user whether to update the conversation title to match the new message content.
|
||||
* Modal dialog for renaming a conversation. Replaces the prior
|
||||
* `window.prompt()`-based flow with a styled, accessible AlertDialog
|
||||
* containing an editable input. Triggered from the sidebar conversation
|
||||
* item's "Edit" action.
|
||||
*
|
||||
* **Architecture:**
|
||||
* - Uses ShadCN AlertDialog
|
||||
* - Shows current vs proposed title comparison
|
||||
* - Triggered by ChatMessages when first message is edited
|
||||
* - Bindable `value` keeps the new title in sync with parent state
|
||||
* - Submit is gated on a non-empty trimmed value that differs from the current title
|
||||
*
|
||||
* **Features:**
|
||||
* - Side-by-side display of current and new title
|
||||
* - "Keep Current Title" and "Update Title" action buttons
|
||||
* - Styled title previews in muted background boxes
|
||||
* - Autofocus on open with text selected for quick overwrite
|
||||
* - Disabled Save button when value is empty or unchanged
|
||||
* - Trim-on-submit normalization
|
||||
* - Cancel via AlertDialog.Cancel or `onOpenChange(false)`
|
||||
*
|
||||
* @example
|
||||
* ```svelte
|
||||
* <DialogConversationTitleUpdate
|
||||
* bind:open={showTitleUpdate}
|
||||
* <DialogConversationRename
|
||||
* bind:open={showRename}
|
||||
* currentTitle={conversation.name}
|
||||
* newTitle={truncatedMessageContent}
|
||||
* onConfirm={updateTitle}
|
||||
* onCancel={() => showTitleUpdate = false}
|
||||
* bind:value={renameDraft}
|
||||
* onConfirm={handleRenameConfirm}
|
||||
* onCancel={() => (showRename = false)}
|
||||
* />
|
||||
* ```
|
||||
*/
|
||||
export { default as DialogConversationTitleUpdate } from './DialogConversationTitleUpdate.svelte';
|
||||
export { default as DialogConversationRename } from './DialogConversationRename.svelte';
|
||||
|
||||
/**
|
||||
*
|
||||
|
||||
Reference in New Issue
Block a user