@vibe-forge/client 0.11.0 → 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 (96) hide show
  1. package/dist/assets/{arc-M4HYfcHs.js → arc-CSepokz3.js} +1 -1
  2. package/dist/assets/{blockDiagram-c4efeb88-CUrDjrxj.js → blockDiagram-c4efeb88-D0ARcoNf.js} +1 -1
  3. package/dist/assets/{c4Diagram-c83219d4-BMEtqlFp.js → c4Diagram-c83219d4-BysYF9kP.js} +1 -1
  4. package/dist/assets/channel-CeKPk6Nd.js +1 -0
  5. package/dist/assets/{classDiagram-beda092f-BOmDJ0Ml.js → classDiagram-beda092f-BG1GhIOL.js} +1 -1
  6. package/dist/assets/{classDiagram-v2-2358418a-BODzX2MB.js → classDiagram-v2-2358418a-Dd08uGSH.js} +1 -1
  7. package/dist/assets/clone-CrkD2PuD.js +1 -0
  8. package/dist/assets/{createText-1719965b-B9Dd8zcR.js → createText-1719965b-CigPEIEn.js} +1 -1
  9. package/dist/assets/{cssMode-DLxG92Ot.js → cssMode-MjflyEfm.js} +1 -1
  10. package/dist/assets/{edges-96097737-CuZFd43m.js → edges-96097737-DuTBJJRv.js} +1 -1
  11. package/dist/assets/{erDiagram-0228fc6a-8g9lu2-Z.js → erDiagram-0228fc6a-Cp1bL7Y7.js} +1 -1
  12. package/dist/assets/{flowDb-c6c81e3f-BlBS1tdN.js → flowDb-c6c81e3f-BfKbhiq5.js} +1 -1
  13. package/dist/assets/{flowDiagram-50d868cf-u6mWflpF.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-BDqI2NFr.js → flowchart-elk-definition-6af322e1-EVeTDRRK.js} +1 -1
  16. package/dist/assets/{freemarker2-tVtpTMPu.js → freemarker2-Bb3-QAIN.js} +1 -1
  17. package/dist/assets/{ganttDiagram-a2739b55-CDQjx9Wu.js → ganttDiagram-a2739b55-DslB2U0R.js} +1 -1
  18. package/dist/assets/{gitGraphDiagram-82fe8481-DUHFKRVA.js → gitGraphDiagram-82fe8481-C-KFWMXL.js} +1 -1
  19. package/dist/assets/{graph-2HKPi5B_.js → graph-CukaUc0o.js} +1 -1
  20. package/dist/assets/{handlebars-D00tgNd8.js → handlebars-C4le-2Y6.js} +1 -1
  21. package/dist/assets/{html-B-TDzBiR.js → html-CjNiRs5S.js} +1 -1
  22. package/dist/assets/{htmlMode-ClycqSTM.js → htmlMode-B73_3-We.js} +1 -1
  23. package/dist/assets/{index-5325376f-DPrJpRQ-.js → index-5325376f-CVISZFPw.js} +1 -1
  24. package/dist/assets/{index-CAHZZEoo.js → index-BZosmb5_.js} +330 -326
  25. package/dist/assets/index-C1oh0w9H.css +32 -0
  26. package/dist/assets/{infoDiagram-8eee0895-Co5tS1I5.js → infoDiagram-8eee0895-DoirLE1K.js} +1 -1
  27. package/dist/assets/{javascript-zbkwarmb.js → javascript-BDjnqJFP.js} +1 -1
  28. package/dist/assets/{journeyDiagram-c64418c1-k_qioHgy.js → journeyDiagram-c64418c1-Ckn-p2CM.js} +1 -1
  29. package/dist/assets/{jsonMode-C3CSpzBF.js → jsonMode-C-ftOc5j.js} +1 -1
  30. package/dist/assets/{layout-CjOXKxvs.js → layout-Z7yUG7hB.js} +1 -1
  31. package/dist/assets/{line-C-XnQrKR.js → line-DPG_cfAy.js} +1 -1
  32. package/dist/assets/{linear-C7MMERzS.js → linear--GSeVfMi.js} +1 -1
  33. package/dist/assets/{liquid-5G37EU6K.js → liquid-COiLZ9py.js} +1 -1
  34. package/dist/assets/{lspLanguageFeatures-zaDMuhCE.js → lspLanguageFeatures-DGmhryFq.js} +1 -1
  35. package/dist/assets/{mdx-Bc-LY0gi.js → mdx-BpL87Gej.js} +1 -1
  36. package/dist/assets/{mermaid.core-CechbHof.js → mermaid.core-Cg1CCDo6.js} +4 -4
  37. package/dist/assets/{mindmap-definition-8da855dc-ejftCDGb.js → mindmap-definition-8da855dc-CKDof1lD.js} +1 -1
  38. package/dist/assets/{pieDiagram-a8764435-DY__X3Qj.js → pieDiagram-a8764435-DwvCaZVE.js} +1 -1
  39. package/dist/assets/{python-vK2Ff2J5.js → python-63dBmWV_.js} +1 -1
  40. package/dist/assets/{quadrantDiagram-1e28029f-azIZCv_2.js → quadrantDiagram-1e28029f-CkzYBQpy.js} +1 -1
  41. package/dist/assets/{razor-BipjBJKu.js → razor-C50tBqEZ.js} +1 -1
  42. package/dist/assets/{requirementDiagram-08caed73-C4EB0Xs2.js → requirementDiagram-08caed73-Brgdjqf4.js} +1 -1
  43. package/dist/assets/{sankeyDiagram-a04cb91d-PNhR6YWu.js → sankeyDiagram-a04cb91d-CGkYexrs.js} +1 -1
  44. package/dist/assets/{sequenceDiagram-c5b8d532-4c-qV-Ri.js → sequenceDiagram-c5b8d532-D0wE-_J8.js} +1 -1
  45. package/dist/assets/{stateDiagram-1ecb1508-CnURumPE.js → stateDiagram-1ecb1508-BYb3NCXZ.js} +1 -1
  46. package/dist/assets/{stateDiagram-v2-c2b004d7-DR2qHTPg.js → stateDiagram-v2-c2b004d7-DrPqi4Pt.js} +1 -1
  47. package/dist/assets/{styles-b4e223ce-B2PWXT_i.js → styles-b4e223ce-DD66TIO4.js} +1 -1
  48. package/dist/assets/{styles-ca3715f6-DEhgVF5H.js → styles-ca3715f6-iy02LHIV.js} +1 -1
  49. package/dist/assets/{styles-d45a18b0-DyzccA5F.js → styles-d45a18b0-BgqAgJyW.js} +1 -1
  50. package/dist/assets/{svgDrawCommon-b86b1483-C_1tMhxp.js → svgDrawCommon-b86b1483-CDq7ugnw.js} +1 -1
  51. package/dist/assets/{timeline-definition-faaaa080-FdaC0dQH.js → timeline-definition-faaaa080-DzcLLjK0.js} +1 -1
  52. package/dist/assets/{tsMode-CrMC5T3_.js → tsMode-BFRFI4ct.js} +1 -1
  53. package/dist/assets/{typescript-CRfPu8v7.js → typescript-CBZQRAPv.js} +1 -1
  54. package/dist/assets/{xml-jlRvQfFI.js → xml-BpWm6upt.js} +1 -1
  55. package/dist/assets/{xychartDiagram-f5964ef8-sxjv75h9.js → xychartDiagram-f5964ef8-zBN8FmLQ.js} +1 -1
  56. package/dist/assets/{yaml-B47_IHOH.js → yaml-CqbJPiIP.js} +1 -1
  57. package/dist/index.html +2 -2
  58. package/package.json +6 -6
  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/git-controls/BranchSwitcherDropdown.tsx +157 -0
  63. package/src/components/chat/git-controls/ChatGitControls.scss +616 -0
  64. package/src/components/chat/git-controls/ChatGitControls.tsx +151 -0
  65. package/src/components/chat/git-controls/GitCommitModal.tsx +199 -0
  66. package/src/components/chat/git-controls/GitCommitModalParts.tsx +151 -0
  67. package/src/components/chat/git-controls/GitOperationsDropdown.tsx +123 -0
  68. package/src/components/chat/git-controls/GitPushModal.tsx +106 -0
  69. package/src/components/chat/git-controls/GitWorktreeDropdown.tsx +68 -0
  70. package/src/components/chat/git-controls/git-branch-utils.ts +88 -0
  71. package/src/components/chat/git-controls/git-commit-utils.ts +79 -0
  72. package/src/components/chat/git-controls/git-mutation-utils.ts +69 -0
  73. package/src/components/chat/git-controls/git-operation-utils.ts +98 -0
  74. package/src/components/chat/git-controls/git-worktree-utils.ts +49 -0
  75. package/src/components/chat/git-controls/use-chat-git-commit.ts +185 -0
  76. package/src/components/chat/git-controls/use-chat-git-controls.ts +200 -0
  77. package/src/components/chat/git-controls/use-chat-git-push-state.ts +19 -0
  78. package/src/components/chat/git-controls/use-chat-git-worktrees.ts +39 -0
  79. package/src/components/chat/tools/DefaultTool.tsx +40 -31
  80. package/src/components/chat/tools/adapter-claude/GenericClaudeTool.tsx +1 -15
  81. package/src/components/chat/tools/adapter-claude/claude-tool-edit-builders.ts +8 -1
  82. package/src/components/chat/tools/adapter-claude/claude-tool-field-sections.tsx +10 -95
  83. package/src/components/chat/tools/core/ToolCallBox.scss +18 -0
  84. package/src/components/chat/tools/core/generic-tool-presentation.ts +661 -0
  85. package/src/components/chat/tools/core/tool-display.ts +12 -1
  86. package/src/components/chat/tools/core/tool-field-sections.tsx +132 -0
  87. package/src/components/chat/tools/core/tool-summary.ts +18 -6
  88. package/src/components/chat/tools/plugin-chrome-devtools/ChromeDevtoolsTool.tsx +0 -7
  89. package/src/hooks/chat/session-view-cache.ts +80 -0
  90. package/src/hooks/chat/use-chat-session-messages.ts +124 -30
  91. package/src/resources/locales/en.json +68 -0
  92. package/src/resources/locales/zh.json +68 -0
  93. package/dist/assets/channel-Cj3Cp2OJ.js +0 -1
  94. package/dist/assets/clone-B7Q9B1dS.js +0 -1
  95. package/dist/assets/flowDiagram-v2-4f6560a1-G3v545eF.js +0 -1
  96. package/dist/assets/index-Di7lePfb.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
+ }