Ask about the chat's own folder when deleting a chat
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
12a3198e77
commit
f77b101847
9 files changed
+191
-15
No files matched your search
@@ -1,6 +1,6 @@
|
||||
import { takePrefill } from "../share";
|
||||
import { Icon } from "./Icon";
|
||||
import { FileChip, FileFigure, fileType, imageSize, imageSrc } from "./FileFigure";
|
||||
import { FileChip, FileFigure, fileType, imageSize, imageSrc, formatSize } from "./FileFigure";
|
||||
import { memo, useCallback, useEffect, useLayoutEffect, useMemo, useRef, useState, type ReactNode, type RefObject } from "react";
|
||||
import { del, get, patch, post, put } from "../api";
|
||||
import { useFetch, useModels } from "../hooks";
|
||||
@@ -24,7 +24,7 @@ import { UsageOverlay } from "./UsageOverlay";
|
||||
import { RewindDialog } from "./RewindDialog";
|
||||
import { ShellsChip } from "./ShellsPanel";
|
||||
import { TodoList, ToolCard } from "./ToolCard";
|
||||
import { confirmDialog, promptDialog, toast } from "../dialogs";
|
||||
import { choiceDialog, confirmDialog, promptDialog, toast } from "../dialogs";
|
||||
import { ChatTabBar, ChatTabPanes, useChatTabs } from "./ChatTabs";
|
||||
import { ColorSwatches, IconPicker } from "./ColorSwatches";
|
||||
import { ProjectPermissions } from "./ProjectPermissions";
|
||||
@@ -757,8 +757,29 @@ export function ChatView({ sessionId, onMenu, onClosePane }: { sessionId: string
|
||||
};
|
||||
|
||||
const remove = async () => {
|
||||
if (!(await confirmDialog({ message: t("chat.deleteConfirm"), danger: true, confirmLabel: t("common.delete") }))) return;
|
||||
await del(`/sessions/${sessionId}`);
|
||||
// Does the chat have a folder of its own? Then ask whether it goes too.
|
||||
const folder = session.parent_id ? undefined : await get<{ own: boolean; path?: string; files?: number; bytes?: number; git?: boolean; truncated?: boolean }>(`/sessions/${sessionId}/folder`).catch(() => undefined);
|
||||
let withFolder = false;
|
||||
if (folder?.own) {
|
||||
const size = `${folder.truncated ? "> " : ""}${t("chat.folderFiles", { n: folder.files ?? 0 })}, ${formatSize(folder.bytes ?? 0)}`;
|
||||
const choice = await choiceDialog({
|
||||
title: t("chat.deleteTitle"),
|
||||
message: `${t("chat.deleteWithFolder", { path: folder.path ?? "", size })}${folder.git ? `\n\n${t("chat.folderHasGit")}` : ""}`,
|
||||
choices: [
|
||||
{ value: "chat", label: t("chat.deleteOnlyChat") },
|
||||
{ value: "both", label: t("chat.deleteChatAndFolder"), danger: true },
|
||||
],
|
||||
});
|
||||
if (!choice) return;
|
||||
withFolder = choice === "both";
|
||||
} else if (!(await confirmDialog({ message: t("chat.deleteConfirm"), danger: true, confirmLabel: t("common.delete") }))) return;
|
||||
try {
|
||||
const r = await del<{ folder_deleted?: boolean; folder_error?: string }>(`/sessions/${sessionId}${withFolder ? "?delete_folder=true" : ""}`);
|
||||
if (r?.folder_error) toast(r.folder_error);
|
||||
else if (r?.folder_deleted) toast(t("chat.folderDeleted"), "success");
|
||||
} catch (e: any) {
|
||||
return void toast(e.message);
|
||||
}
|
||||
navigate(session.parent_id ? `/s/${session.parent_id}` : "/new");
|
||||
};
|
||||
|
||||
|
||||
+36
-8
@@ -20,8 +20,15 @@ interface PromptOpts {
|
||||
confirmLabel?: string;
|
||||
}
|
||||
|
||||
interface ChoiceOpts {
|
||||
title?: string;
|
||||
message: string;
|
||||
choices: { value: string; label: string; danger?: boolean }[];
|
||||
}
|
||||
|
||||
type Dialog =
|
||||
| ({ kind: "confirm"; id: number; resolve: (v: boolean) => void } & ConfirmOpts)
|
||||
| ({ kind: "choice"; id: number; resolve: (v: string | null) => void } & ChoiceOpts)
|
||||
| ({ kind: "prompt"; id: number; resolve: (v: string | null) => void } & PromptOpts);
|
||||
|
||||
interface Toast {
|
||||
@@ -45,6 +52,14 @@ export function confirmDialog(opts: ConfirmOpts | string): Promise<boolean> {
|
||||
});
|
||||
}
|
||||
|
||||
/** Several actions to choose from (plus Cancel): resolves to the chosen value, or null. */
|
||||
export function choiceDialog(opts: ChoiceOpts): Promise<string | null> {
|
||||
return new Promise((resolve) => {
|
||||
dialogs = [...dialogs, { kind: "choice", id: ++seq, resolve, ...opts }];
|
||||
emit();
|
||||
});
|
||||
}
|
||||
|
||||
export function promptDialog(opts: PromptOpts | string): Promise<string | null> {
|
||||
const o = typeof opts === "string" ? { message: opts } : opts;
|
||||
return new Promise((resolve) => {
|
||||
@@ -108,14 +123,27 @@ function DialogView({ d }: { d: Dialog }) {
|
||||
style={{ width: "100%" }}
|
||||
/>
|
||||
)}
|
||||
<div className="row dialog-actions">
|
||||
<button type="button" onClick={() => close(d, cancelValue)}>
|
||||
{t("common.cancel")}
|
||||
</button>
|
||||
<button ref={ok} type="submit" className={d.kind === "confirm" && d.danger ? "danger-solid" : "primary"}>
|
||||
{d.confirmLabel ?? t("common.ok")}
|
||||
</button>
|
||||
</div>
|
||||
{d.kind === "choice" ? (
|
||||
<div className="row dialog-actions dialog-choices">
|
||||
<button type="button" onClick={() => close(d, null)}>
|
||||
{t("common.cancel")}
|
||||
</button>
|
||||
{d.choices.map((c, i) => (
|
||||
<button key={c.value} ref={i === d.choices.length - 1 ? ok : undefined} type="button" className={c.danger ? "danger-solid" : i === d.choices.length - 1 ? "primary" : undefined} onClick={() => close(d, c.value)}>
|
||||
{c.label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<div className="row dialog-actions">
|
||||
<button type="button" onClick={() => close(d, cancelValue)}>
|
||||
{t("common.cancel")}
|
||||
</button>
|
||||
<button ref={ok} type="submit" className={d.kind === "confirm" && d.danger ? "danger-solid" : "primary"}>
|
||||
{d.confirmLabel ?? t("common.ok")}
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
|
||||
@@ -1161,4 +1161,11 @@ export const de: Messages = {
|
||||
"ssh.copyFailed": "Kopieren nicht möglich – Text von Hand markieren",
|
||||
"ssh.added": "Schlüssel hinzugefügt",
|
||||
"ssh.deleteConfirm": "Schlüssel \"{name}\" löschen? Server, die ihm vertrauen, akzeptieren dich von hier aus nicht mehr.",
|
||||
"chat.deleteTitle": "Chat löschen",
|
||||
"chat.deleteWithFolder": "Dieser Chat hat einen eigenen Ordner:\n{path}\n({size})\n\nSoll der Ordner mit dem Chat gelöscht werden?",
|
||||
"chat.deleteOnlyChat": "Nur Chat löschen",
|
||||
"chat.deleteChatAndFolder": "Chat und Ordner löschen",
|
||||
"chat.folderFiles": "{n} Dateien",
|
||||
"chat.folderHasGit": "Der Ordner enthält ein Git-Repository – nicht gepushte Commits und Branches gehen verloren.",
|
||||
"chat.folderDeleted": "Chat und Ordner gelöscht",
|
||||
};
|
||||
@@ -1160,6 +1160,13 @@ export const en = {
|
||||
"ssh.copyFailed": "Could not copy – select the text manually",
|
||||
"ssh.added": "Key added",
|
||||
"ssh.deleteConfirm": "Delete the key \"{name}\"? Servers that trust it will no longer accept you from here.",
|
||||
"chat.deleteTitle": "Delete chat",
|
||||
"chat.deleteWithFolder": "This chat has a folder of its own:\n{path}\n({size})\n\nDelete the folder with the chat?",
|
||||
"chat.deleteOnlyChat": "Delete chat only",
|
||||
"chat.deleteChatAndFolder": "Delete chat and folder",
|
||||
"chat.folderFiles": "{n} files",
|
||||
"chat.folderHasGit": "The folder contains a git repository – commits and branches that were not pushed are lost.",
|
||||
"chat.folderDeleted": "Chat and folder deleted",
|
||||
};
|
||||
|
||||
export type Messages = Record<keyof typeof en, string>;
|
||||
@@ -5026,3 +5026,5 @@ td.fold-indent {
|
||||
.ssh-key { display: flex; flex-direction: column; gap: 4px; margin-bottom: 10px; }
|
||||
.ssh-key .row { align-items: center; flex-wrap: wrap; }
|
||||
.ssh-key input.mono { width: 100%; font-size: 12px; }
|
||||
.dialog-message { overflow-wrap: anywhere; }
|
||||
.dialog-choices { flex-wrap: wrap; }
|
||||
@@ -3,6 +3,10 @@
|
||||
* under "unreleased"; scripts/release.sh renames the key to the release version.
|
||||
*/
|
||||
export const WHATS_NEW: Record<string, { en: string[]; de: string[] }> = {
|
||||
unreleased: {
|
||||
en: ["Deleting a chat now asks whether to delete its own folder too"],
|
||||
de: ["Beim Löschen eines Chats wirst du gefragt, ob sein eigener Ordner mitgelöscht werden soll"],
|
||||
},
|
||||
"202611-00036": {
|
||||
en: ["Claude Code can now use all CodeCraft tools (image generation, 3D, browser, scheduling …)"],
|
||||
de: ["Claude Code kann jetzt alle CodeCraft-Tools nutzen (Bildgenerierung, 3D, Browser, Zeitpläne …)"],
|
||||
|
||||
Reference in new issue
Block a user