Merge pull request #200 from different-ai/feat/session-rename

feat: add session rename command
This commit is contained in:
Omar McAdam
2026-01-22 16:56:26 -08:00
committed by GitHub
5 changed files with 134 additions and 37 deletions

View File

@@ -464,7 +464,6 @@ export default function App() {
}
async function respondPermissionAndRemember(
requestID: string,
reply: "once" | "always" | "reject"

View File

@@ -0,0 +1,64 @@
import { Show } from "solid-js";
import { X } from "lucide-solid";
import { t, currentLocale } from "../../i18n";
import Button from "./button";
import TextInput from "./text-input";
export type RenameSessionModalProps = {
open: boolean;
title: string;
busy: boolean;
canSave: boolean;
onClose: () => void;
onSave: () => void;
onTitleChange: (value: string) => void;
};
export default function RenameSessionModal(props: RenameSessionModalProps) {
const translate = (key: string) => t(key, currentLocale());
return (
<Show when={props.open}>
<div class="fixed inset-0 z-50 bg-gray-1/60 backdrop-blur-sm flex items-center justify-center p-4">
<div class="bg-gray-2 border border-gray-6/70 w-full max-w-lg rounded-2xl shadow-2xl overflow-hidden">
<div class="p-6">
<div class="flex items-start justify-between gap-4">
<div>
<h3 class="text-lg font-semibold text-gray-12">{translate("session.rename_title")}</h3>
<p class="text-sm text-gray-11 mt-1">{translate("session.rename_description")}</p>
</div>
<Button variant="ghost" class="!p-2 rounded-full" onClick={props.onClose}>
<X size={16} />
</Button>
</div>
<div class="mt-6">
<TextInput
label={translate("session.rename_label")}
value={props.title}
onInput={(e) => props.onTitleChange(e.currentTarget.value)}
placeholder={translate("session.rename_placeholder")}
class="bg-gray-3"
onKeyDown={(event) => {
if (event.key !== "Enter") return;
event.preventDefault();
if (props.canSave) props.onSave();
}}
/>
</div>
<div class="mt-6 flex justify-end gap-2">
<Button variant="outline" onClick={props.onClose} disabled={props.busy}>
{translate("common.cancel")}
</Button>
<Button onClick={props.onSave} disabled={!props.canSave}>
{translate("common.save")}
</Button>
</div>
</div>
</div>
</div>
</Show>
);
}

View File

@@ -27,6 +27,7 @@ import {
import Button from "../components/button";
import PartView from "../components/part-view";
import RenameSessionModal from "../components/rename-session-modal";
import WorkspaceChip from "../components/workspace-chip";
import ProviderAuthModal from "../components/provider-auth-modal";
import { isTauriRuntime, isWindowsPlatform } from "../utils";
@@ -82,7 +83,7 @@ export type SessionViewProps = {
safeStringify: (value: unknown) => string;
error: string | null;
sessionStatus: string;
renameSession: (sessionId: string, title: string) => Promise<void> | void;
renameSession: (sessionId: string, title: string) => Promise<void>;
openConnect: () => void;
startProviderAuth: (providerId?: string) => Promise<string>;
openProviderAuthModal: () => Promise<void>;
@@ -122,6 +123,9 @@ export default function SessionView(props: SessionViewProps) {
const [commandToast, setCommandToast] = createSignal<string | null>(null);
const [commandIndex, setCommandIndex] = createSignal(0);
const [providerAuthActionBusy, setProviderAuthActionBusy] = createSignal(false);
const [renameModalOpen, setRenameModalOpen] = createSignal(false);
const [renameTitle, setRenameTitle] = createSignal("");
const [renameBusy, setRenameBusy] = createSignal(false);
let promptInputEl: HTMLTextAreaElement | undefined;
@@ -315,6 +319,50 @@ export default function SessionView(props: SessionViewProps) {
return !hasAssistantTextAfterLastUser();
});
const selectedSessionTitle = createMemo(() => {
const id = props.selectedSessionId;
if (!id) return "";
return props.sessions.find((session) => session.id === id)?.title ?? "";
});
const renameCanSave = createMemo(() => {
if (renameBusy()) return false;
const next = renameTitle().trim();
if (!next) return false;
return next !== selectedSessionTitle().trim();
});
const openRenameModal = () => {
if (!props.selectedSessionId) {
setCommandToast("No session selected");
return;
}
setRenameTitle(selectedSessionTitle());
setRenameModalOpen(true);
};
const closeRenameModal = () => {
if (renameBusy()) return;
setRenameModalOpen(false);
};
const submitRename = async () => {
const sessionId = props.selectedSessionId;
if (!sessionId) return;
const next = renameTitle().trim();
if (!next || !renameCanSave()) return;
setRenameBusy(true);
try {
await props.renameSession(sessionId, next);
setRenameModalOpen(false);
} catch (error) {
const message = error instanceof Error ? error.message : props.safeStringify(error);
setCommandToast(message);
} finally {
setRenameBusy(false);
}
};
const clearPrompt = () => {
props.setPrompt("");
queueMicrotask(syncPromptHeight);
@@ -367,13 +415,6 @@ export default function SessionView(props: SessionViewProps) {
return items.length > 4 ? `${preview}, ...` : preview;
};
const activeSessionTitle = createMemo(() => {
const selectedId = props.selectedSessionId;
if (!selectedId) return "";
const entry = props.sessions.find((session) => session.id === selectedId);
return entry?.title ?? "";
});
const handleProviderAuthSelect = async (providerId: string) => {
if (providerAuthActionBusy()) return;
setProviderAuthActionBusy(true);
@@ -521,34 +562,9 @@ export default function SessionView(props: SessionViewProps) {
id: "rename",
label: "Rename",
description: "Rename this session",
run: async () => {
const sessionId = requireSessionId();
if (!sessionId) return;
let nextTitle = extractCommandArgs(props.prompt);
if (!nextTitle) {
const fallback = activeSessionTitle();
const prompted = window.prompt("Rename session", fallback);
if (prompted == null) {
return;
}
nextTitle = prompted.trim();
}
if (!nextTitle) {
setCommandToast("Session name is required");
clearPrompt();
return;
}
try {
await props.renameSession(sessionId, nextTitle);
setCommandToast("Session renamed");
clearPrompt();
} catch (error) {
const message = error instanceof Error ? error.message : "Rename failed";
setCommandToast(message);
}
run: () => {
openRenameModal();
clearPrompt();
},
},
{
@@ -1259,6 +1275,16 @@ export default function SessionView(props: SessionViewProps) {
onClose={props.closeProviderAuthModal}
/>
<RenameSessionModal
open={renameModalOpen()}
title={renameTitle()}
busy={renameBusy()}
canSave={renameCanSave()}
onClose={closeRenameModal}
onSave={submitRename}
onTitleChange={setRenameTitle}
/>
<Show when={props.activePermission}>
<div class="absolute inset-0 z-50 bg-gray-1/60 backdrop-blur-sm flex items-center justify-center p-4">
<div class="bg-gray-2 border border-amber-7/30 w-full max-w-md rounded-2xl shadow-2xl overflow-hidden">

View File

@@ -143,6 +143,10 @@ export default {
"session.recents_label": "Recents",
"session.model_standard": "Standard",
"session.run_button_title": "Run",
"session.rename_title": "Rename session",
"session.rename_description": "Update the name for this session.",
"session.rename_label": "Session name",
"session.rename_placeholder": "Enter a new name",
// ==================== Templates ====================
"templates.new": "New",
"templates.empty_state": "Starter templates will appear here. Create one or save from a session.",

View File

@@ -143,6 +143,10 @@ export default {
"session.recents_label": "最近",
"session.model_standard": "标准",
"session.run_button_title": "运行",
"session.rename_title": "重命名会话",
"session.rename_description": "更新此会话名称。",
"session.rename_label": "会话名称",
"session.rename_placeholder": "输入新的名称",
// ==================== Templates ====================
"templates.new": "新建",