| ... | @@ -40,6 +40,7 @@ import BranchToolbar from "./BranchToolbar"; | ... | @@ -40,6 +40,7 @@ import BranchToolbar from "./BranchToolbar"; |
| 40 | import { isTerminalToggleShortcut } from "../terminal-shortcuts"; | 40 | import { isTerminalToggleShortcut } from "../terminal-shortcuts"; |
| 41 | import ChatMarkdown from "./ChatMarkdown"; | 41 | import ChatMarkdown from "./ChatMarkdown"; |
| 42 | import ThreadTerminalDrawer from "./ThreadTerminalDrawer"; | 42 | import ThreadTerminalDrawer from "./ThreadTerminalDrawer"; |
| | 43 | import { CloudUploadIcon, GitCommitIcon, GithubIcon } from "lucide-react"; |
| 43 | | 44 | |
| 44 | function formatMessageMeta(createdAt: string, duration: string | null): string { | 45 | function formatMessageMeta(createdAt: string, duration: string | null): string { |
| 45 | if (!duration) return formatTimestamp(createdAt); | 46 | if (!duration) return formatTimestamp(createdAt); |
| ... | @@ -70,62 +71,14 @@ function GitActionIcon(props: { icon: GitActionMenuItem["icon"]; disabled: boole | ... | @@ -70,62 +71,14 @@ function GitActionIcon(props: { icon: GitActionMenuItem["icon"]; disabled: boole |
| 70 | const toneClass = props.disabled ? "text-muted-foreground/45" : "text-foreground/85"; | 71 | const toneClass = props.disabled ? "text-muted-foreground/45" : "text-foreground/85"; |
| 71 | | 72 | |
| 72 | if (props.icon === "commit") { | 73 | if (props.icon === "commit") { |
| 73 | return ( | 74 | return <GitCommitIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; |
| 74 | <svg | | |
| 75 | viewBox="0 0 20 20" | | |
| 76 | aria-hidden="true" | | |
| 77 | className={`h-5 w-5 shrink-0 ${toneClass}`} | | |
| 78 | fill="none" | | |
| 79 | stroke="currentColor" | | |
| 80 | strokeWidth="1.8" | | |
| 81 | strokeLinecap="round" | | |
| 82 | strokeLinejoin="round" | | |
| 83 | > | | |
| 84 | <circle cx="10" cy="10" r="4.2" /> | | |
| 85 | <path d="M1.8 10h4.2" /> | | |
| 86 | <path d="M14 10h4.2" /> | | |
| 87 | </svg> | | |
| 88 | ); | | |
| 89 | } | 75 | } |
| 90 | | 76 | |
| 91 | if (props.icon === "push") { | 77 | if (props.icon === "push") { |
| 92 | return ( | 78 | return <CloudUploadIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; |
| 93 | <svg | | |
| 94 | viewBox="0 0 20 20" | | |
| 95 | aria-hidden="true" | | |
| 96 | className={`h-5 w-5 shrink-0 ${toneClass}`} | | |
| 97 | fill="none" | | |
| 98 | stroke="currentColor" | | |
| 99 | strokeWidth="1.8" | | |
| 100 | strokeLinecap="round" | | |
| 101 | strokeLinejoin="round" | | |
| 102 | > | | |
| 103 | <path d="M5.2 16h9.6a3.2 3.2 0 0 0 .2-6.4h-.5A4.8 4.8 0 0 0 5 9 3.7 3.7 0 0 0 5.2 16Z" /> | | |
| 104 | <path d="M10 5.8v6.2" /> | | |
| 105 | <path d="m7.7 8.1 2.3-2.3 2.3 2.3" /> | | |
| 106 | </svg> | | |
| 107 | ); | | |
| 108 | } | 79 | } |
| 109 | | 80 | |
| 110 | return ( | 81 | return <GithubIcon className={`h-5 w-5 shrink-0 ${toneClass}`} />; |
| 111 | <svg | | |
| 112 | viewBox="0 0 20 20" | | |
| 113 | aria-hidden="true" | | |
| 114 | className={`h-5 w-5 shrink-0 ${toneClass}`} | | |
| 115 | fill="none" | | |
| 116 | stroke="currentColor" | | |
| 117 | strokeWidth="1.8" | | |
| 118 | strokeLinecap="round" | | |
| 119 | strokeLinejoin="round" | | |
| 120 | > | | |
| 121 | <circle cx="6" cy="5" r="1.7" /> | | |
| 122 | <circle cx="6" cy="15" r="1.7" /> | | |
| 123 | <circle cx="14" cy="10" r="1.7" /> | | |
| 124 | <path d="M6 6.8v6.5" /> | | |
| 125 | <path d="M7.8 6.1c2.9 0 3.8 1.4 5.1 2.9" /> | | |
| 126 | <path d="M7.8 13.9c2.9 0 3.8-1.4 5.1-2.9" /> | | |
| 127 | </svg> | | |
| 128 | ); | | |
| 129 | } | 82 | } |
| 130 | | 83 | |
| 131 | function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string { | 84 | function workToneClass(tone: "thinking" | "tool" | "info" | "error"): string { |
| ... | @@ -334,8 +287,7 @@ export default function ChatView() { | ... | @@ -334,8 +287,7 @@ export default function ChatView() { |
| 334 | sandboxMode: "workspace-write", | 287 | sandboxMode: "workspace-write", |
| 335 | } as const); | 288 | } as const); |
| 336 | const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null; | 289 | const gitCwd = activeThread?.worktreePath ?? activeProject?.cwd ?? null; |
| 337 | const gitBaseDisabled = | 290 | const gitBaseDisabled = !api || !gitCwd || !gitStatus || isGitActionRunning; |
| 338 | !api || !gitCwd || !gitStatus || isGitActionRunning; | | |
| 339 | const gitActionMenuItems = useMemo<GitActionMenuItem[]>(() => { | 291 | const gitActionMenuItems = useMemo<GitActionMenuItem[]>(() => { |
| 340 | if (!gitStatus) return []; | 292 | if (!gitStatus) return []; |
| 341 | | 293 | |
| ... | @@ -343,7 +295,9 @@ export default function ChatView() { | ... | @@ -343,7 +295,9 @@ export default function ChatView() { |
| 343 | const hasOpenPr = gitStatus.openPr !== null; | 295 | const hasOpenPr = gitStatus.openPr !== null; |
| 344 | const canCommit = !gitBaseDisabled && gitStatus.hasWorkingTreeChanges; | 296 | const canCommit = !gitBaseDisabled && gitStatus.hasWorkingTreeChanges; |
| 345 | const canPush = | 297 | const canPush = |
| 346 | !gitBaseDisabled && hasBranch && (gitStatus.hasWorkingTreeChanges || gitStatus.aheadCount > 0); | 298 | !gitBaseDisabled && |
| | 299 | hasBranch && |
| | 300 | (gitStatus.hasWorkingTreeChanges || gitStatus.aheadCount > 0); |
| 347 | const canViewPr = | 301 | const canViewPr = |
| 348 | !gitBaseDisabled && | 302 | !gitBaseDisabled && |
| 349 | hasBranch && | 303 | hasBranch && |
| ... | @@ -403,9 +357,7 @@ export default function ChatView() { | ... | @@ -403,9 +357,7 @@ export default function ChatView() { |
| 403 | action, | 357 | action, |
| 404 | }); | 358 | }); |
| 405 | } catch (error) { | 359 | } catch (error) { |
| 406 | setGitActionError( | 360 | setGitActionError(error instanceof Error ? error.message : "Git action failed."); |
| 407 | error instanceof Error ? error.message : "Git action failed.", | | |
| 408 | ); | | |
| 409 | } finally { | 361 | } finally { |
| 410 | setIsGitActionRunning(false); | 362 | setIsGitActionRunning(false); |
| 411 | try { | 363 | try { |
| ... | @@ -425,9 +377,7 @@ export default function ChatView() { | ... | @@ -425,9 +377,7 @@ export default function ChatView() { |
| 425 | (activeThread.messages.length > 0 || | 377 | (activeThread.messages.length > 0 || |
| 426 | (activeThread.session !== null && activeThread.session.status !== "closed")), | 378 | (activeThread.session !== null && activeThread.session.status !== "closed")), |
| 427 | ); | 379 | ); |
| 428 | const terminalShortcutHint = navigator.platform.includes("Mac") | 380 | const terminalShortcutHint = navigator.platform.includes("Mac") ? "\u2318J" : "Ctrl+J"; |
| 429 | ? "\u2318J" | | |
| 430 | : "Ctrl+J"; | | |
| 431 | const focusComposer = useCallback(() => { | 381 | const focusComposer = useCallback(() => { |
| 432 | const textarea = textareaRef.current; | 382 | const textarea = textareaRef.current; |
| 433 | if (!textarea) return; | 383 | if (!textarea) return; |
| ... | @@ -505,11 +455,7 @@ export default function ChatView() { | ... | @@ -505,11 +455,7 @@ export default function ChatView() { |
| 505 | } catch (error) { | 455 | } catch (error) { |
| 506 | if (!cancelled) { | 456 | if (!cancelled) { |
| 507 | setGitStatus(null); | 457 | setGitStatus(null); |
| 508 | setGitActionError( | 458 | setGitActionError(error instanceof Error ? error.message : "Failed to read git status."); |
| 509 | error instanceof Error | | |
| 510 | ? error.message | | |
| 511 | : "Failed to read git status.", | | |
| 512 | ); | | |
| 513 | } | 459 | } |
| 514 | } | 460 | } |
| 515 | }; | 461 | }; |
| ... | @@ -927,9 +873,7 @@ export default function ChatView() { | ... | @@ -927,9 +873,7 @@ export default function ChatView() { |
| 927 | </button> | 873 | </button> |
| 928 | {isGitMenuOpen && ( | 874 | {isGitMenuOpen && ( |
| 929 | <div className="absolute right-0 top-full z-50 mt-1 w-[280px] rounded-3xl border border-border bg-popover p-3 shadow-xl"> | 875 | <div className="absolute right-0 top-full z-50 mt-1 w-[280px] rounded-3xl border border-border bg-popover p-3 shadow-xl"> |
| 930 | <p className="px-3 pb-2 text-[13px] text-muted-foreground/75"> | 876 | <p className="px-3 pb-2 text-[13px] text-muted-foreground/75">Git actions</p> |
| 931 | Git actions | | |
| 932 | </p> | | |
| 933 | {gitActionMenuItems.map((item) => { | 877 | {gitActionMenuItems.map((item) => { |
| 934 | return ( | 878 | return ( |
| 935 | <button | 879 | <button |
| ... | @@ -1036,7 +980,7 @@ export default function ChatView() { | ... | @@ -1036,7 +980,7 @@ export default function ChatView() { |
| 1036 | </button> | 980 | </button> |
| 1037 | <button | 981 | <button |
| 1038 | type="button" | 982 | type="button" |
| 1039 | className="rounded-md border border-sky-300/30 bg-sky-500/[0.15] px-2 py-1 text-[11px] text-sky-100 transition-colors duration-150 hover:bg-sky-500/[0.22] disabled:cursor-not-allowed disabled:opacity-50" | 983 | className="rounded-md border border-sky-300/30 bg-sky-500/15 px-2 py-1 text-[11px] text-sky-100 transition-colors duration-150 hover:bg-sky-500/22 disabled:cursor-not-allowed disabled:opacity-50" |
| 1040 | disabled={isResponding} | 984 | disabled={isResponding} |
| 1041 | onClick={() => void onRespondToApproval(approval.requestId, "acceptForSession")} | 985 | onClick={() => void onRespondToApproval(approval.requestId, "acceptForSession")} |
| 1042 | > | 986 | > |
| ... | @@ -1052,7 +996,7 @@ export default function ChatView() { | ... | @@ -1052,7 +996,7 @@ export default function ChatView() { |
| 1052 | </button> | 996 | </button> |
| 1053 | <button | 997 | <button |
| 1054 | type="button" | 998 | type="button" |
| 1055 | className="rounded-md border border-rose-300/30 bg-rose-500/[0.12] px-2 py-1 text-[11px] text-rose-100 transition-colors duration-150 hover:bg-rose-500/[0.2] disabled:cursor-not-allowed disabled:opacity-50" | 999 | className="rounded-md border border-rose-300/30 bg-rose-500/12 px-2 py-1 text-[11px] text-rose-100 transition-colors duration-150 hover:bg-rose-500/20 disabled:cursor-not-allowed disabled:opacity-50" |
| 1056 | disabled={isResponding} | 1000 | disabled={isResponding} |
| 1057 | onClick={() => void onRespondToApproval(approval.requestId, "cancel")} | 1001 | onClick={() => void onRespondToApproval(approval.requestId, "cancel")} |
| 1058 | > | 1002 | > |
| ... | @@ -1171,7 +1115,7 @@ export default function ChatView() { | ... | @@ -1171,7 +1115,7 @@ export default function ChatView() { |
| 1171 | <Fragment key={timelineEntry.id}> | 1115 | <Fragment key={timelineEntry.id}> |
| 1172 | <div className="flex justify-end"> | 1116 | <div className="flex justify-end"> |
| 1173 | <div className="max-w-[80%] rounded-2xl rounded-br-sm border border-border bg-secondary px-4 py-3"> | 1117 | <div className="max-w-[80%] rounded-2xl rounded-br-sm border border-border bg-secondary px-4 py-3"> |
| 1174 | <pre className="whitespace-pre-wrap break-words font-mono text-sm leading-relaxed text-foreground"> | 1118 | <pre className="whitespace-pre-wrap wrap-break-word font-mono text-sm leading-relaxed text-foreground"> |
| 1175 | {timelineEntry.message.text} | 1119 | {timelineEntry.message.text} |
| 1176 | </pre> | 1120 | </pre> |
| 1177 | <p className="mt-1.5 text-right text-[10px] text-muted-foreground/30"> | 1121 | <p className="mt-1.5 text-right text-[10px] text-muted-foreground/30"> |