From 93fff645feec676bcc44ba76eeb8d9cf4ec9d491 Mon Sep 17 00:00:00 2001 From: anduimagui Date: Fri, 6 Mar 2026 17:04:53 +0000 Subject: [PATCH 1/2] feat(app): add copy session id actions --- .../src/components/session/session-header.tsx | 44 +++++++++++ packages/app/src/i18n/en.ts | 4 + .../pages/session/use-session-commands.tsx | 73 +++++++++++++------ 3 files changed, 97 insertions(+), 24 deletions(-) diff --git a/packages/app/src/components/session/session-header.tsx b/packages/app/src/components/session/session-header.tsx index bb4d9812503..31ac6c16ba0 100644 --- a/packages/app/src/components/session/session-header.tsx +++ b/packages/app/src/components/session/session-header.tsx @@ -343,6 +343,22 @@ export function SessionHeader() { .catch((err: unknown) => showRequestError(language, err)) } + const copySessionID = () => { + const id = params.id + if (!id) return + navigator.clipboard + .writeText(id) + .then(() => { + showToast({ + variant: "success", + icon: "circle-check", + title: language.t("session.share.copy.copied"), + description: id, + }) + }) + .catch((err: unknown) => showRequestError(language, err)) + } + const share = useSessionShare({ globalSDK, currentSession, @@ -407,6 +423,20 @@ export function SessionHeader() { {language.t("session.header.open.copyPath")} + +
+ +
} > @@ -497,6 +527,20 @@ export function SessionHeader() { {language.t("session.header.open.copyPath")} + { + setMenu("open", false) + copySessionID() + }} + disabled={!params.id} + > +
+ +
+ + {language.t("session.header.open.copySessionID")} + +
diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 7e95fd739df..c8216e40fd9 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -83,6 +83,8 @@ export const dict = { "command.session.compact.description": "Summarize the session to reduce context size", "command.session.fork": "Fork from message", "command.session.fork.description": "Create a new session from a previous message", + "command.session.copyID": "Copy session ID", + "command.session.copyID.description": "Copy the current session ID to clipboard", "command.session.share": "Share session", "command.session.share.description": "Share this session and copy the URL to clipboard", "command.session.unshare": "Unshare session", @@ -431,6 +433,7 @@ export const dict = { "toast.context.noLineSelection.description": "Select a line range in a file tab first.", "toast.session.share.copyFailed.title": "Failed to copy URL to clipboard", + "toast.session.copyID.failed.title": "Failed to copy session ID", "toast.session.share.success.title": "Session shared", "toast.session.share.success.description": "Share URL copied to clipboard!", "toast.session.share.failed.title": "Failed to share session", @@ -541,6 +544,7 @@ export const dict = { "session.header.open.ariaLabel": "Open in {{app}}", "session.header.open.menu": "Open options", "session.header.open.copyPath": "Copy path", + "session.header.open.copySessionID": "Copy session ID", "status.popover.trigger": "Status", "status.popover.ariaLabel": "Server configurations", diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index 461351878b6..8fefdb74434 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -79,6 +79,30 @@ export const useSessionCommands = (actions: SessionCommandContext) => { return lines.slice(0, 2).join("\n") } + const write = (value: string) => { + const body = typeof document === "undefined" ? undefined : document.body + if (body) { + const textarea = document.createElement("textarea") + textarea.value = value + textarea.setAttribute("readonly", "") + textarea.style.position = "fixed" + textarea.style.opacity = "0" + textarea.style.pointerEvents = "none" + body.appendChild(textarea) + textarea.select() + const copied = document.execCommand("copy") + body.removeChild(textarea) + if (copied) return Promise.resolve(true) + } + + const clipboard = typeof navigator === "undefined" ? undefined : navigator.clipboard + if (!clipboard?.writeText) return Promise.resolve(false) + return clipboard.writeText(value).then( + () => true, + () => false, + ) + } + const addSelectionToContext = (path: string, selection: FileSelection) => { const preview = selectionPreview(path, selection) prompt.context.add({ type: "file", path, selection, preview }) @@ -368,6 +392,31 @@ export const useSessionCommands = (actions: SessionCommandContext) => { disabled: !params.id || visibleUserMessages().length === 0, onSelect: () => dialog.show(() => ), }), + sessionCommand({ + id: "session.copyID", + title: language.t("command.session.copyID"), + description: language.t("command.session.copyID.description"), + slash: "id", + disabled: !params.id, + onSelect: async () => { + const id = params.id + if (!id) return + const ok = await write(id) + if (!ok) { + showToast({ + title: language.t("toast.session.copyID.failed.title"), + variant: "error", + }) + return + } + + showToast({ + title: language.t("session.share.copy.copied"), + description: id, + variant: "success", + }) + }, + }), ]) const shareCommands = createMemo(() => { @@ -384,30 +433,6 @@ export const useSessionCommands = (actions: SessionCommandContext) => { onSelect: async () => { if (!params.id) return - const write = (value: string) => { - const body = typeof document === "undefined" ? undefined : document.body - if (body) { - const textarea = document.createElement("textarea") - textarea.value = value - textarea.setAttribute("readonly", "") - textarea.style.position = "fixed" - textarea.style.opacity = "0" - textarea.style.pointerEvents = "none" - body.appendChild(textarea) - textarea.select() - const copied = document.execCommand("copy") - body.removeChild(textarea) - if (copied) return Promise.resolve(true) - } - - const clipboard = typeof navigator === "undefined" ? undefined : navigator.clipboard - if (!clipboard?.writeText) return Promise.resolve(false) - return clipboard.writeText(value).then( - () => true, - () => false, - ) - } - const copy = async (url: string, existing: boolean) => { const ok = await write(url) if (!ok) { From deb71d085d0fdb7a8ff7bef7efd501021fc54640 Mon Sep 17 00:00:00 2001 From: anduimagui Date: Fri, 6 Mar 2026 17:37:34 +0000 Subject: [PATCH 2/2] fix(app): remove session id slash command --- packages/app/src/i18n/en.ts | 3 --- .../pages/session/use-session-commands.tsx | 25 ------------------- 2 files changed, 28 deletions(-) diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index c8216e40fd9..4556277c4bb 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -83,8 +83,6 @@ export const dict = { "command.session.compact.description": "Summarize the session to reduce context size", "command.session.fork": "Fork from message", "command.session.fork.description": "Create a new session from a previous message", - "command.session.copyID": "Copy session ID", - "command.session.copyID.description": "Copy the current session ID to clipboard", "command.session.share": "Share session", "command.session.share.description": "Share this session and copy the URL to clipboard", "command.session.unshare": "Unshare session", @@ -433,7 +431,6 @@ export const dict = { "toast.context.noLineSelection.description": "Select a line range in a file tab first.", "toast.session.share.copyFailed.title": "Failed to copy URL to clipboard", - "toast.session.copyID.failed.title": "Failed to copy session ID", "toast.session.share.success.title": "Session shared", "toast.session.share.success.description": "Share URL copied to clipboard!", "toast.session.share.failed.title": "Failed to share session", diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index 7cfbc0a3685..531c4636e49 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -403,31 +403,6 @@ export const useSessionCommands = (actions: SessionCommandContext) => { disabled: !params.id || visibleUserMessages().length === 0, onSelect: () => dialog.show(() => ), }), - sessionCommand({ - id: "session.copyID", - title: language.t("command.session.copyID"), - description: language.t("command.session.copyID.description"), - slash: "id", - disabled: !params.id, - onSelect: async () => { - const id = params.id - if (!id) return - const ok = await write(id) - if (!ok) { - showToast({ - title: language.t("toast.session.copyID.failed.title"), - variant: "error", - }) - return - } - - showToast({ - title: language.t("session.share.copy.copied"), - description: id, - variant: "success", - }) - }, - }), ]) const shareCommands = createMemo(() => {