CheckpointMenu.tsx 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. import { useState, useEffect, useCallback } from "react"
  2. import { CheckIcon, Cross2Icon } from "@radix-ui/react-icons"
  3. import { vscode } from "../../../utils/vscode"
  4. import { Button, Popover, PopoverContent, PopoverTrigger } from "@/components/ui"
  5. type CheckpointMenuProps = {
  6. ts: number
  7. commitHash: string
  8. currentCheckpointHash?: string
  9. }
  10. export const CheckpointMenu = ({ ts, commitHash, currentCheckpointHash }: CheckpointMenuProps) => {
  11. const [portalContainer, setPortalContainer] = useState<HTMLElement>()
  12. const [isOpen, setIsOpen] = useState(false)
  13. const [isConfirming, setIsConfirming] = useState(false)
  14. const isCurrent = currentCheckpointHash === commitHash
  15. const onCheckpointDiff = useCallback(() => {
  16. vscode.postMessage({ type: "checkpointDiff", payload: { ts, commitHash, mode: "checkpoint" } })
  17. }, [ts, commitHash])
  18. const onPreview = useCallback(() => {
  19. vscode.postMessage({ type: "checkpointRestore", payload: { ts, commitHash, mode: "preview" } })
  20. setIsOpen(false)
  21. }, [ts, commitHash])
  22. const onRestore = useCallback(() => {
  23. vscode.postMessage({ type: "checkpointRestore", payload: { ts, commitHash, mode: "restore" } })
  24. setIsOpen(false)
  25. }, [ts, commitHash])
  26. useEffect(() => {
  27. // The dropdown menu uses a portal from @shadcn/ui which by default renders
  28. // at the document root. This causes the menu to remain visible even when
  29. // the parent ChatView component is hidden (during settings/history view).
  30. // By moving the portal inside ChatView, the menu will properly hide when
  31. // its parent is hidden.
  32. setPortalContainer(document.getElementById("chat-view-portal") || undefined)
  33. }, [])
  34. return (
  35. <div className="flex flex-row gap-1">
  36. <Button variant="ghost" size="icon" onClick={onCheckpointDiff}>
  37. <span className="codicon codicon-diff-single" />
  38. </Button>
  39. <Popover
  40. open={isOpen}
  41. onOpenChange={(open) => {
  42. setIsOpen(open)
  43. setIsConfirming(false)
  44. }}>
  45. <PopoverTrigger asChild>
  46. <Button variant="ghost" size="icon">
  47. <span className="codicon codicon-history" />
  48. </Button>
  49. </PopoverTrigger>
  50. <PopoverContent align="end" container={portalContainer}>
  51. <div className="flex flex-col gap-2">
  52. {!isCurrent && (
  53. <div className="flex flex-col gap-1 group hover:text-foreground">
  54. <Button variant="secondary" onClick={onPreview}>
  55. Restore Files
  56. </Button>
  57. <div className="text-muted transition-colors group-hover:text-foreground">
  58. Restores your project's files back to a snapshot taken at this point.
  59. </div>
  60. </div>
  61. )}
  62. <div className="flex flex-col gap-1 group hover:text-foreground">
  63. <div className="flex flex-col gap-1 group hover:text-foreground">
  64. {!isConfirming ? (
  65. <Button variant="secondary" onClick={() => setIsConfirming(true)}>
  66. Restore Files & Task
  67. </Button>
  68. ) : (
  69. <>
  70. <Button variant="default" onClick={onRestore} className="grow">
  71. <div className="flex flex-row gap-1">
  72. <CheckIcon />
  73. <div>Confirm</div>
  74. </div>
  75. </Button>
  76. <Button variant="secondary" onClick={() => setIsConfirming(false)}>
  77. <div className="flex flex-row gap-1">
  78. <Cross2Icon />
  79. <div>Cancel</div>
  80. </div>
  81. </Button>
  82. </>
  83. )}
  84. {isConfirming ? (
  85. <div className="text-destructive font-bold">This action cannot be undone.</div>
  86. ) : (
  87. <div className="text-muted transition-colors group-hover:text-foreground">
  88. Restores your project's files back to a snapshot taken at this point and deletes
  89. all messages after this point.
  90. </div>
  91. )}
  92. </div>
  93. </div>
  94. </div>
  95. </PopoverContent>
  96. </Popover>
  97. </div>
  98. )
  99. }