@vibe-forge/client 0.10.1 → 0.11.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (133) hide show
  1. package/dist/assets/{arc-C1rWFTer.js → arc-CSepokz3.js} +1 -1
  2. package/dist/assets/{blockDiagram-c4efeb88-DlZ9x70F.js → blockDiagram-c4efeb88-D0ARcoNf.js} +1 -1
  3. package/dist/assets/{c4Diagram-c83219d4-BKKxi__y.js → c4Diagram-c83219d4-BysYF9kP.js} +1 -1
  4. package/dist/assets/channel-CeKPk6Nd.js +1 -0
  5. package/dist/assets/{classDiagram-beda092f-CVGPySZq.js → classDiagram-beda092f-BG1GhIOL.js} +1 -1
  6. package/dist/assets/{classDiagram-v2-2358418a-7kp8GVVj.js → classDiagram-v2-2358418a-Dd08uGSH.js} +1 -1
  7. package/dist/assets/clone-CrkD2PuD.js +1 -0
  8. package/dist/assets/{createText-1719965b-Dykv8kT9.js → createText-1719965b-CigPEIEn.js} +1 -1
  9. package/dist/assets/{cssMode-B59COYVW.js → cssMode-MjflyEfm.js} +1 -1
  10. package/dist/assets/{edges-96097737-CkZ1ZBro.js → edges-96097737-DuTBJJRv.js} +1 -1
  11. package/dist/assets/{erDiagram-0228fc6a-281ADcRp.js → erDiagram-0228fc6a-Cp1bL7Y7.js} +1 -1
  12. package/dist/assets/{flowDb-c6c81e3f-BQjX_flP.js → flowDb-c6c81e3f-BfKbhiq5.js} +1 -1
  13. package/dist/assets/{flowDiagram-50d868cf-DMHZTjES.js → flowDiagram-50d868cf-m7gGc3PK.js} +1 -1
  14. package/dist/assets/flowDiagram-v2-4f6560a1-4ZU4bdp1.js +1 -0
  15. package/dist/assets/{flowchart-elk-definition-6af322e1-CI3yz4z8.js → flowchart-elk-definition-6af322e1-EVeTDRRK.js} +1 -1
  16. package/dist/assets/{freemarker2-DWnWjibn.js → freemarker2-Bb3-QAIN.js} +1 -1
  17. package/dist/assets/{ganttDiagram-a2739b55-B3IING9L.js → ganttDiagram-a2739b55-DslB2U0R.js} +1 -1
  18. package/dist/assets/{gitGraphDiagram-82fe8481-CnArIr_T.js → gitGraphDiagram-82fe8481-C-KFWMXL.js} +1 -1
  19. package/dist/assets/{graph-BZ1F0Yve.js → graph-CukaUc0o.js} +1 -1
  20. package/dist/assets/{handlebars-C1QH9qTz.js → handlebars-C4le-2Y6.js} +1 -1
  21. package/dist/assets/{html-D1NkqHjC.js → html-CjNiRs5S.js} +1 -1
  22. package/dist/assets/{htmlMode-DAZCE_rA.js → htmlMode-B73_3-We.js} +1 -1
  23. package/dist/assets/{index-5325376f-Da9zSHjA.js → index-5325376f-CVISZFPw.js} +1 -1
  24. package/dist/assets/{index-C0vjF3D0.js → index-BZosmb5_.js} +336 -336
  25. package/dist/assets/index-C1oh0w9H.css +32 -0
  26. package/dist/assets/{infoDiagram-8eee0895-DYbFvRM7.js → infoDiagram-8eee0895-DoirLE1K.js} +1 -1
  27. package/dist/assets/{javascript-CoMjGRHa.js → javascript-BDjnqJFP.js} +1 -1
  28. package/dist/assets/{journeyDiagram-c64418c1-Boebox0b.js → journeyDiagram-c64418c1-Ckn-p2CM.js} +1 -1
  29. package/dist/assets/{jsonMode-D__gAvuz.js → jsonMode-C-ftOc5j.js} +1 -1
  30. package/dist/assets/{layout-CTcHNbHp.js → layout-Z7yUG7hB.js} +1 -1
  31. package/dist/assets/{line-4AwinCz2.js → line-DPG_cfAy.js} +1 -1
  32. package/dist/assets/{linear-CeSMLzJW.js → linear--GSeVfMi.js} +1 -1
  33. package/dist/assets/{liquid-DZF6egdE.js → liquid-COiLZ9py.js} +1 -1
  34. package/dist/assets/{lspLanguageFeatures-6K4lv5S2.js → lspLanguageFeatures-DGmhryFq.js} +1 -1
  35. package/dist/assets/{mdx-Cnt4ka6w.js → mdx-BpL87Gej.js} +1 -1
  36. package/dist/assets/{mermaid.core-B0yG5s4D.js → mermaid.core-Cg1CCDo6.js} +4 -4
  37. package/dist/assets/{mindmap-definition-8da855dc-KJEvXMKj.js → mindmap-definition-8da855dc-CKDof1lD.js} +1 -1
  38. package/dist/assets/{pieDiagram-a8764435-17nFAXPJ.js → pieDiagram-a8764435-DwvCaZVE.js} +1 -1
  39. package/dist/assets/{python-DA3TtjDv.js → python-63dBmWV_.js} +1 -1
  40. package/dist/assets/{quadrantDiagram-1e28029f-Dt4vubi-.js → quadrantDiagram-1e28029f-CkzYBQpy.js} +1 -1
  41. package/dist/assets/{razor-CWDJgvX_.js → razor-C50tBqEZ.js} +1 -1
  42. package/dist/assets/{requirementDiagram-08caed73-H6aDyDK-.js → requirementDiagram-08caed73-Brgdjqf4.js} +1 -1
  43. package/dist/assets/{sankeyDiagram-a04cb91d-DxsVtbjI.js → sankeyDiagram-a04cb91d-CGkYexrs.js} +1 -1
  44. package/dist/assets/{sequenceDiagram-c5b8d532-BHa148XJ.js → sequenceDiagram-c5b8d532-D0wE-_J8.js} +1 -1
  45. package/dist/assets/{stateDiagram-1ecb1508-DgwBm8LO.js → stateDiagram-1ecb1508-BYb3NCXZ.js} +1 -1
  46. package/dist/assets/{stateDiagram-v2-c2b004d7-BK7IQLVc.js → stateDiagram-v2-c2b004d7-DrPqi4Pt.js} +1 -1
  47. package/dist/assets/{styles-b4e223ce-DzW27Bc-.js → styles-b4e223ce-DD66TIO4.js} +1 -1
  48. package/dist/assets/{styles-ca3715f6-Dex2GiLT.js → styles-ca3715f6-iy02LHIV.js} +1 -1
  49. package/dist/assets/{styles-d45a18b0-B6fGtDKS.js → styles-d45a18b0-BgqAgJyW.js} +1 -1
  50. package/dist/assets/{svgDrawCommon-b86b1483-B4HYgfV5.js → svgDrawCommon-b86b1483-CDq7ugnw.js} +1 -1
  51. package/dist/assets/{timeline-definition-faaaa080--QSbWb25.js → timeline-definition-faaaa080-DzcLLjK0.js} +1 -1
  52. package/dist/assets/{tsMode-ZM7ocZCH.js → tsMode-BFRFI4ct.js} +1 -1
  53. package/dist/assets/{typescript-CKWDmBCc.js → typescript-CBZQRAPv.js} +1 -1
  54. package/dist/assets/{xml-DuEUAzPi.js → xml-BpWm6upt.js} +1 -1
  55. package/dist/assets/{xychartDiagram-f5964ef8-D09Zkv2K.js → xychartDiagram-f5964ef8-zBN8FmLQ.js} +1 -1
  56. package/dist/assets/{yaml-DL7QPRYk.js → yaml-CqbJPiIP.js} +1 -1
  57. package/dist/index.html +2 -2
  58. package/package.json +10 -10
  59. package/src/api/git.ts +78 -0
  60. package/src/api.ts +24 -0
  61. package/src/components/chat/ChatHeader.tsx +4 -0
  62. package/src/components/chat/ChatHistoryView.tsx +22 -13
  63. package/src/components/chat/git-controls/BranchSwitcherDropdown.tsx +157 -0
  64. package/src/components/chat/git-controls/ChatGitControls.scss +616 -0
  65. package/src/components/chat/git-controls/ChatGitControls.tsx +151 -0
  66. package/src/components/chat/git-controls/GitCommitModal.tsx +199 -0
  67. package/src/components/chat/git-controls/GitCommitModalParts.tsx +151 -0
  68. package/src/components/chat/git-controls/GitOperationsDropdown.tsx +123 -0
  69. package/src/components/chat/git-controls/GitPushModal.tsx +106 -0
  70. package/src/components/chat/git-controls/GitWorktreeDropdown.tsx +68 -0
  71. package/src/components/chat/git-controls/git-branch-utils.ts +88 -0
  72. package/src/components/chat/git-controls/git-commit-utils.ts +79 -0
  73. package/src/components/chat/git-controls/git-mutation-utils.ts +69 -0
  74. package/src/components/chat/git-controls/git-operation-utils.ts +98 -0
  75. package/src/components/chat/git-controls/git-worktree-utils.ts +49 -0
  76. package/src/components/chat/git-controls/use-chat-git-commit.ts +185 -0
  77. package/src/components/chat/git-controls/use-chat-git-controls.ts +200 -0
  78. package/src/components/chat/git-controls/use-chat-git-push-state.ts +19 -0
  79. package/src/components/chat/git-controls/use-chat-git-worktrees.ts +39 -0
  80. package/src/components/chat/messages/MessageStatusNotice.scss +163 -0
  81. package/src/components/chat/messages/MessageStatusNotice.tsx +48 -0
  82. package/src/components/chat/messages/build-chat-history-status-notices.ts +138 -0
  83. package/src/components/chat/sender/@components/sender-body/SenderBody.tsx +0 -24
  84. package/src/components/chat/sender/@core/build-sender-controller-result.ts +0 -6
  85. package/src/components/chat/sender/@hooks/use-sender-controller.ts +0 -2
  86. package/src/components/chat/sender/@types/sender-props.ts +0 -3
  87. package/src/components/chat/sender/Sender.scss +0 -58
  88. package/src/components/chat/sender/Sender.tsx +0 -2
  89. package/src/components/chat/tools/DefaultTool.tsx +84 -208
  90. package/src/components/chat/tools/adapter-claude/ClaudeEditDiff.tsx +30 -0
  91. package/src/components/chat/tools/adapter-claude/GenericClaudeTool.scss +128 -0
  92. package/src/components/chat/tools/adapter-claude/GenericClaudeTool.tsx +119 -0
  93. package/src/components/chat/tools/adapter-claude/claude-tool-edit-builders.ts +109 -0
  94. package/src/components/chat/tools/adapter-claude/claude-tool-field-sections.tsx +83 -0
  95. package/src/components/chat/tools/adapter-claude/claude-tool-operation-builders.ts +135 -0
  96. package/src/components/chat/tools/adapter-claude/claude-tool-presentation.ts +61 -0
  97. package/src/components/chat/tools/adapter-claude/claude-tool-shared.ts +185 -0
  98. package/src/components/chat/tools/adapter-claude/claude-tool-summary.ts +76 -0
  99. package/src/components/chat/tools/adapter-claude/claude-tool-system-builders.ts +125 -0
  100. package/src/components/chat/tools/adapter-claude/claude-tool-task-builders.ts +148 -0
  101. package/src/components/chat/tools/adapter-claude/index.ts +24 -15
  102. package/src/components/chat/tools/core/ToolCallBox.scss +362 -36
  103. package/src/components/chat/tools/core/ToolCallBox.tsx +35 -13
  104. package/src/components/chat/tools/core/ToolDiffViewer.scss +138 -0
  105. package/src/components/chat/tools/core/ToolDiffViewer.tsx +180 -0
  106. package/src/components/chat/tools/core/ToolGroup.scss +52 -74
  107. package/src/components/chat/tools/core/ToolGroup.tsx +25 -40
  108. package/src/components/chat/tools/core/ToolRenderer.tsx +3 -3
  109. package/src/components/chat/tools/core/ToolResultContent.tsx +66 -0
  110. package/src/components/chat/tools/core/ToolSummaryHeader.tsx +67 -0
  111. package/src/components/chat/tools/core/generic-tool-presentation.ts +661 -0
  112. package/src/components/chat/tools/core/tool-content-presence.ts +57 -0
  113. package/src/components/chat/tools/core/tool-display.ts +203 -0
  114. package/src/components/chat/tools/core/tool-field-sections.tsx +132 -0
  115. package/src/components/chat/tools/core/tool-result-content-utils.ts +171 -0
  116. package/src/components/chat/tools/core/tool-summary.ts +206 -0
  117. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.tsx +59 -53
  118. package/src/components/chat/tools/task/GetTaskInfoTool.tsx +26 -9
  119. package/src/components/chat/tools/task/ListTasksTool.tsx +22 -9
  120. package/src/components/chat/tools/task/StartTasksTool.tsx +22 -9
  121. package/src/hooks/chat/interaction-state.ts +29 -9
  122. package/src/hooks/chat/session-view-cache.ts +80 -0
  123. package/src/hooks/chat/use-chat-scroll.ts +2 -2
  124. package/src/hooks/chat/use-chat-session-messages.ts +139 -39
  125. package/src/hooks/chat/use-chat-session.ts +2 -2
  126. package/src/resources/locales/en.json +149 -0
  127. package/src/resources/locales/zh.json +149 -0
  128. package/src/routes/ChatRoute.tsx +24 -27
  129. package/src/utils/strip-ansi.ts +26 -0
  130. package/dist/assets/channel-F1aqMANO.js +0 -1
  131. package/dist/assets/clone-B-GCuXNo.js +0 -1
  132. package/dist/assets/flowDiagram-v2-4f6560a1-C5FzdVl1.js +0 -1
  133. package/dist/assets/index-vzEbM21t.css +0 -32
@@ -0,0 +1,151 @@
1
+ import './ChatGitControls.scss'
2
+
3
+ import { useTranslation } from 'react-i18next'
4
+
5
+ import { syncSessionGitBranch } from '#~/api'
6
+
7
+ import { BranchSwitcherDropdown } from './BranchSwitcherDropdown'
8
+ import { GitCommitModal } from './GitCommitModal'
9
+ import { GitOperationsDropdown } from './GitOperationsDropdown'
10
+ import { GitPushModal } from './GitPushModal'
11
+ import { GitWorktreeDropdown } from './GitWorktreeDropdown'
12
+ import { useChatGitControls } from './use-chat-git-controls'
13
+
14
+ export function ChatGitControls({
15
+ sessionId
16
+ }: {
17
+ sessionId: string
18
+ }) {
19
+ const { t } = useTranslation()
20
+ const git = useChatGitControls(sessionId)
21
+
22
+ if (git.repoState?.available !== true) {
23
+ return null
24
+ }
25
+
26
+ return (
27
+ <>
28
+ <div className='chat-header-git'>
29
+ <GitOperationsDropdown
30
+ isBusy={git.isBusy}
31
+ open={git.operationsMenuOpen}
32
+ repoState={git.repoState}
33
+ onOpenChange={(nextOpen) => {
34
+ git.setOperationsMenuOpen(nextOpen)
35
+ if (nextOpen) {
36
+ git.setBranchMenuOpen(false)
37
+ git.setWorktreeMenuOpen(false)
38
+ }
39
+ }}
40
+ onOpenCommit={() => {
41
+ git.setOperationsMenuOpen(false)
42
+ git.setCommitMessageError('')
43
+ git.setCommitModalOpen(true)
44
+ }}
45
+ onPush={git.handleOpenPushModal}
46
+ onSync={() => {
47
+ void git.runMutation(
48
+ 'sync',
49
+ () => syncSessionGitBranch(sessionId),
50
+ t('chat.gitSyncSuccess'),
51
+ () => git.setOperationsMenuOpen(false)
52
+ )
53
+ }}
54
+ />
55
+
56
+ <BranchSwitcherDropdown
57
+ availableLocalBranches={git.availableLocalBranches}
58
+ currentBranchLabel={git.currentBranchLabel}
59
+ isBusy={git.isBusy}
60
+ isLoading={git.isBranchListLoading}
61
+ open={git.branchMenuOpen}
62
+ repoState={git.repoState}
63
+ branchQuery={git.branchQuery}
64
+ canCreateBranch={git.canCreateBranch}
65
+ hasBranchResults={git.hasBranchResults}
66
+ remoteBranches={git.remoteBranches}
67
+ onCreateBranch={git.handleCreateBranch}
68
+ onOpenChange={(nextOpen) => {
69
+ git.setBranchMenuOpen(nextOpen)
70
+ if (nextOpen) {
71
+ git.setOperationsMenuOpen(false)
72
+ git.setWorktreeMenuOpen(false)
73
+ git.setShouldLoadBranches(true)
74
+ return
75
+ }
76
+ git.setBranchQuery('')
77
+ }}
78
+ onQueryChange={git.setBranchQuery}
79
+ onSwitchBranch={git.handleBranchSwitch}
80
+ />
81
+
82
+ {git.showWorktreeButton && (
83
+ <GitWorktreeDropdown
84
+ open={git.worktreeMenuOpen}
85
+ worktrees={git.worktrees}
86
+ onOpenChange={(nextOpen) => {
87
+ git.setWorktreeMenuOpen(nextOpen)
88
+ if (nextOpen) {
89
+ git.setOperationsMenuOpen(false)
90
+ git.setBranchMenuOpen(false)
91
+ }
92
+ }}
93
+ />
94
+ )}
95
+
96
+ <div className='chat-header-git__separator' />
97
+ </div>
98
+
99
+ <GitCommitModal
100
+ canCommitAndPush={git.canCommitAndPush}
101
+ canSubmit={git.canSubmitCommit}
102
+ commitAmend={git.commitAmend}
103
+ commitBlockedMessage={git.commitBlockedMessage}
104
+ commitForcePush={git.commitForcePush}
105
+ commitIncludeUnstagedChanges={git.commitIncludeUnstagedChanges}
106
+ commitMessage={git.commitMessage}
107
+ commitMessageError={git.commitMessageError}
108
+ commitNextStep={git.commitNextStep}
109
+ commitSkipHooks={git.commitSkipHooks}
110
+ commitSummary={git.commitSummary}
111
+ currentBranchLabel={git.currentBranchLabel}
112
+ headCommit={git.repoState.headCommit ?? null}
113
+ open={git.commitModalOpen}
114
+ pending={git.pendingAction === 'commit' || git.pendingAction === 'commit-and-push'}
115
+ onCancel={git.resetCommitState}
116
+ onCommit={git.handleCommit}
117
+ onNextStepChange={git.setCommitNextStep}
118
+ onToggleAmend={(checked) => {
119
+ git.setCommitAmend(checked)
120
+ if (git.commitMessageError !== '') {
121
+ git.setCommitMessageError('')
122
+ }
123
+ }}
124
+ onToggleForcePush={git.setCommitForcePush}
125
+ onToggleIncludeUnstagedChanges={(checked) => {
126
+ git.setCommitIncludeUnstagedChanges(checked)
127
+ }}
128
+ onToggleSkipHooks={git.setCommitSkipHooks}
129
+ onMessageChange={(value) => {
130
+ git.setCommitMessage(value)
131
+ if (git.commitMessageError !== '') {
132
+ git.setCommitMessageError('')
133
+ }
134
+ }}
135
+ />
136
+
137
+ <GitPushModal
138
+ blockedMessage={git.pushBlockedMessage}
139
+ currentBranchLabel={git.currentBranchLabel}
140
+ forcePush={git.pushForce}
141
+ hasUpstream={git.repoState.upstream != null && git.repoState.upstream.trim() !== ''}
142
+ open={git.pushModalOpen}
143
+ pending={git.pendingAction === 'push'}
144
+ upstreamLabel={git.repoState.upstream?.trim() || t('chat.gitNoUpstream')}
145
+ onCancel={git.resetPushState}
146
+ onPush={git.handlePush}
147
+ onToggleForcePush={git.setPushForce}
148
+ />
149
+ </>
150
+ )
151
+ }
@@ -0,0 +1,199 @@
1
+ import { Button, Input, Modal } from 'antd'
2
+ import { useTranslation } from 'react-i18next'
3
+
4
+ import type { GitChangeSummary, GitHeadCommitSummary } from '@vibe-forge/types'
5
+
6
+ import {
7
+ GitCommitForcePushOption,
8
+ GitCommitNextSteps,
9
+ GitCommitSummaryGrid,
10
+ GitCommitToggleRow,
11
+ getGitCommitNextSteps
12
+ } from './GitCommitModalParts'
13
+ import type { GitCommitNextStep } from './git-commit-utils'
14
+
15
+ export function GitCommitModal({
16
+ canCommitAndPush,
17
+ canSubmit,
18
+ commitAmend,
19
+ commitBlockedMessage,
20
+ commitForcePush,
21
+ commitIncludeUnstagedChanges,
22
+ commitMessage,
23
+ commitMessageError,
24
+ commitNextStep,
25
+ commitSkipHooks,
26
+ commitSummary,
27
+ currentBranchLabel,
28
+ headCommit,
29
+ open,
30
+ pending,
31
+ onCancel,
32
+ onCommit,
33
+ onMessageChange,
34
+ onNextStepChange,
35
+ onToggleAmend,
36
+ onToggleForcePush,
37
+ onToggleIncludeUnstagedChanges,
38
+ onToggleSkipHooks
39
+ }: {
40
+ canCommitAndPush: boolean
41
+ canSubmit: boolean
42
+ commitAmend: boolean
43
+ commitBlockedMessage: string
44
+ commitForcePush: boolean
45
+ commitIncludeUnstagedChanges: boolean
46
+ commitMessage: string
47
+ commitMessageError: string
48
+ commitNextStep: GitCommitNextStep
49
+ commitSkipHooks: boolean
50
+ commitSummary: GitChangeSummary | null
51
+ currentBranchLabel: string
52
+ headCommit: GitHeadCommitSummary | null
53
+ open: boolean
54
+ pending: boolean
55
+ onCancel: () => void
56
+ onCommit: () => void
57
+ onMessageChange: (value: string) => void
58
+ onNextStepChange: (value: GitCommitNextStep) => void
59
+ onToggleAmend: (checked: boolean) => void
60
+ onToggleForcePush: (checked: boolean) => void
61
+ onToggleIncludeUnstagedChanges: (checked: boolean) => void
62
+ onToggleSkipHooks: (checked: boolean) => void
63
+ }) {
64
+ const { i18n, t } = useTranslation()
65
+ const numberFormatter = new Intl.NumberFormat(i18n.language)
66
+ const nextSteps = getGitCommitNextSteps(t)
67
+ const changedFilesCount = commitSummary?.changedFiles ?? 0
68
+ return (
69
+ <Modal
70
+ centered
71
+ className='chat-header-git__commit-modal'
72
+ closeIcon={<span className='material-symbols-rounded'>close</span>}
73
+ destroyOnHidden
74
+ footer={null}
75
+ open={open}
76
+ title={null}
77
+ width={592}
78
+ onCancel={onCancel}
79
+ >
80
+ <div className='chat-header-git__commit-sheet'>
81
+ <div className='chat-header-git__commit-icon'>
82
+ <span className='material-symbols-rounded'>commit</span>
83
+ </div>
84
+ <div className='chat-header-git__commit-title'>
85
+ {t('chat.gitCommitPanelTitle')}
86
+ </div>
87
+ <GitCommitSummaryGrid
88
+ branchLabel={t('chat.gitCommitPanelBranch')}
89
+ changedFilesLabel={t('chat.gitChangedFilesCount', {
90
+ count: changedFilesCount
91
+ })}
92
+ changesLabel={t('chat.gitCommitPanelChanges')}
93
+ currentBranchLabel={currentBranchLabel}
94
+ formattedAdditions={numberFormatter.format(commitSummary?.additions ?? 0)}
95
+ formattedDeletions={numberFormatter.format(commitSummary?.deletions ?? 0)}
96
+ summary={commitSummary}
97
+ />
98
+
99
+ <div className='chat-header-git__commit-toggles'>
100
+ <GitCommitToggleRow
101
+ checked={commitIncludeUnstagedChanges}
102
+ description={commitIncludeUnstagedChanges
103
+ ? t('chat.gitCommitIncludeUnstagedChangesDescription')
104
+ : t('chat.gitCommitOnlyStagedChangesDescription')}
105
+ disabled={pending}
106
+ title={t('chat.gitCommitIncludeUnstagedChanges')}
107
+ onChange={onToggleIncludeUnstagedChanges}
108
+ />
109
+
110
+ <GitCommitToggleRow
111
+ checked={commitSkipHooks}
112
+ description={t('chat.gitCommitSkipHooksDescription')}
113
+ disabled={pending}
114
+ title={t('chat.gitCommitSkipHooks')}
115
+ onChange={onToggleSkipHooks}
116
+ />
117
+
118
+ <GitCommitToggleRow
119
+ checked={commitAmend}
120
+ description={headCommit == null
121
+ ? t('chat.gitCommitAmendUnavailableDescription')
122
+ : t('chat.gitCommitAmendDescription', { subject: headCommit.subject })}
123
+ disabled={pending || headCommit == null}
124
+ title={t('chat.gitCommitAmend')}
125
+ onChange={onToggleAmend}
126
+ />
127
+ </div>
128
+
129
+ <div className='chat-header-git__commit-section-header'>
130
+ <span>{t('chat.gitCommitMessageLabel')}</span>
131
+ {commitAmend && (
132
+ <span className='chat-header-git__overlay-meta'>
133
+ {t('chat.gitCommitMessageOptional')}
134
+ </span>
135
+ )}
136
+ </div>
137
+
138
+ <Input.TextArea
139
+ autoFocus
140
+ className='chat-header-git__commit-input'
141
+ placeholder={commitAmend
142
+ ? t('chat.gitCommitMessagePlaceholderAmend')
143
+ : t('chat.gitCommitMessagePlaceholder')}
144
+ rows={3}
145
+ status={commitMessageError !== '' ? 'error' : undefined}
146
+ value={commitMessage}
147
+ onChange={(event) => onMessageChange(event.target.value)}
148
+ />
149
+
150
+ {commitMessageError !== '' && (
151
+ <div className='chat-header-git__commit-error'>{commitMessageError}</div>
152
+ )}
153
+ {commitMessageError === '' && commitAmend && (
154
+ <div className='chat-header-git__overlay-meta'>
155
+ {t('chat.gitCommitMessageAmendHint')}
156
+ </div>
157
+ )}
158
+
159
+ <div className='chat-header-git__commit-section-header'>
160
+ <span>{t('chat.gitCommitNextStep')}</span>
161
+ </div>
162
+
163
+ <GitCommitNextSteps
164
+ canCommitAndPush={canCommitAndPush}
165
+ pending={pending}
166
+ selected={commitNextStep}
167
+ steps={nextSteps}
168
+ onChange={onNextStepChange}
169
+ />
170
+
171
+ {commitNextStep === 'commit-and-push' && (
172
+ <GitCommitForcePushOption
173
+ checked={commitForcePush}
174
+ pending={pending}
175
+ onChange={onToggleForcePush}
176
+ />
177
+ )}
178
+
179
+ {!canSubmit && commitBlockedMessage !== '' && (
180
+ <div className='chat-header-git__overlay-meta'>
181
+ {commitBlockedMessage}
182
+ </div>
183
+ )}
184
+
185
+ <div className='chat-header-git__commit-footer'>
186
+ <Button
187
+ className='chat-header-git__commit-submit'
188
+ disabled={!canSubmit}
189
+ loading={pending}
190
+ type='primary'
191
+ onClick={onCommit}
192
+ >
193
+ {t('common.continue')}
194
+ </Button>
195
+ </div>
196
+ </div>
197
+ </Modal>
198
+ )
199
+ }
@@ -0,0 +1,151 @@
1
+ import { useTranslation } from 'react-i18next'
2
+
3
+ import type { GitChangeSummary } from '@vibe-forge/types'
4
+
5
+ import type { GitCommitNextStep } from './git-commit-utils'
6
+
7
+ export function GitCommitSummaryGrid({
8
+ changedFilesLabel,
9
+ changesLabel,
10
+ currentBranchLabel,
11
+ formattedAdditions,
12
+ formattedDeletions,
13
+ summary,
14
+ branchLabel
15
+ }: {
16
+ changedFilesLabel: string
17
+ changesLabel: string
18
+ currentBranchLabel: string
19
+ formattedAdditions: string
20
+ formattedDeletions: string
21
+ summary: GitChangeSummary | null
22
+ branchLabel: string
23
+ }) {
24
+ return (
25
+ <div className='chat-header-git__commit-summary-grid'>
26
+ <div className='chat-header-git__commit-summary-label'>{branchLabel}</div>
27
+ <div className='chat-header-git__commit-summary-value chat-header-git__commit-summary-value--branch'>
28
+ <span className='material-symbols-rounded'>call_split</span>
29
+ <span>{currentBranchLabel}</span>
30
+ </div>
31
+
32
+ <div className='chat-header-git__commit-summary-label'>{changesLabel}</div>
33
+ <div className='chat-header-git__commit-summary-value chat-header-git__commit-summary-value--stats'>
34
+ <span>{changedFilesLabel}</span>
35
+ <span className='chat-header-git__commit-summary-delta is-positive'>+{formattedAdditions}</span>
36
+ <span className='chat-header-git__commit-summary-delta is-negative'>-{formattedDeletions}</span>
37
+ </div>
38
+ </div>
39
+ )
40
+ }
41
+
42
+ export function GitCommitToggleRow({
43
+ checked,
44
+ description,
45
+ disabled,
46
+ onChange,
47
+ title
48
+ }: {
49
+ checked: boolean
50
+ description: string
51
+ disabled?: boolean
52
+ onChange: (checked: boolean) => void
53
+ title: string
54
+ }) {
55
+ return (
56
+ <label className={`chat-header-git__commit-toggle-row ${disabled ? 'is-disabled' : ''}`}>
57
+ <input
58
+ checked={checked}
59
+ className='chat-header-git__commit-toggle-checkbox'
60
+ disabled={disabled}
61
+ type='checkbox'
62
+ onChange={(event) => onChange(event.target.checked)}
63
+ />
64
+ <div className='chat-header-git__commit-toggle-copy'>
65
+ <div className='chat-header-git__commit-toggle-title'>{title}</div>
66
+ <div className='chat-header-git__commit-toggle-description'>{description}</div>
67
+ </div>
68
+ </label>
69
+ )
70
+ }
71
+
72
+ export function GitCommitNextSteps({
73
+ canCommitAndPush,
74
+ pending,
75
+ selected,
76
+ onChange,
77
+ steps
78
+ }: {
79
+ canCommitAndPush: boolean
80
+ pending: boolean
81
+ selected: GitCommitNextStep
82
+ onChange: (value: GitCommitNextStep) => void
83
+ steps: Array<{
84
+ icon: string
85
+ key: GitCommitNextStep
86
+ label: string
87
+ }>
88
+ }) {
89
+ return (
90
+ <div className='chat-header-git__commit-next-steps'>
91
+ {steps.map(step => {
92
+ const disabled = pending || (step.key === 'commit-and-push' && !canCommitAndPush)
93
+ const isSelected = selected === step.key
94
+ return (
95
+ <button
96
+ key={step.key}
97
+ aria-pressed={isSelected}
98
+ className={`chat-header-git__commit-next-step ${isSelected ? 'is-selected' : ''}`}
99
+ disabled={disabled}
100
+ type='button'
101
+ onClick={() => onChange(step.key)}
102
+ >
103
+ <div className='chat-header-git__commit-next-step-main'>
104
+ <span className='chat-header-git__row-icon material-symbols-rounded'>{step.icon}</span>
105
+ <span>{step.label}</span>
106
+ </div>
107
+ {isSelected && (
108
+ <span className='chat-header-git__row-state material-symbols-rounded'>check</span>
109
+ )}
110
+ </button>
111
+ )
112
+ })}
113
+ </div>
114
+ )
115
+ }
116
+
117
+ export function GitCommitForcePushOption({
118
+ checked,
119
+ pending,
120
+ onChange
121
+ }: {
122
+ checked: boolean
123
+ pending: boolean
124
+ onChange: (checked: boolean) => void
125
+ }) {
126
+ const { t } = useTranslation()
127
+
128
+ return (
129
+ <div className='chat-header-git__commit-sub-options'>
130
+ <GitCommitToggleRow
131
+ checked={checked}
132
+ description={t('chat.gitForcePushDescription')}
133
+ disabled={pending}
134
+ title={t('chat.gitForcePush')}
135
+ onChange={onChange}
136
+ />
137
+ {checked && (
138
+ <div className='chat-header-git__overlay-meta'>
139
+ {t('chat.gitForcePushHint')}
140
+ </div>
141
+ )}
142
+ </div>
143
+ )
144
+ }
145
+
146
+ export const getGitCommitNextSteps = (t: ReturnType<typeof useTranslation>['t']) => {
147
+ return [
148
+ { key: 'commit', icon: 'commit', label: t('chat.gitCommitShort') },
149
+ { key: 'commit-and-push', icon: 'upload', label: t('chat.gitCommitAndPush') }
150
+ ] satisfies Array<{ icon: string; key: GitCommitNextStep; label: string }>
151
+ }
@@ -0,0 +1,123 @@
1
+ import { Button, Dropdown } from 'antd'
2
+ import { useTranslation } from 'react-i18next'
3
+
4
+ import type { GitRepositoryState } from '@vibe-forge/types'
5
+ import type { GitOperationKind } from './git-operation-utils'
6
+
7
+ import { getPrimaryGitOperationKind, isGitOperationDisabled } from './git-operation-utils'
8
+
9
+ export function GitOperationsDropdown({
10
+ isBusy,
11
+ open,
12
+ repoState,
13
+ onOpenChange,
14
+ onOpenCommit,
15
+ onPush,
16
+ onSync
17
+ }: {
18
+ isBusy: boolean
19
+ open: boolean
20
+ repoState: GitRepositoryState
21
+ onOpenChange: (open: boolean) => void
22
+ onOpenCommit: () => void
23
+ onPush: () => void
24
+ onSync: () => void
25
+ }) {
26
+ const { t } = useTranslation()
27
+ const primaryActionKind = getPrimaryGitOperationKind(repoState)
28
+ const operationKinds: GitOperationKind[] = primaryActionKind == null
29
+ ? ['commit', 'push', 'sync']
30
+ : [
31
+ primaryActionKind,
32
+ ...(['commit', 'push', 'sync'] as GitOperationKind[]).filter(kind => kind !== primaryActionKind)
33
+ ]
34
+ const actionMap = {
35
+ commit: {
36
+ disabled: isBusy || isGitOperationDisabled(repoState, 'commit'),
37
+ icon: 'commit',
38
+ label: t('chat.gitCommitShort'),
39
+ onClick: onOpenCommit
40
+ },
41
+ push: {
42
+ disabled: isBusy || isGitOperationDisabled(repoState, 'push'),
43
+ icon: 'upload',
44
+ label: t('chat.gitPushShort'),
45
+ onClick: onPush
46
+ },
47
+ sync: {
48
+ disabled: isBusy || isGitOperationDisabled(repoState, 'sync'),
49
+ icon: 'sync',
50
+ label: t('chat.gitSyncShort'),
51
+ onClick: onSync
52
+ }
53
+ } satisfies Record<GitOperationKind, {
54
+ disabled: boolean
55
+ icon: string
56
+ label: string
57
+ onClick: () => void
58
+ }>
59
+ const primaryAction = primaryActionKind != null ? actionMap[primaryActionKind] : null
60
+
61
+ return (
62
+ <div className={`chat-header-git__split ${open ? 'is-open' : ''}`}>
63
+ <Button
64
+ type='text'
65
+ className='chat-header-git__split-main'
66
+ disabled={primaryAction?.disabled ?? true}
67
+ title={primaryAction?.label ?? t('chat.gitOperations')}
68
+ aria-label={primaryAction?.label ?? t('chat.gitOperations')}
69
+ onClick={() => {
70
+ primaryAction?.onClick()
71
+ }}
72
+ >
73
+ <span className='material-symbols-rounded'>
74
+ {primaryAction?.icon ?? 'deployed_code'}
75
+ </span>
76
+ <span className='chat-header-git__trigger-label'>
77
+ {primaryAction?.label ?? t('chat.gitOperations')}
78
+ </span>
79
+ </Button>
80
+
81
+ {primaryActionKind !== 'commit' && (
82
+ <div className='chat-header-git__split-divider' />
83
+ )}
84
+
85
+ <Dropdown
86
+ open={open}
87
+ placement='bottomLeft'
88
+ trigger={['click']}
89
+ onOpenChange={onOpenChange}
90
+ dropdownRender={() => (
91
+ <div className='chat-header-git__overlay chat-header-git__overlay--operations'>
92
+ {operationKinds.map(kind => {
93
+ const action = actionMap[kind]
94
+ return (
95
+ <button
96
+ key={kind}
97
+ type='button'
98
+ className='chat-header-git__operation-row'
99
+ disabled={action.disabled}
100
+ onClick={action.onClick}
101
+ >
102
+ <div className='chat-header-git__operation-row-main'>
103
+ <span className='chat-header-git__row-icon material-symbols-rounded'>{action.icon}</span>
104
+ <span className='chat-header-git__row-title'>{action.label}</span>
105
+ </div>
106
+ </button>
107
+ )
108
+ })}
109
+ </div>
110
+ )}
111
+ >
112
+ <Button
113
+ type='text'
114
+ className='chat-header-git__split-toggle'
115
+ title={t('chat.gitOperations')}
116
+ aria-label={t('chat.gitOperations')}
117
+ >
118
+ <span className='material-symbols-rounded'>expand_more</span>
119
+ </Button>
120
+ </Dropdown>
121
+ </div>
122
+ )
123
+ }