Compare commits
2 Commits
dev
...
undo-git-guard
| Author | SHA1 | Date | |
|---|---|---|---|
| 6cf2c80a98 | |||
| 55d6e8903d |
@@ -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": "ضغط الجلسة",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "セッションを圧縮",
|
||||
|
||||
@@ -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": "세션 압축",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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ę",
|
||||
|
||||
@@ -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": "Сжать сессию",
|
||||
|
||||
@@ -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": "บีบอัดเซสชัน",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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": "Стиснути сесію",
|
||||
|
||||
@@ -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": "精简会话",
|
||||
|
||||
@@ -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": "精簡工作階段",
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -18,6 +18,7 @@ export type SessionComposerRevertDock = {
|
||||
items: { id: string; text: string }[]
|
||||
restoring?: string
|
||||
disabled?: boolean
|
||||
disabledReason?: string
|
||||
onRestore: (id: string) => void
|
||||
}
|
||||
|
||||
|
||||
@@ -93,6 +93,7 @@ export function SessionComposerRegion(props: {
|
||||
items={revert.items}
|
||||
restoring={revert.restoring}
|
||||
disabled={revert.disabled}
|
||||
disabledReason={revert.disabledReason}
|
||||
onRestore={revert.onRestore}
|
||||
/>
|
||||
</div>
|
||||
@@ -118,6 +119,7 @@ export function SessionComposerRegion(props: {
|
||||
items={revert.items}
|
||||
restoring={revert.restoring}
|
||||
disabled={revert.disabled}
|
||||
disabledReason={revert.disabledReason}
|
||||
onRestore={revert.onRestore}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -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) => (
|
||||
<div class="flex items-center gap-2 min-w-0 py-1">
|
||||
<span class="min-w-0 flex-1 truncate text-13-regular text-text-strong">{item.text}</span>
|
||||
<Button
|
||||
size="small"
|
||||
variant="secondary"
|
||||
class="shrink-0"
|
||||
disabled={props.disabled || !!props.restoring}
|
||||
onClick={() => props.onRestore(item.id)}
|
||||
>
|
||||
{language.t("session.revertDock.restore")}
|
||||
</Button>
|
||||
<Tooltip inactive={!props.disabledReason} value={props.disabledReason} placement="top">
|
||||
<Button
|
||||
size="small"
|
||||
variant="secondary"
|
||||
class="shrink-0"
|
||||
disabled={props.disabled || !!props.restoring}
|
||||
onClick={() => props.onRestore(item.id)}
|
||||
>
|
||||
{language.t("session.revertDock.restore")}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
@@ -167,15 +172,17 @@ export function SessionRevertDock(props: {
|
||||
<span class="min-w-0 flex-1 truncate text-[13px] font-[400] leading-5 tracking-[-0.04px] text-v2-text-text-muted">
|
||||
{item.text}
|
||||
</span>
|
||||
<ButtonV2
|
||||
size="small"
|
||||
variant="neutral"
|
||||
class="shrink-0"
|
||||
disabled={props.disabled || !!props.restoring}
|
||||
onClick={() => props.onRestore(item.id)}
|
||||
>
|
||||
{language.t("session.revertDock.restore")}
|
||||
</ButtonV2>
|
||||
<TooltipV2 inactive={!props.disabledReason} value={props.disabledReason} placement="top">
|
||||
<ButtonV2
|
||||
size="small"
|
||||
variant="neutral"
|
||||
class="shrink-0"
|
||||
disabled={props.disabled || !!props.restoring}
|
||||
onClick={() => props.onRestore(item.id)}
|
||||
>
|
||||
{language.t("session.revertDock.restore")}
|
||||
</ButtonV2>
|
||||
</TooltipV2>
|
||||
</div>
|
||||
)}
|
||||
</For>
|
||||
|
||||
@@ -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([])
|
||||
})
|
||||
})
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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({
|
||||
|
||||
@@ -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: {
|
||||
<Show when={props.actions?.revert}>
|
||||
<MessageActionButton
|
||||
icon="reset"
|
||||
label={i18n.t("ui.message.revertMessage")}
|
||||
label={props.actions?.revertDisabledReason ?? i18n.t("ui.message.revertMessage")}
|
||||
useV2={props.useV2Actions}
|
||||
disabled={!!busy()}
|
||||
disabled={!!busy() || props.actions?.revertDisabled}
|
||||
onMouseDown={(event) => event.preventDefault()}
|
||||
onClick={(event) => {
|
||||
event.stopPropagation()
|
||||
|
||||
Reference in New Issue
Block a user