mirror of
https://github.com/different-ai/openwork
synced 2026-05-10 09:12:03 +02:00
Merge pull request #200 from different-ai/feat/session-rename
feat: add session rename command
This commit is contained in:
@@ -464,7 +464,6 @@ export default function App() {
|
||||
}
|
||||
|
||||
|
||||
|
||||
async function respondPermissionAndRemember(
|
||||
requestID: string,
|
||||
reply: "once" | "always" | "reject"
|
||||
|
||||
64
packages/app/src/app/components/rename-session-modal.tsx
Normal file
64
packages/app/src/app/components/rename-session-modal.tsx
Normal 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>
|
||||
);
|
||||
}
|
||||
@@ -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">
|
||||
|
||||
@@ -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.",
|
||||
|
||||
@@ -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": "新建",
|
||||
|
||||
Reference in New Issue
Block a user