diff --git a/packages/app/src/i18n/ar.ts b/packages/app/src/i18n/ar.ts index 3a992b5d07d4..e2421493c2e3 100644 --- a/packages/app/src/i18n/ar.ts +++ b/packages/app/src/i18n/ar.ts @@ -75,6 +75,7 @@ export const dict = { "command.workspace.toggle.description": "تمكين أو تعطيل مساحات العمل المتعددة في الشريط الجانبي", "command.session.undo": "تراجع", "command.session.undo.description": "تراجع عن الرسالة الأخيرة", + "command.session.undo.gitRequired": "لم يتم اكتشاف مستودع Git", "command.session.redo": "إعادة", "command.session.redo.description": "إعادة الرسالة التي تم التراجع عنها", "command.session.compact": "ضغط الجلسة", diff --git a/packages/app/src/i18n/br.ts b/packages/app/src/i18n/br.ts index e8674cd547e6..8a20ca877144 100644 --- a/packages/app/src/i18n/br.ts +++ b/packages/app/src/i18n/br.ts @@ -75,6 +75,7 @@ export const dict = { "command.workspace.toggle.description": "Habilitar ou desabilitar múltiplos espaços de trabalho na barra lateral", "command.session.undo": "Desfazer", "command.session.undo.description": "Desfazer a última mensagem", + "command.session.undo.gitRequired": "Nenhum repositório Git detectado", "command.session.redo": "Refazer", "command.session.redo.description": "Refazer a última mensagem desfeita", "command.session.compact": "Compactar sessão", diff --git a/packages/app/src/i18n/bs.ts b/packages/app/src/i18n/bs.ts index 7796afd3657e..211e81b89b6d 100644 --- a/packages/app/src/i18n/bs.ts +++ b/packages/app/src/i18n/bs.ts @@ -81,6 +81,7 @@ export const dict = { "command.workspace.toggle.description": "Omogući ili onemogući više radnih prostora u bočnoj traci", "command.session.undo": "Poništi", "command.session.undo.description": "Poništi posljednju poruku", + "command.session.undo.gitRequired": "Git repozitorij nije otkriven", "command.session.redo": "Vrati", "command.session.redo.description": "Vrati posljednju poništenu poruku", "command.session.compact": "Sažmi sesiju", diff --git a/packages/app/src/i18n/da.ts b/packages/app/src/i18n/da.ts index 79039ae7e1a7..b97a5713ebb3 100644 --- a/packages/app/src/i18n/da.ts +++ b/packages/app/src/i18n/da.ts @@ -81,6 +81,7 @@ export const dict = { "command.workspace.toggle.description": "Aktiver eller deaktiver flere arbejdsområder i sidebjælken", "command.session.undo": "Fortryd", "command.session.undo.description": "Fortryd den sidste besked", + "command.session.undo.gitRequired": "Intet Git-repository fundet", "command.session.redo": "Omgør", "command.session.redo.description": "Omgør den sidste fortrudte besked", "command.session.compact": "Komprimér session", diff --git a/packages/app/src/i18n/de.ts b/packages/app/src/i18n/de.ts index f267ff2f5f23..62876304435f 100644 --- a/packages/app/src/i18n/de.ts +++ b/packages/app/src/i18n/de.ts @@ -79,6 +79,7 @@ export const dict = { "command.workspace.toggle.description": "Mehrere Arbeitsbereiche in der Seitenleiste aktivieren oder deaktivieren", "command.session.undo": "Rückgängig", "command.session.undo.description": "Letzte Nachricht rückgängig machen", + "command.session.undo.gitRequired": "Kein Git-Repository erkannt", "command.session.redo": "Wiederherstellen", "command.session.redo.description": "Letzte rückgängig gemachte Nachricht wiederherstellen", "command.session.compact": "Sitzung komprimieren", diff --git a/packages/app/src/i18n/en.ts b/packages/app/src/i18n/en.ts index 1a2d66ecc251..e9442921d1bf 100644 --- a/packages/app/src/i18n/en.ts +++ b/packages/app/src/i18n/en.ts @@ -81,6 +81,7 @@ export const dict = { "command.workspace.toggle.description": "Enable or disable multiple workspaces in the sidebar", "command.session.undo": "Undo", "command.session.undo.description": "Undo the last message", + "command.session.undo.gitRequired": "No Git repository detected", "command.session.redo": "Redo", "command.session.redo.description": "Redo the last undone message", "command.session.compact": "Compact session", diff --git a/packages/app/src/i18n/es.ts b/packages/app/src/i18n/es.ts index 1412d7dea30f..34ef72ae83e7 100644 --- a/packages/app/src/i18n/es.ts +++ b/packages/app/src/i18n/es.ts @@ -81,6 +81,7 @@ export const dict = { "command.workspace.toggle.description": "Habilitar o deshabilitar múltiples espacios de trabajo en la barra lateral", "command.session.undo": "Deshacer", "command.session.undo.description": "Deshacer el último mensaje", + "command.session.undo.gitRequired": "No se detectó ningún repositorio Git", "command.session.redo": "Rehacer", "command.session.redo.description": "Rehacer el último mensaje deshecho", "command.session.compact": "Compactar sesión", diff --git a/packages/app/src/i18n/fr.ts b/packages/app/src/i18n/fr.ts index 2df7c88c287e..6c5dbdad962e 100644 --- a/packages/app/src/i18n/fr.ts +++ b/packages/app/src/i18n/fr.ts @@ -75,6 +75,7 @@ export const dict = { "command.workspace.toggle.description": "Activer ou désactiver plusieurs espaces de travail dans la barre latérale", "command.session.undo": "Annuler", "command.session.undo.description": "Annuler le dernier message", + "command.session.undo.gitRequired": "Aucun dépôt Git détecté", "command.session.redo": "Rétablir", "command.session.redo.description": "Rétablir le dernier message annulé", "command.session.compact": "Compacter la session", diff --git a/packages/app/src/i18n/ja.ts b/packages/app/src/i18n/ja.ts index 3a02aac430f1..caa613943bbc 100644 --- a/packages/app/src/i18n/ja.ts +++ b/packages/app/src/i18n/ja.ts @@ -75,6 +75,7 @@ export const dict = { "command.workspace.toggle.description": "サイドバーでの複数のワークスペースの有効化・無効化", "command.session.undo": "元に戻す", "command.session.undo.description": "最後のメッセージを元に戻す", + "command.session.undo.gitRequired": "Git リポジトリが検出されませんでした", "command.session.redo": "やり直す", "command.session.redo.description": "元に戻したメッセージをやり直す", "command.session.compact": "セッションを圧縮", diff --git a/packages/app/src/i18n/ko.ts b/packages/app/src/i18n/ko.ts index 233b8c8b5940..1dbe2cd2e451 100644 --- a/packages/app/src/i18n/ko.ts +++ b/packages/app/src/i18n/ko.ts @@ -71,6 +71,7 @@ export const dict = { "command.workspace.toggle.description": "사이드바에서 다중 작업 공간 활성화 또는 비활성화", "command.session.undo": "실행 취소", "command.session.undo.description": "마지막 메시지 실행 취소", + "command.session.undo.gitRequired": "Git 저장소가 감지되지 않았습니다", "command.session.redo": "다시 실행", "command.session.redo.description": "마지막 실행 취소된 메시지 다시 실행", "command.session.compact": "세션 압축", diff --git a/packages/app/src/i18n/no.ts b/packages/app/src/i18n/no.ts index 3f4e859dc70c..e359b1a7dcc9 100644 --- a/packages/app/src/i18n/no.ts +++ b/packages/app/src/i18n/no.ts @@ -80,6 +80,7 @@ export const dict = { "command.workspace.toggle.description": "Aktiver eller deaktiver flere arbeidsområder i sidefeltet", "command.session.undo": "Angre", "command.session.undo.description": "Angre siste melding", + "command.session.undo.gitRequired": "Fant ikke noe Git-repositorium", "command.session.redo": "Gjør om", "command.session.redo.description": "Gjør om siste angrede melding", "command.session.compact": "Komprimer sesjon", diff --git a/packages/app/src/i18n/pl.ts b/packages/app/src/i18n/pl.ts index a94c3e2766e3..3094c52e4456 100644 --- a/packages/app/src/i18n/pl.ts +++ b/packages/app/src/i18n/pl.ts @@ -75,6 +75,7 @@ export const dict = { "command.workspace.toggle.description": "Włącz lub wyłącz wiele przestrzeni roboczych na pasku bocznym", "command.session.undo": "Cofnij", "command.session.undo.description": "Cofnij ostatnią wiadomość", + "command.session.undo.gitRequired": "Nie wykryto repozytorium Git", "command.session.redo": "Ponów", "command.session.redo.description": "Ponów ostatnią cofniętą wiadomość", "command.session.compact": "Kompaktuj sesję", diff --git a/packages/app/src/i18n/ru.ts b/packages/app/src/i18n/ru.ts index 8a781f23f580..15bc83525bfb 100644 --- a/packages/app/src/i18n/ru.ts +++ b/packages/app/src/i18n/ru.ts @@ -81,6 +81,7 @@ export const dict = { "command.workspace.toggle.description": "Включить или отключить несколько рабочих пространств в боковой панели", "command.session.undo": "Отменить", "command.session.undo.description": "Отменить последнее сообщение", + "command.session.undo.gitRequired": "Репозиторий Git не обнаружен", "command.session.redo": "Повторить", "command.session.redo.description": "Повторить отменённое сообщение", "command.session.compact": "Сжать сессию", diff --git a/packages/app/src/i18n/th.ts b/packages/app/src/i18n/th.ts index 089a87d2e4a9..c2bfdefcf937 100644 --- a/packages/app/src/i18n/th.ts +++ b/packages/app/src/i18n/th.ts @@ -81,6 +81,7 @@ export const dict = { "command.workspace.toggle.description": "เปิดหรือปิดใช้งานพื้นที่ทำงานหลายรายการในแถบด้านข้าง", "command.session.undo": "ยกเลิก", "command.session.undo.description": "ยกเลิกข้อความล่าสุด", + "command.session.undo.gitRequired": "ไม่พบที่เก็บ Git", "command.session.redo": "ทำซ้ำ", "command.session.redo.description": "ทำซ้ำข้อความที่ถูกยกเลิกล่าสุด", "command.session.compact": "บีบอัดเซสชัน", diff --git a/packages/app/src/i18n/tr.ts b/packages/app/src/i18n/tr.ts index c3b4e262a7f0..2899af0912a2 100644 --- a/packages/app/src/i18n/tr.ts +++ b/packages/app/src/i18n/tr.ts @@ -85,6 +85,7 @@ export const dict = { "command.workspace.toggle.description": "Kenar çubuğunda birden fazla çalışma alanını göster veya gizle", "command.session.undo": "Geri al", "command.session.undo.description": "Son mesajı geri al", + "command.session.undo.gitRequired": "Git deposu algılanmadı", "command.session.redo": "Yinele", "command.session.redo.description": "Son geri alınan mesajı yinele", "command.session.compact": "Oturumu sıkıştır", diff --git a/packages/app/src/i18n/uk.ts b/packages/app/src/i18n/uk.ts index db6f0abd5fc8..c7a243f1e221 100644 --- a/packages/app/src/i18n/uk.ts +++ b/packages/app/src/i18n/uk.ts @@ -81,6 +81,7 @@ export const dict = { "command.workspace.toggle.description": "Увімкнути або вимкнути декілька робочих областей на бічній панелі", "command.session.undo": "Скасувати", "command.session.undo.description": "Скасувати останнє повідомлення", + "command.session.undo.gitRequired": "Репозиторій Git не виявлено", "command.session.redo": "Повторити", "command.session.redo.description": "Повторити останнє скасоване повідомлення", "command.session.compact": "Стиснути сесію", diff --git a/packages/app/src/i18n/zh.ts b/packages/app/src/i18n/zh.ts index 79701ee40660..38065d5f6194 100644 --- a/packages/app/src/i18n/zh.ts +++ b/packages/app/src/i18n/zh.ts @@ -108,6 +108,7 @@ export const dict = { "command.session.undo": "撤销", "command.session.undo.description": "撤销上一条消息", + "command.session.undo.gitRequired": "未检测到 Git 仓库", "command.session.redo": "重做", "command.session.redo.description": "重做上一条撤销的消息", "command.session.compact": "精简会话", diff --git a/packages/app/src/i18n/zht.ts b/packages/app/src/i18n/zht.ts index f53aa02dc934..e99b548afaa5 100644 --- a/packages/app/src/i18n/zht.ts +++ b/packages/app/src/i18n/zht.ts @@ -85,6 +85,7 @@ export const dict = { "command.workspace.toggle.description": "在側邊欄啟用或停用多個工作區", "command.session.undo": "復原", "command.session.undo.description": "復原上一則訊息", + "command.session.undo.gitRequired": "未偵測到 Git 儲存庫", "command.session.redo": "重做", "command.session.redo.description": "重做上一則復原的訊息", "command.session.compact": "精簡工作階段", diff --git a/packages/app/src/pages/session.tsx b/packages/app/src/pages/session.tsx index 2327cbf735b1..9ecb4d6df4d8 100644 --- a/packages/app/src/pages/session.tsx +++ b/packages/app/src/pages/session.tsx @@ -92,6 +92,7 @@ import { TerminalPanel } from "@/pages/session/terminal-panel" import { TerminalPanelV2 } from "@/pages/session/terminal-panel-v2" import { useComposerCommands } from "@/pages/session/use-composer-commands" import { useSessionCommands } from "@/pages/session/use-session-commands" +import { supportsSessionHistory } from "@/pages/session/session-history-actions" import { useSessionHashScroll } from "@/pages/session/use-session-hash-scroll" import { Identifier } from "@/utils/id" import { diffs as list } from "@/utils/diffs" @@ -650,6 +651,7 @@ export default function Page() { }, desktopReviewOpen()) const turnDiffs = createMemo(() => list(lastUserMessage()?.summary?.diffs)) + const git = createMemo(() => supportsSessionHistory(sync().project?.vcs)) const nogit = createMemo(() => { const project = sync().project return !!project && project.vcs !== "git" @@ -1915,12 +1917,12 @@ export default function Page() { const restoring = createMemo(() => (restoreMutation.isPending ? restoreMutation.variables : undefined)) const revert = (input: { sessionID: string; messageID: string }) => { - if (reverting()) return + if (reverting() || !git()) return return revertMutation.mutateAsync(input) } const restore = (id: string) => { - if (!params.id || reverting()) return + if (!params.id || reverting() || !git()) return return restoreMutation.mutateAsync(id) } @@ -1955,7 +1957,16 @@ export default function Page() { download() } - const actions = { revert, openAttachment } + const actions = { + revert, + get revertDisabled() { + return !git() + }, + get revertDisabledReason() { + return nogit() ? language.t("command.session.undo.gitRequired") : undefined + }, + openAttachment, + } createEffect(() => { const sessionID = params.id @@ -2187,7 +2198,8 @@ export default function Page() { ? { items: rolled(), restoring: restoring(), - disabled: reverting(), + disabled: reverting() || !git(), + disabledReason: nogit() ? language.t("command.session.undo.gitRequired") : undefined, onRestore: restore, } : undefined, diff --git a/packages/app/src/pages/session/composer/session-composer-region-controller.ts b/packages/app/src/pages/session/composer/session-composer-region-controller.ts index 4073f1c19174..92fdbbfeafea 100644 --- a/packages/app/src/pages/session/composer/session-composer-region-controller.ts +++ b/packages/app/src/pages/session/composer/session-composer-region-controller.ts @@ -18,6 +18,7 @@ export type SessionComposerRevertDock = { items: { id: string; text: string }[] restoring?: string disabled?: boolean + disabledReason?: string onRestore: (id: string) => void } diff --git a/packages/app/src/pages/session/composer/session-composer-region.tsx b/packages/app/src/pages/session/composer/session-composer-region.tsx index 600ff41e3def..667c03248c81 100644 --- a/packages/app/src/pages/session/composer/session-composer-region.tsx +++ b/packages/app/src/pages/session/composer/session-composer-region.tsx @@ -93,6 +93,7 @@ export function SessionComposerRegion(props: { items={revert.items} restoring={revert.restoring} disabled={revert.disabled} + disabledReason={revert.disabledReason} onRestore={revert.onRestore} /> @@ -118,6 +119,7 @@ export function SessionComposerRegion(props: { items={revert.items} restoring={revert.restoring} disabled={revert.disabled} + disabledReason={revert.disabledReason} onRestore={revert.onRestore} /> diff --git a/packages/app/src/pages/session/composer/session-revert-dock.tsx b/packages/app/src/pages/session/composer/session-revert-dock.tsx index d8f022a53780..2ef2490dc77d 100644 --- a/packages/app/src/pages/session/composer/session-revert-dock.tsx +++ b/packages/app/src/pages/session/composer/session-revert-dock.tsx @@ -6,6 +6,8 @@ import { IconButton } from "@opencode-ai/ui/icon-button" import { ButtonV2 } from "@opencode-ai/ui/v2/button-v2" import { Icon as IconV2 } from "@opencode-ai/ui/v2/icon" import { IconButtonV2 } from "@opencode-ai/ui/v2/icon-button-v2" +import { Tooltip } from "@opencode-ai/ui/tooltip" +import { TooltipV2 } from "@opencode-ai/ui/v2/tooltip-v2" import { useLanguage } from "@/context/language" import { useSettings } from "@/context/settings" @@ -13,6 +15,7 @@ export function SessionRevertDock(props: { items: { id: string; text: string }[] restoring?: string disabled?: boolean + disabledReason?: string onRestore: (id: string) => void }) { const language = useLanguage() @@ -89,15 +92,17 @@ export function SessionRevertDock(props: { {(item) => (
{item.text} - + + +
)} @@ -167,15 +172,17 @@ export function SessionRevertDock(props: { {item.text} - props.onRestore(item.id)} - > - {language.t("session.revertDock.restore")} - + + props.onRestore(item.id)} + > + {language.t("session.revertDock.restore")} + + )} diff --git a/packages/app/src/pages/session/session-history-actions.test.ts b/packages/app/src/pages/session/session-history-actions.test.ts new file mode 100644 index 000000000000..bfcfb8ef9c9e --- /dev/null +++ b/packages/app/src/pages/session/session-history-actions.test.ts @@ -0,0 +1,28 @@ +import { describe, expect, test } from "bun:test" +import { commandPaletteOptions } from "@/context/command" +import { sessionHistoryActions } from "./session-history-actions" + +describe("sessionHistoryActions", () => { + test("enables session history actions for Git projects", () => { + expect( + sessionHistoryActions({ + vcs: "git", + sessionID: "session-1", + hasVisibleUserMessage: true, + hasRevert: true, + }), + ).toEqual({ undo: true, redo: true, revert: true }) + }) + + test("disables session history actions for non-Git projects", () => { + const actions = sessionHistoryActions({ + vcs: undefined, + sessionID: "session-1", + hasVisibleUserMessage: true, + hasRevert: true, + }) + + expect(actions).toEqual({ undo: false, redo: false, revert: false }) + expect(commandPaletteOptions([{ id: "session.undo", title: "Undo", disabled: !actions.undo }])).toEqual([]) + }) +}) diff --git a/packages/app/src/pages/session/session-history-actions.ts b/packages/app/src/pages/session/session-history-actions.ts new file mode 100644 index 000000000000..e0256b99cfce --- /dev/null +++ b/packages/app/src/pages/session/session-history-actions.ts @@ -0,0 +1,17 @@ +export function supportsSessionHistory(vcs?: string) { + return vcs === "git" +} + +export function sessionHistoryActions(input: { + vcs?: string + sessionID?: string + hasVisibleUserMessage: boolean + hasRevert: boolean +}) { + const git = supportsSessionHistory(input.vcs) + return { + undo: git && !!input.sessionID && input.hasVisibleUserMessage, + redo: git && !!input.sessionID && input.hasRevert, + revert: git, + } +} diff --git a/packages/app/src/pages/session/use-session-commands.tsx b/packages/app/src/pages/session/use-session-commands.tsx index 275e6ec4bc22..61673a77fdca 100644 --- a/packages/app/src/pages/session/use-session-commands.tsx +++ b/packages/app/src/pages/session/use-session-commands.tsx @@ -19,6 +19,7 @@ import { extractPromptFromParts } from "@/utils/prompt" import { UserMessage } from "@opencode-ai/sdk/v2" import { useSessionLayout } from "@/pages/session/session-layout" import { createSessionOwnership } from "./session-ownership" +import { sessionHistoryActions } from "./session-history-actions" export type SessionCommandContext = { navigateMessageByOffset: (offset: number) => void @@ -101,6 +102,13 @@ export const useSessionCommands = (actions: SessionCommandContext) => { if (!revert) return userMessages() return userMessages().filter((m) => m.id < revert) } + const historyActions = () => + sessionHistoryActions({ + vcs: sync().project?.vcs, + sessionID: params.id, + hasVisibleUserMessage: visibleUserMessages().length > 0, + hasRevert: !!info()?.revert?.messageID, + }) const showAllFiles = () => { if (layout.fileTree.tab() !== "changes") return @@ -303,6 +311,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => { } const undo = async () => { + if (!historyActions().undo) return const sessionID = params.id if (!sessionID) return const owner = sessionOwnership.capture() @@ -331,6 +340,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => { } const redo = async () => { + if (!historyActions().redo) return const sessionID = params.id if (!sessionID) return const owner = sessionOwnership.capture() @@ -430,9 +440,12 @@ export const useSessionCommands = (actions: SessionCommandContext) => { sessionCommand({ id: "session.undo", title: language.t("command.session.undo"), - description: language.t("command.session.undo.description"), + description: + sync().project && sync().project?.vcs !== "git" + ? language.t("command.session.undo.gitRequired") + : language.t("command.session.undo.description"), slash: "undo", - disabled: !params.id || visibleUserMessages().length === 0, + disabled: !historyActions().undo, onSelect: undo, }), sessionCommand({ @@ -440,7 +453,7 @@ export const useSessionCommands = (actions: SessionCommandContext) => { title: language.t("command.session.redo"), description: language.t("command.session.redo.description"), slash: "redo", - disabled: !params.id || !info()?.revert?.messageID, + disabled: !historyActions().redo, onSelect: redo, }), sessionCommand({ diff --git a/packages/session-ui/src/components/message-part.tsx b/packages/session-ui/src/components/message-part.tsx index 601d7839ef97..6d05e6a358a6 100644 --- a/packages/session-ui/src/components/message-part.tsx +++ b/packages/session-ui/src/components/message-part.tsx @@ -178,6 +178,8 @@ export type SessionAction = (input: { sessionID: string; messageID: string }) => export type UserActions = { fork?: SessionAction revert?: SessionAction + revertDisabled?: boolean + revertDisabledReason?: JSX.Element openAttachment?: (file: FilePart) => void } @@ -1380,9 +1382,9 @@ export function UserMessageDisplay(props: { event.preventDefault()} onClick={(event) => { event.stopPropagation()