commitgpt 0.3.1 → 0.3.4
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.
- checksums.yaml +4 -4
- data/bin/aicm +1 -1
- data/commitgpt.gemspec +19 -18
- data/lib/commitgpt/cli.rb +28 -23
- data/lib/commitgpt/commit_ai.rb +92 -210
- data/lib/commitgpt/config_manager.rb +34 -34
- data/lib/commitgpt/diff_helpers.rb +149 -0
- data/lib/commitgpt/provider_presets.rb +13 -12
- data/lib/commitgpt/setup_wizard.rb +87 -84
- data/lib/commitgpt/string.rb +19 -0
- data/lib/commitgpt/version.rb +1 -1
- data/lib/commitgpt.rb +2 -2
- metadata +7 -5
data/lib/commitgpt/commit_ai.rb
CHANGED
|
@@ -1,33 +1,36 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require
|
|
4
|
-
require
|
|
5
|
-
require
|
|
6
|
-
require
|
|
7
|
-
require
|
|
8
|
-
require
|
|
9
|
-
require_relative
|
|
10
|
-
require_relative
|
|
3
|
+
require 'httparty'
|
|
4
|
+
require 'net/http'
|
|
5
|
+
require 'uri'
|
|
6
|
+
require 'json'
|
|
7
|
+
require 'io/console'
|
|
8
|
+
require 'tty-prompt'
|
|
9
|
+
require_relative 'string'
|
|
10
|
+
require_relative 'config_manager'
|
|
11
|
+
require_relative 'diff_helpers'
|
|
11
12
|
|
|
12
13
|
# CommitGpt based on GPT-3
|
|
13
14
|
module CommitGpt
|
|
14
15
|
# Commit AI roboter based on GPT-3
|
|
15
16
|
class CommitAi
|
|
17
|
+
include DiffHelpers
|
|
18
|
+
|
|
16
19
|
attr_reader :api_key, :base_url, :model, :diff_len
|
|
17
20
|
|
|
18
21
|
def initialize
|
|
19
22
|
provider_config = ConfigManager.get_active_provider_config
|
|
20
|
-
|
|
23
|
+
|
|
21
24
|
if provider_config
|
|
22
|
-
@api_key = provider_config[
|
|
23
|
-
@base_url = provider_config[
|
|
24
|
-
@model = provider_config[
|
|
25
|
-
@diff_len = provider_config[
|
|
25
|
+
@api_key = provider_config['api_key']
|
|
26
|
+
@base_url = provider_config['base_url']
|
|
27
|
+
@model = provider_config['model']
|
|
28
|
+
@diff_len = provider_config['diff_len'] || 32_768
|
|
26
29
|
else
|
|
27
30
|
@api_key = nil
|
|
28
31
|
@base_url = nil
|
|
29
32
|
@model = nil
|
|
30
|
-
@diff_len =
|
|
33
|
+
@diff_len = 32_768
|
|
31
34
|
end
|
|
32
35
|
end
|
|
33
36
|
|
|
@@ -47,7 +50,7 @@ module CommitGpt
|
|
|
47
50
|
case action
|
|
48
51
|
when :commit
|
|
49
52
|
commit_command = "git commit -m \"#{ai_commit_message}\""
|
|
50
|
-
puts "\n▲ Executing: #{commit_command}".
|
|
53
|
+
puts "\n▲ Executing: #{commit_command}".yellow
|
|
51
54
|
system(commit_command)
|
|
52
55
|
puts "\n\n"
|
|
53
56
|
puts `git log -1`
|
|
@@ -57,18 +60,18 @@ module CommitGpt
|
|
|
57
60
|
next
|
|
58
61
|
when :edit
|
|
59
62
|
prompt = TTY::Prompt.new
|
|
60
|
-
new_message = prompt.ask(
|
|
63
|
+
new_message = prompt.ask('Enter your commit message:')
|
|
61
64
|
if new_message && !new_message.strip.empty?
|
|
62
65
|
commit_command = "git commit -m \"#{new_message}\""
|
|
63
66
|
system(commit_command)
|
|
64
67
|
puts "\n"
|
|
65
68
|
puts `git log -1`
|
|
66
69
|
else
|
|
67
|
-
puts
|
|
70
|
+
puts '▲ Commit aborted (empty message).'.red
|
|
68
71
|
end
|
|
69
72
|
break
|
|
70
73
|
when :exit
|
|
71
|
-
puts
|
|
74
|
+
puts '▲ Exit without commit.'.yellow
|
|
72
75
|
break
|
|
73
76
|
end
|
|
74
77
|
end
|
|
@@ -76,15 +79,15 @@ module CommitGpt
|
|
|
76
79
|
|
|
77
80
|
def list_models
|
|
78
81
|
headers = {
|
|
79
|
-
|
|
80
|
-
|
|
82
|
+
'Content-Type' => 'application/json',
|
|
83
|
+
'User-Agent' => "Ruby/#{RUBY_VERSION}"
|
|
81
84
|
}
|
|
82
|
-
headers[
|
|
85
|
+
headers['Authorization'] = "Bearer #{@api_key}" if @api_key
|
|
83
86
|
|
|
84
87
|
begin
|
|
85
88
|
response = HTTParty.get("#{@base_url}/models", headers: headers)
|
|
86
|
-
models = response[
|
|
87
|
-
models.each { |m| puts m[
|
|
89
|
+
models = response['data'] || []
|
|
90
|
+
models.each { |m| puts m['id'] }
|
|
88
91
|
rescue StandardError => e
|
|
89
92
|
puts "▲ Failed to list models: #{e.message}".red
|
|
90
93
|
end
|
|
@@ -92,15 +95,15 @@ module CommitGpt
|
|
|
92
95
|
|
|
93
96
|
def list_models
|
|
94
97
|
headers = {
|
|
95
|
-
|
|
96
|
-
|
|
98
|
+
'Content-Type' => 'application/json',
|
|
99
|
+
'User-Agent' => "Ruby/#{RUBY_VERSION}"
|
|
97
100
|
}
|
|
98
|
-
headers[
|
|
101
|
+
headers['Authorization'] = "Bearer #{AICM_KEY}" if AICM_KEY
|
|
99
102
|
|
|
100
103
|
begin
|
|
101
104
|
response = HTTParty.get("#{AICM_LINK}/models", headers: headers)
|
|
102
|
-
models = response[
|
|
103
|
-
models.each { |m| puts m[
|
|
105
|
+
models = response['data'] || []
|
|
106
|
+
models.each { |m| puts m['id'] }
|
|
104
107
|
rescue StandardError => e
|
|
105
108
|
puts "▲ Failed to list models: #{e.message}".red
|
|
106
109
|
end
|
|
@@ -108,15 +111,15 @@ module CommitGpt
|
|
|
108
111
|
|
|
109
112
|
private
|
|
110
113
|
|
|
111
|
-
def confirm_commit(
|
|
114
|
+
def confirm_commit(_message)
|
|
112
115
|
prompt = TTY::Prompt.new
|
|
113
|
-
|
|
116
|
+
|
|
114
117
|
begin
|
|
115
|
-
prompt.select(
|
|
116
|
-
menu.choice
|
|
117
|
-
menu.choice
|
|
118
|
-
menu.choice
|
|
119
|
-
menu.choice
|
|
118
|
+
prompt.select('Action:') do |menu|
|
|
119
|
+
menu.choice 'Commit', :commit
|
|
120
|
+
menu.choice 'Regenerate', :regenerate
|
|
121
|
+
menu.choice 'Edit', :edit
|
|
122
|
+
menu.choice 'Exit without commit', :exit
|
|
120
123
|
end
|
|
121
124
|
rescue TTY::Reader::InputInterrupt, Interrupt
|
|
122
125
|
:exit
|
|
@@ -127,132 +130,12 @@ module CommitGpt
|
|
|
127
130
|
generate_commit(diff)
|
|
128
131
|
end
|
|
129
132
|
|
|
130
|
-
def git_diff
|
|
131
|
-
exclusions = '":(exclude)Gemfile.lock" ":(exclude)package-lock.json" ":(exclude)yarn.lock" ":(exclude)pnpm-lock.yaml"'
|
|
132
|
-
diff_cached = `git diff --cached . #{exclusions}`.chomp
|
|
133
|
-
diff_unstaged = `git diff . #{exclusions}`.chomp
|
|
134
|
-
|
|
135
|
-
if !diff_unstaged.empty?
|
|
136
|
-
if !diff_cached.empty?
|
|
137
|
-
# Scenario: Mixed state (some staged, some not)
|
|
138
|
-
puts "▲ You have both staged and unstaged changes:".yellow
|
|
139
|
-
|
|
140
|
-
staged_files = `git diff --cached --name-status . #{exclusions}`.chomp
|
|
141
|
-
unstaged_files = `git diff --name-status . #{exclusions}`.chomp
|
|
142
|
-
|
|
143
|
-
puts "\n #{'Staged changes:'.green}"
|
|
144
|
-
puts staged_files.gsub(/^/, " ")
|
|
145
|
-
|
|
146
|
-
puts "\n #{'Unstaged changes:'.red}"
|
|
147
|
-
puts unstaged_files.gsub(/^/, " ")
|
|
148
|
-
puts ""
|
|
149
|
-
|
|
150
|
-
prompt = TTY::Prompt.new
|
|
151
|
-
choice = prompt.select("How to proceed?") do |menu|
|
|
152
|
-
menu.choice "Include unstaged changes (git add .)", :add_all
|
|
153
|
-
menu.choice "Use staged changes only", :staged_only
|
|
154
|
-
menu.choice "Exit", :exit
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
case choice
|
|
158
|
-
when :add_all
|
|
159
|
-
puts "▲ Running git add .".yellow
|
|
160
|
-
system("git add .")
|
|
161
|
-
diff_cached = `git diff --cached . #{exclusions}`.chomp
|
|
162
|
-
when :exit
|
|
163
|
-
return nil
|
|
164
|
-
end
|
|
165
|
-
else
|
|
166
|
-
# Scenario: Only unstaged changes
|
|
167
|
-
choice = prompt_no_staged_changes
|
|
168
|
-
case choice
|
|
169
|
-
when :add_all
|
|
170
|
-
puts "▲ Running git add .".yellow
|
|
171
|
-
system("git add .")
|
|
172
|
-
diff_cached = `git diff --cached . #{exclusions}`.chomp
|
|
173
|
-
if diff_cached.empty?
|
|
174
|
-
puts "▲ Still no changes to commit.".red
|
|
175
|
-
return nil
|
|
176
|
-
end
|
|
177
|
-
when :exit
|
|
178
|
-
return nil
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
elsif diff_cached.empty?
|
|
182
|
-
# Scenario: No changes at all (staged or unstaged)
|
|
183
|
-
# Check if there are ANY unstaged files (maybe untracked?)
|
|
184
|
-
# git status --porcelain includes untracked files
|
|
185
|
-
git_status = `git status --porcelain`.chomp
|
|
186
|
-
if git_status.empty?
|
|
187
|
-
puts "▲ No changes to commit. Working tree clean.".yellow
|
|
188
|
-
return nil
|
|
189
|
-
else
|
|
190
|
-
# Only untracked files? Or ignored files?
|
|
191
|
-
# If diff_unstaged is empty but git status is not, it usually means untracked files.
|
|
192
|
-
# Let's offer to add them too.
|
|
193
|
-
choice = prompt_no_staged_changes
|
|
194
|
-
case choice
|
|
195
|
-
when :add_all
|
|
196
|
-
puts "▲ Running git add .".yellow
|
|
197
|
-
system("git add .")
|
|
198
|
-
diff_cached = `git diff --cached . #{exclusions}`.chomp
|
|
199
|
-
when :exit
|
|
200
|
-
return nil
|
|
201
|
-
end
|
|
202
|
-
end
|
|
203
|
-
end
|
|
204
|
-
|
|
205
|
-
diff = diff_cached
|
|
206
|
-
|
|
207
|
-
if diff.length > @diff_len
|
|
208
|
-
choice = prompt_diff_handling(diff.length, @diff_len)
|
|
209
|
-
case choice
|
|
210
|
-
when :truncate
|
|
211
|
-
puts "▲ Truncating diff to #{@diff_len} chars...".yellow
|
|
212
|
-
diff = diff[0...@diff_len]
|
|
213
|
-
when :unlimited
|
|
214
|
-
puts "▲ Using full diff (#{diff.length} chars)...".yellow
|
|
215
|
-
when :exit
|
|
216
|
-
return nil
|
|
217
|
-
end
|
|
218
|
-
end
|
|
219
|
-
|
|
220
|
-
diff
|
|
221
|
-
end
|
|
222
|
-
|
|
223
|
-
def prompt_no_staged_changes
|
|
224
|
-
puts "▲ No staged changes found (but unstaged/untracked files exist).".yellow
|
|
225
|
-
prompt = TTY::Prompt.new
|
|
226
|
-
begin
|
|
227
|
-
prompt.select("Choose an option:") do |menu|
|
|
228
|
-
menu.choice "Run 'git add .' to stage all changes", :add_all
|
|
229
|
-
menu.choice "Exit (stage files manually)", :exit
|
|
230
|
-
end
|
|
231
|
-
rescue TTY::Reader::InputInterrupt, Interrupt
|
|
232
|
-
:exit
|
|
233
|
-
end
|
|
234
|
-
end
|
|
235
|
-
|
|
236
|
-
def prompt_diff_handling(current_len, max_len)
|
|
237
|
-
puts "▲ The diff is too large (#{current_len} chars, max #{max_len}).".yellow
|
|
238
|
-
prompt = TTY::Prompt.new
|
|
239
|
-
begin
|
|
240
|
-
prompt.select("Choose an option:") do |menu|
|
|
241
|
-
menu.choice "Use first #{max_len} characters to generate commit message", :truncate
|
|
242
|
-
menu.choice "Use unlimited characters (may fail or be slow)", :unlimited
|
|
243
|
-
menu.choice "Exit", :exit
|
|
244
|
-
end
|
|
245
|
-
rescue TTY::Reader::InputInterrupt, Interrupt
|
|
246
|
-
:exit
|
|
247
|
-
end
|
|
248
|
-
end
|
|
249
|
-
|
|
250
133
|
def welcome
|
|
251
134
|
puts "\n▲ Welcome to AI Commits!".green
|
|
252
135
|
|
|
253
136
|
# Check if config exists
|
|
254
137
|
unless ConfigManager.config_exists?
|
|
255
|
-
puts
|
|
138
|
+
puts '▲ Configuration not found. Generating default config...'.yellow
|
|
256
139
|
ConfigManager.generate_default_configs
|
|
257
140
|
puts "▲ Please run 'aicm setup' to configure your provider.".red
|
|
258
141
|
return false
|
|
@@ -272,36 +155,36 @@ module CommitGpt
|
|
|
272
155
|
begin
|
|
273
156
|
`git rev-parse --is-inside-work-tree`
|
|
274
157
|
rescue StandardError
|
|
275
|
-
puts
|
|
158
|
+
puts '▲ This is not a git repository'.red
|
|
276
159
|
return false
|
|
277
160
|
end
|
|
278
161
|
|
|
279
162
|
true
|
|
280
163
|
end
|
|
281
164
|
|
|
282
|
-
def generate_commit(diff =
|
|
165
|
+
def generate_commit(diff = '')
|
|
283
166
|
messages = [
|
|
284
167
|
{
|
|
285
|
-
role:
|
|
286
|
-
content:
|
|
168
|
+
role: 'system',
|
|
169
|
+
content: 'Generate a concise git commit message title in present tense that precisely describes the key changes in the following code diff. Focus on what was changed, not just file names. Provide only the title, no description or body. ' \
|
|
287
170
|
"Message language: English. Rules:\n" \
|
|
288
171
|
"- Commit message must be a maximum of 100 characters.\n" \
|
|
289
172
|
"- Exclude anything unnecessary such as translation. Your entire response will be passed directly into git commit.\n" \
|
|
290
173
|
"- IMPORTANT: Do not include any explanations, introductions, or additional text. Do not wrap the commit message in quotes or any other formatting. The commit message must not exceed 100 characters. Respond with ONLY the commit message text. \n" \
|
|
291
174
|
"- Be specific: include concrete details (package names, versions, functionality) rather than generic statements. \n" \
|
|
292
|
-
|
|
175
|
+
'- Return ONLY the commit message, nothing else.'
|
|
293
176
|
},
|
|
294
177
|
{
|
|
295
|
-
role:
|
|
178
|
+
role: 'user',
|
|
296
179
|
content: "Generate a commit message for the following git diff:\n\n#{diff}"
|
|
297
180
|
}
|
|
298
181
|
]
|
|
299
182
|
|
|
300
183
|
# Check config for disable_reasoning support (default true if not set)
|
|
301
184
|
provider_config = ConfigManager.get_active_provider_config
|
|
302
|
-
can_disable_reasoning = provider_config.key?(
|
|
185
|
+
can_disable_reasoning = provider_config.key?('can_disable_reasoning') ? provider_config['can_disable_reasoning'] : true
|
|
303
186
|
# Get configured max_tokens or default to 2000
|
|
304
|
-
configured_max_tokens = provider_config[
|
|
187
|
+
configured_max_tokens = provider_config['max_tokens'] || 2000
|
|
305
188
|
|
|
306
189
|
payload = {
|
|
307
190
|
model: @model,
|
|
@@ -318,33 +201,37 @@ module CommitGpt
|
|
|
318
201
|
end
|
|
319
202
|
|
|
320
203
|
# Initial UI feedback (only on first try)
|
|
321
|
-
puts
|
|
204
|
+
puts '....... Generating your AI commit message ......'.gray unless defined?(@is_retrying) && @is_retrying
|
|
322
205
|
|
|
323
|
-
full_content =
|
|
324
|
-
full_reasoning =
|
|
206
|
+
full_content = ''
|
|
207
|
+
full_reasoning = ''
|
|
325
208
|
printed_reasoning = false
|
|
326
209
|
printed_content_prefix = false
|
|
327
210
|
stop_stream = false
|
|
328
|
-
|
|
211
|
+
|
|
329
212
|
uri = URI("#{@base_url}/chat/completions")
|
|
330
213
|
http = Net::HTTP.new(uri.host, uri.port)
|
|
331
|
-
http.use_ssl = (uri.scheme ==
|
|
214
|
+
http.use_ssl = (uri.scheme == 'https')
|
|
332
215
|
http.read_timeout = 120
|
|
333
216
|
|
|
334
217
|
request = Net::HTTP::Post.new(uri)
|
|
335
|
-
request[
|
|
336
|
-
request[
|
|
218
|
+
request['Content-Type'] = 'application/json'
|
|
219
|
+
request['Authorization'] = "Bearer #{@api_key}" if @api_key
|
|
337
220
|
request.body = payload.to_json
|
|
338
221
|
|
|
339
222
|
begin
|
|
340
223
|
http.request(request) do |response|
|
|
341
|
-
if response.code !=
|
|
224
|
+
if response.code != '200'
|
|
342
225
|
# Parse error body
|
|
343
|
-
error_body = response.read_body
|
|
344
|
-
result =
|
|
345
|
-
|
|
226
|
+
error_body = response.read_body
|
|
227
|
+
result = begin
|
|
228
|
+
JSON.parse(error_body)
|
|
229
|
+
rescue StandardError
|
|
230
|
+
nil
|
|
231
|
+
end
|
|
232
|
+
|
|
346
233
|
error_msg = if result
|
|
347
|
-
result.dig(
|
|
234
|
+
result.dig('error', 'message') || result['error'] || result['message']
|
|
348
235
|
else
|
|
349
236
|
error_body
|
|
350
237
|
end
|
|
@@ -353,10 +240,10 @@ module CommitGpt
|
|
|
353
240
|
error_msg = "HTTP #{response.code}"
|
|
354
241
|
error_msg += " Raw: #{error_body}" unless error_body.to_s.strip.empty?
|
|
355
242
|
end
|
|
356
|
-
|
|
357
|
-
if can_disable_reasoning && (error_msg =~ /parameter|reasoning|unsupported/i || response.code ==
|
|
243
|
+
|
|
244
|
+
if can_disable_reasoning && (error_msg =~ /parameter|reasoning|unsupported/i || response.code == '400')
|
|
358
245
|
puts "▲ Provider does not support 'disable_reasoning'. Updating config and retrying...".yellow
|
|
359
|
-
ConfigManager.update_provider(provider_config[
|
|
246
|
+
ConfigManager.update_provider(provider_config['name'], { 'can_disable_reasoning' => false })
|
|
360
247
|
@is_retrying = true
|
|
361
248
|
return generate_commit(diff)
|
|
362
249
|
else
|
|
@@ -364,9 +251,9 @@ module CommitGpt
|
|
|
364
251
|
return nil
|
|
365
252
|
end
|
|
366
253
|
end
|
|
367
|
-
|
|
254
|
+
|
|
368
255
|
# Process Streaming Response
|
|
369
|
-
buffer =
|
|
256
|
+
buffer = ''
|
|
370
257
|
response.read_body do |chunk|
|
|
371
258
|
break if stop_stream
|
|
372
259
|
|
|
@@ -374,22 +261,20 @@ module CommitGpt
|
|
|
374
261
|
while (line_end = buffer.index("\n"))
|
|
375
262
|
line = buffer.slice!(0, line_end + 1).strip
|
|
376
263
|
next if line.empty?
|
|
377
|
-
next unless line.start_with?(
|
|
264
|
+
next unless line.start_with?('data: ')
|
|
378
265
|
|
|
379
|
-
data_str = line[6
|
|
380
|
-
next if data_str ==
|
|
266
|
+
data_str = line[6..]
|
|
267
|
+
next if data_str == '[DONE]'
|
|
381
268
|
|
|
382
269
|
begin
|
|
383
270
|
data = JSON.parse(data_str)
|
|
384
|
-
delta = data.dig(
|
|
271
|
+
delta = data.dig('choices', 0, 'delta')
|
|
385
272
|
next unless delta
|
|
386
273
|
|
|
387
274
|
# Handle Reasoning
|
|
388
|
-
reasoning_chunk = delta[
|
|
275
|
+
reasoning_chunk = delta['reasoning_content'] || delta['reasoning']
|
|
389
276
|
if reasoning_chunk && !reasoning_chunk.empty?
|
|
390
|
-
unless printed_reasoning
|
|
391
|
-
puts "\nThinking...".gray
|
|
392
|
-
end
|
|
277
|
+
puts "\nThinking...".gray unless printed_reasoning
|
|
393
278
|
print reasoning_chunk.gray
|
|
394
279
|
full_reasoning += reasoning_chunk
|
|
395
280
|
printed_reasoning = true
|
|
@@ -397,17 +282,17 @@ module CommitGpt
|
|
|
397
282
|
end
|
|
398
283
|
|
|
399
284
|
# Handle Content
|
|
400
|
-
content_chunk = delta[
|
|
285
|
+
content_chunk = delta['content']
|
|
401
286
|
if content_chunk && !content_chunk.empty?
|
|
402
287
|
if printed_reasoning && !printed_content_prefix
|
|
403
|
-
puts
|
|
288
|
+
puts '' # Newline after reasoning block
|
|
404
289
|
end
|
|
405
|
-
|
|
290
|
+
|
|
406
291
|
unless printed_content_prefix
|
|
407
292
|
print "\n▲ Commit message: git commit -am \"".green
|
|
408
293
|
printed_content_prefix = true
|
|
409
294
|
end
|
|
410
|
-
|
|
295
|
+
|
|
411
296
|
# Prevent infinite loops/repetitive garbage
|
|
412
297
|
if full_content.length + content_chunk.length > 300
|
|
413
298
|
stop_stream = true
|
|
@@ -418,12 +303,9 @@ module CommitGpt
|
|
|
418
303
|
full_content += content_chunk
|
|
419
304
|
$stdout.flush
|
|
420
305
|
end
|
|
421
|
-
|
|
422
|
-
# Handle Usage (some providers send usage at the end)
|
|
423
|
-
if data["usage"]
|
|
424
|
-
@last_usage = data["usage"]
|
|
425
|
-
end
|
|
426
306
|
|
|
307
|
+
# Handle Usage (some providers send usage at the end)
|
|
308
|
+
@last_usage = data['usage'] if data['usage']
|
|
427
309
|
rescue JSON::ParserError
|
|
428
310
|
# Partial JSON, wait for more data
|
|
429
311
|
end
|
|
@@ -434,38 +316,38 @@ module CommitGpt
|
|
|
434
316
|
puts "▲ Error: #{e.message}".red
|
|
435
317
|
return nil
|
|
436
318
|
end
|
|
437
|
-
|
|
319
|
+
|
|
438
320
|
# Close the quote
|
|
439
|
-
puts "
|
|
321
|
+
puts '"'.green if printed_content_prefix
|
|
440
322
|
|
|
441
323
|
# Post-processing Logic (Retry if empty content)
|
|
442
324
|
if (full_content.nil? || full_content.strip.empty?) && (full_reasoning && !full_reasoning.strip.empty?)
|
|
443
325
|
if can_disable_reasoning
|
|
444
326
|
puts "\n▲ Model returned reasoning despite 'disable_reasoning: true'. Updating config and retrying...".yellow
|
|
445
|
-
ConfigManager.update_provider(provider_config[
|
|
327
|
+
ConfigManager.update_provider(provider_config['name'], { 'can_disable_reasoning' => false })
|
|
446
328
|
@is_retrying = true
|
|
447
329
|
return generate_commit(diff)
|
|
448
330
|
else
|
|
449
331
|
puts "\n▲ Model output truncated (Reasoning consumed all #{configured_max_tokens} tokens).".red
|
|
450
332
|
prompt = TTY::Prompt.new
|
|
451
|
-
choice = prompt.select(
|
|
333
|
+
choice = prompt.select('Choose an action:') do |menu|
|
|
452
334
|
menu.choice "Double max_tokens to #{configured_max_tokens * 2}", :double
|
|
453
|
-
menu.choice
|
|
454
|
-
menu.choice
|
|
335
|
+
menu.choice 'Set custom max_tokens...', :custom
|
|
336
|
+
menu.choice 'Abort', :abort
|
|
455
337
|
end
|
|
456
338
|
|
|
457
339
|
new_max = case choice
|
|
458
340
|
when :double
|
|
459
341
|
configured_max_tokens * 2
|
|
460
342
|
when :custom
|
|
461
|
-
prompt.ask(
|
|
343
|
+
prompt.ask('Enter new max_tokens:', convert: :int)
|
|
462
344
|
when :abort
|
|
463
345
|
return nil
|
|
464
346
|
end
|
|
465
|
-
|
|
347
|
+
|
|
466
348
|
if new_max
|
|
467
349
|
puts "▲ Updating max_tokens to #{new_max} and retrying...".yellow
|
|
468
|
-
ConfigManager.update_provider(provider_config[
|
|
350
|
+
ConfigManager.update_provider(provider_config['name'], { 'max_tokens' => new_max })
|
|
469
351
|
@is_retrying = true
|
|
470
352
|
return generate_commit(diff)
|
|
471
353
|
end
|
|
@@ -474,10 +356,10 @@ module CommitGpt
|
|
|
474
356
|
end
|
|
475
357
|
|
|
476
358
|
if full_content.empty? && full_reasoning.empty?
|
|
477
|
-
puts
|
|
359
|
+
puts '▲ No response from AI.'.red
|
|
478
360
|
return nil
|
|
479
361
|
end
|
|
480
|
-
|
|
362
|
+
|
|
481
363
|
# Print usage info if available (saved from stream or approximated)
|
|
482
364
|
if defined?(@last_usage) && @last_usage
|
|
483
365
|
puts "\n...... Tokens: #{@last_usage['total_tokens']} (Prompt: #{@last_usage['prompt_tokens']}, Completion: #{@last_usage['completion_tokens']})\n\n".gray
|
|
@@ -489,7 +371,7 @@ module CommitGpt
|
|
|
489
371
|
|
|
490
372
|
# Take only the first non-empty line to avoid repetition or multi-line garbage
|
|
491
373
|
first_line = full_content.split("\n").map(&:strip).reject(&:empty?).first
|
|
492
|
-
first_line&.gsub(/\A["']|["']\z/,
|
|
374
|
+
first_line&.gsub(/\A["']|["']\z/, '') || ''
|
|
493
375
|
end
|
|
494
376
|
end
|
|
495
377
|
end
|