Skip to content

Commit 59b55c7

Browse files
authored
feat(cz-git,czg): add modify message with prompt on AI confirm (#103)
1 parent e956ca0 commit 59b55c7

6 files changed

Lines changed: 131 additions & 85 deletions

File tree

‎packages/cz-git/src/generator/api.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import url from 'url'
12
import { style } from '@cz-git/inquirer'
23
import HttpsProxyAgent from 'https-proxy-agent'
34
// @ts-expect-error
@@ -16,7 +17,8 @@ export async function fetchOpenAIMessage(options: CommitizenGitOptions, prompt:
1617
const httpProxy = options.apiProxy || process.env.https_proxy || process.env.all_proxy || process.env.ALL_PROXY || process.env.http_proxy
1718
let agent: any
1819
if (httpProxy) {
19-
// const proxyUrl = url.parse(httpProxy)
20+
// eslint-disable-next-line n/no-deprecated-api
21+
const proxyUrl = url.parse(httpProxy)
2022
// @ts-expect-error
2123
agent = new HttpsProxyAgent(proxyUrl)
2224
agent.path = agent?.pathname

‎packages/cz-git/src/generator/message.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -212,6 +212,6 @@ function generateSubjectDefaultPrompt(
212212
if (!maxSubjectLength || maxSubjectLength === Infinity || maxSubjectLength > 90)
213213
maxSubjectLength = 65
214214

215-
return `Write an insightful and concise Git commit message in the present tense for the following Git diff code, without any prefixes. Note that this sentence should never exceed ${maxSubjectLength} characters in length.: \n\`\`\`diff\n${diff}\n\`\`\``
215+
return `Write an insightful and concise Git commit message in the present tense for the following Git diff code, without any prefixes. Note that this sentence must never exceed ${maxSubjectLength} characters in length!! : \n\`\`\`diff\n${diff}\n\`\`\``
216216
}
217217
/** EndSection: */

‎packages/cz-git/src/generator/question.ts‎

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
isSingleItem,
1414
log,
1515
parseStandardScopes,
16+
resolveDefaultType,
1617
resolveListItemPinTop,
1718
resovleCustomListTemplate,
1819
useThemeCode,
@@ -50,8 +51,9 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => {
5051
separator: options.scopeEnumSeparator,
5152
source: (answer: Answers, input: string) => {
5253
let scopeSource: Option[] = []
54+
const _answerType = resolveDefaultType(options, answer)
5355
scopeSource = parseStandardScopes(
54-
getCurrentScopes(options.scopes, options.scopeOverrides, answer.type),
56+
getCurrentScopes(options.scopes, options.scopeOverrides, _answerType),
5557
)
5658
scopeSource = resovleCustomListTemplate(
5759
scopeSource,
@@ -79,11 +81,12 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => {
7981
return input.length !== 0 ? true : style.red('[ERROR] scope is required')
8082
},
8183
when: (answer: Answers) => {
84+
const _answerType = resolveDefaultType(options, answer)
8285
return !isSingleItem(
8386
options.allowCustomScopes,
8487
options.allowEmptyScopes,
8588
parseStandardScopes(
86-
getCurrentScopes(options.scopes, options.scopeOverrides, answer.type),
89+
getCurrentScopes(options.scopes, options.scopeOverrides, _answerType),
8790
),
8891
)
8992
},
@@ -119,7 +122,8 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => {
119122
log('err', 'Error [Subject Length] Option')
120123
return false
121124
}
122-
const maxSubjectLength = getMaxSubjectLength(answers.type, answers.scope, options)
125+
const _answerType = resolveDefaultType(options, answers)
126+
const maxSubjectLength = getMaxSubjectLength(_answerType, answers.scope, options)
123127
if (options.minSubjectLength && processedSubject.length < options.minSubjectLength) {
124128
return style.red(
125129
`[ERROR]subject length must be greater than or equal to ${options.minSubjectLength} characters`,
@@ -137,7 +141,8 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => {
137141
transformer: (subject: string, answers: Answers) => {
138142
const { minSubjectLength, isIgnoreCheckMaxSubjectLength } = options
139143
const subjectLength = subject.length
140-
const maxSubjectLength = getMaxSubjectLength(answers.type, answers.scope, options)
144+
const _answerType = resolveDefaultType(options, answers)
145+
const maxSubjectLength = getMaxSubjectLength(_answerType, answers.scope, options)
141146
let tooltip
142147
let isWarning = false
143148
if (typeof minSubjectLength === 'number' && subjectLength < minSubjectLength) {
@@ -203,10 +208,11 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => {
203208
message: options.messages?.breaking,
204209
completeValue: options.defaultBody || undefined,
205210
when: (answers: Answers) => {
211+
const _answerType = resolveDefaultType(options, answers)
206212
if (
207213
options.allowBreakingChanges
208-
&& answers.type
209-
&& options.allowBreakingChanges.includes(answers.type)
214+
&& _answerType
215+
&& options.allowBreakingChanges.includes(_answerType)
210216
)
211217
return true
212218

‎packages/cz-git/src/generator/questionAI.ts‎

Lines changed: 25 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,36 @@
44
* @license: MIT
55
*/
66

7-
import { fuzzyFilter } from '@cz-git/inquirer'
8-
import type { Answers, CommitizenGitOptions } from '../shared'
7+
import { fuzzyFilter, style } from '@cz-git/inquirer'
8+
import type { CommitizenGitOptions, CommitizenType } from '../shared'
99
import {
1010
log,
1111
parseStandardScopes,
12-
previewMessage,
1312
resolveListItemPinTop,
1413
} from '../shared'
15-
import { generateMessage } from './message'
14+
import { generateAISubjects } from './message'
1615

17-
export const generateAITypesQuestions = (options: CommitizenGitOptions) => {
16+
export async function generateAIPrompt(options: CommitizenGitOptions, cz: CommitizenType) {
17+
const answers = await cz.prompt(generateAITypesQuestions(options))
18+
console.log(style.green('ℹ'), style.bold(options.messages!.generatingByAI))
19+
const subjects = await generateAISubjects(answers, options)
20+
if (!Array.isArray(subjects))
21+
throw new Error('subjects fetch value failed')
22+
23+
if (subjects.length === 1) {
24+
answers.subject = subjects[0]
25+
}
26+
else {
27+
const { subject } = await cz.prompt(generateAISubjectsQuestions(options, subjects))
28+
answers.subject = subject
29+
}
30+
31+
if (options.defaultScope)
32+
answers.scope = options.defaultScope
33+
return answers
34+
}
35+
36+
function generateAITypesQuestions(options: CommitizenGitOptions) {
1837
if (!Array.isArray(options.types) || options.types.length === 0) {
1938
if (!process.env.VITEST)
2039
log('err', 'Error [types] Option')
@@ -40,7 +59,7 @@ export const generateAITypesQuestions = (options: CommitizenGitOptions) => {
4059
]
4160
}
4261

43-
export const generateAISubjectsQuestions = (options: CommitizenGitOptions, subjects: string[]) => {
62+
function generateAISubjectsQuestions(options: CommitizenGitOptions, subjects: string[]) {
4463
return [
4564
{
4665
type: 'search-list',
@@ -53,25 +72,3 @@ export const generateAISubjectsQuestions = (options: CommitizenGitOptions, subje
5372
},
5473
]
5574
}
56-
57-
export const generateAIConfirmQuestions = (options: CommitizenGitOptions, answers: Answers) => {
58-
return [
59-
{
60-
type: 'expand',
61-
name: 'confirmCommit',
62-
choices: [
63-
{ key: 'y', name: 'Yes', value: 'yes' },
64-
{ key: 'n', name: 'Abort commit', value: 'no' },
65-
{ key: 'e', name: 'Edit message(wq: save, cq: exit)', value: 'edit' },
66-
],
67-
default: 0,
68-
message() {
69-
previewMessage(
70-
generateMessage(answers, options, options.confirmColorize),
71-
options.confirmColorize,
72-
)
73-
return options.messages?.confirmCommit
74-
},
75-
},
76-
]
77-
}

‎packages/cz-git/src/index.ts‎

Lines changed: 80 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,11 @@
55
* @copyright: Copyright (c) 2022-present Qiubin Zheng
66
*/
77

8-
import { CompleteInput, SearchCheckbox, SearchList, style } from '@cz-git/inquirer'
8+
import { CompleteInput, SearchCheckbox, SearchList } from '@cz-git/inquirer'
99
import { configLoader } from '@cz-git/loader'
1010
import { editCommit, log, previewMessage } from './shared'
11-
import { generateAIConfirmQuestions, generateAISubjects, generateAISubjectsQuestions, generateAITypesQuestions, generateMessage, generateOptions, generateQuestions, getAliasMessage } from './generator'
12-
import type { CommitizenType } from './shared'
11+
import { generateAIPrompt, generateMessage, generateOptions, generateQuestions, getAliasMessage } from './generator'
12+
import type { Answers, CommitizenGitOptions, CommitizenType } from './shared'
1313

1414
export * from './shared/types'
1515
export * from '@cz-git/inquirer'
@@ -35,55 +35,86 @@ export const prompter = (
3535
cz.registerPrompt('complete-input', CompleteInput)
3636

3737
let answers
38-
if (options.useAI) {
39-
answers = await cz.prompt(generateAITypesQuestions(options))
40-
console.log(style.green('ℹ'), style.bold(options.messages!.generatingByAI))
41-
// Power By and Modified part of the code: https://github.com/Nutlope/aicommits
42-
const subjects = await generateAISubjects(answers, options)
43-
if (!Array.isArray(subjects))
44-
throw new Error('subjects fetch value failed')
45-
46-
if (subjects.length === 1) {
47-
answers.subject = subjects[0]
48-
}
49-
else {
50-
const { subject } = await cz.prompt(generateAISubjectsQuestions(options, subjects))
51-
answers.subject = subject
38+
if (options.useAI)
39+
answers = await generateAIPrompt(options, cz)
40+
else
41+
answers = await cz.prompt(generateQuestions(options, cz))
42+
43+
answers = await generateConfirmPrompt(options, cz, answers)
44+
45+
await confirmMessage(options, cz, answers, commit)
46+
})
47+
}
48+
49+
async function generateConfirmPrompt(options: CommitizenGitOptions, cz: CommitizenType, answers: Answers) {
50+
const result = answers
51+
if (options.skipQuestions?.includes('confirmCommit')) {
52+
previewMessage(
53+
generateMessage(answers, options, options.confirmColorize),
54+
options.confirmColorize,
55+
)
56+
result.confirmCommit = 'yes'
57+
}
58+
else {
59+
const question: any = [
60+
{
61+
type: 'expand',
62+
name: 'confirmCommit',
63+
choices: [
64+
{ key: 'y', name: 'Yes', value: 'yes' },
65+
{ key: 'n', name: 'Abort commit', value: 'no' },
66+
{ key: 'e', name: 'Edit message(wq: save, cq: exit)', value: 'edit' },
67+
],
68+
default: 0,
69+
message() {
70+
previewMessage(
71+
generateMessage(answers, options, options.confirmColorize),
72+
options.confirmColorize,
73+
)
74+
return options.messages?.confirmCommit
75+
},
76+
},
77+
]
78+
if (options.useAI)
79+
question[0].choices.push({ key: 'm', name: 'Modify and additional message with prompt', value: 'ai-modify' })
80+
81+
const { confirmCommit } = await cz.prompt(question)
82+
result.confirmCommit = confirmCommit
83+
}
84+
85+
return result
86+
}
87+
88+
async function confirmMessage(options: CommitizenGitOptions, cz: CommitizenType, answers: Answers, commit: (message: string) => void) {
89+
switch (answers.confirmCommit) {
90+
case 'edit':
91+
editCommit(answers, options, commit)
92+
break
93+
94+
case 'ai-modify': {
95+
options.defaultType = answers.type
96+
options.defaultSubject = answers.subject
97+
let question = generateQuestions(options, cz)
98+
if (question) {
99+
question.shift()
100+
const scopeIdx = 2
101+
question = [question[scopeIdx], ...question.slice(0, scopeIdx), ...question.slice(scopeIdx + 1)]
52102
}
103+
answers = await cz.prompt(question)
53104

54-
if (options.defaultScope)
55-
answers.scope = options.defaultScope
56-
}
57-
else {
58-
const questions = generateQuestions(options, cz)
59-
answers = await cz.prompt(questions)
105+
options.useAI = false
106+
answers.type = options.defaultType
107+
answers = await generateConfirmPrompt(options, cz, answers)
108+
confirmMessage(options, cz, answers, commit)
109+
break
60110
}
61111

62-
if (options.skipQuestions?.includes('confirmCommit')) {
112+
case 'yes':
63113
commit(generateMessage(answers, options))
64-
previewMessage(
65-
generateMessage(answers, options, options.confirmColorize),
66-
options.confirmColorize,
67-
)
68-
return 0
69-
}
70-
else {
71-
// @ts-expect-error
72-
const { confirmCommit } = await cz.prompt(generateAIConfirmQuestions(options, answers))
73-
answers.confirmCommit = confirmCommit
74-
}
75-
switch (answers.confirmCommit) {
76-
case 'edit':
77-
editCommit(answers, options, commit)
78-
break
79-
80-
case 'yes':
81-
commit(generateMessage(answers, options))
82-
break
83-
84-
default:
85-
log('info', 'Commit has been canceled.')
86-
break
87-
}
88-
})
114+
break
115+
116+
default:
117+
log('info', 'Commit has been canceled.')
118+
break
119+
}
89120
}

‎packages/cz-git/src/shared/utils/util.ts‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,16 @@ export const resolveListItemPinTop = (
5252
export const isSingleItem = (allowCustom = true, allowEmpty = true, list: Array<any> = []) =>
5353
!allowCustom && !allowEmpty && Array.isArray(list) && list.length === 1
5454

55+
/**
56+
* @description: resolve AI modify mode and normal answer type and default type
57+
*/
58+
export const resolveDefaultType = (options: CommitizenGitOptions, answer: Answers) => {
59+
if (!answer.type && options.useAI)
60+
return options.defaultType
61+
62+
return answer.type || options.defaultType
63+
}
64+
5565
/**
5666
* @description: parse scope configuration option to standard options
5767
*/

0 commit comments

Comments
 (0)