Skip to content

Commit

Permalink
feat(cz-git,czg): add modify message with prompt on AI confirm (#103)
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhengqbbb committed Mar 11, 2023
1 parent e956ca0 commit 59b55c7
Show file tree
Hide file tree
Showing 6 changed files with 131 additions and 85 deletions.
4 changes: 3 additions & 1 deletion packages/cz-git/src/generator/api.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import url from 'url'
import { style } from '@cz-git/inquirer'
import HttpsProxyAgent from 'https-proxy-agent'
// @ts-expect-error
Expand All @@ -16,7 +17,8 @@ export async function fetchOpenAIMessage(options: CommitizenGitOptions, prompt:
const httpProxy = options.apiProxy || process.env.https_proxy || process.env.all_proxy || process.env.ALL_PROXY || process.env.http_proxy
let agent: any
if (httpProxy) {
// const proxyUrl = url.parse(httpProxy)
// eslint-disable-next-line n/no-deprecated-api
const proxyUrl = url.parse(httpProxy)
// @ts-expect-error
agent = new HttpsProxyAgent(proxyUrl)
agent.path = agent?.pathname
Expand Down
2 changes: 1 addition & 1 deletion packages/cz-git/src/generator/message.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,6 @@ function generateSubjectDefaultPrompt(
if (!maxSubjectLength || maxSubjectLength === Infinity || maxSubjectLength > 90)
maxSubjectLength = 65

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\`\`\``
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\`\`\``
}
/** EndSection: */
18 changes: 12 additions & 6 deletions packages/cz-git/src/generator/question.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
isSingleItem,
log,
parseStandardScopes,
resolveDefaultType,
resolveListItemPinTop,
resovleCustomListTemplate,
useThemeCode,
Expand Down Expand Up @@ -50,8 +51,9 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => {
separator: options.scopeEnumSeparator,
source: (answer: Answers, input: string) => {
let scopeSource: Option[] = []
const _answerType = resolveDefaultType(options, answer)
scopeSource = parseStandardScopes(
getCurrentScopes(options.scopes, options.scopeOverrides, answer.type),
getCurrentScopes(options.scopes, options.scopeOverrides, _answerType),
)
scopeSource = resovleCustomListTemplate(
scopeSource,
Expand Down Expand Up @@ -79,11 +81,12 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => {
return input.length !== 0 ? true : style.red('[ERROR] scope is required')
},
when: (answer: Answers) => {
const _answerType = resolveDefaultType(options, answer)
return !isSingleItem(
options.allowCustomScopes,
options.allowEmptyScopes,
parseStandardScopes(
getCurrentScopes(options.scopes, options.scopeOverrides, answer.type),
getCurrentScopes(options.scopes, options.scopeOverrides, _answerType),
),
)
},
Expand Down Expand Up @@ -119,7 +122,8 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => {
log('err', 'Error [Subject Length] Option')
return false
}
const maxSubjectLength = getMaxSubjectLength(answers.type, answers.scope, options)
const _answerType = resolveDefaultType(options, answers)
const maxSubjectLength = getMaxSubjectLength(_answerType, answers.scope, options)
if (options.minSubjectLength && processedSubject.length < options.minSubjectLength) {
return style.red(
`[ERROR]subject length must be greater than or equal to ${options.minSubjectLength} characters`,
Expand All @@ -137,7 +141,8 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => {
transformer: (subject: string, answers: Answers) => {
const { minSubjectLength, isIgnoreCheckMaxSubjectLength } = options
const subjectLength = subject.length
const maxSubjectLength = getMaxSubjectLength(answers.type, answers.scope, options)
const _answerType = resolveDefaultType(options, answers)
const maxSubjectLength = getMaxSubjectLength(_answerType, answers.scope, options)
let tooltip
let isWarning = false
if (typeof minSubjectLength === 'number' && subjectLength < minSubjectLength) {
Expand Down Expand Up @@ -203,10 +208,11 @@ export const generateQuestions = (options: CommitizenGitOptions, cz: any) => {
message: options.messages?.breaking,
completeValue: options.defaultBody || undefined,
when: (answers: Answers) => {
const _answerType = resolveDefaultType(options, answers)
if (
options.allowBreakingChanges
&& answers.type
&& options.allowBreakingChanges.includes(answers.type)
&& _answerType
&& options.allowBreakingChanges.includes(_answerType)
)
return true

Expand Down
53 changes: 25 additions & 28 deletions packages/cz-git/src/generator/questionAI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,17 +4,36 @@
* @license: MIT
*/

import { fuzzyFilter } from '@cz-git/inquirer'
import type { Answers, CommitizenGitOptions } from '../shared'
import { fuzzyFilter, style } from '@cz-git/inquirer'
import type { CommitizenGitOptions, CommitizenType } from '../shared'
import {
log,
parseStandardScopes,
previewMessage,
resolveListItemPinTop,
} from '../shared'
import { generateMessage } from './message'
import { generateAISubjects } from './message'

export const generateAITypesQuestions = (options: CommitizenGitOptions) => {
export async function generateAIPrompt(options: CommitizenGitOptions, cz: CommitizenType) {
const answers = await cz.prompt(generateAITypesQuestions(options))
console.log(style.green('ℹ'), style.bold(options.messages!.generatingByAI))
const subjects = await generateAISubjects(answers, options)
if (!Array.isArray(subjects))
throw new Error('subjects fetch value failed')

if (subjects.length === 1) {
answers.subject = subjects[0]
}
else {
const { subject } = await cz.prompt(generateAISubjectsQuestions(options, subjects))
answers.subject = subject
}

if (options.defaultScope)
answers.scope = options.defaultScope
return answers
}

function generateAITypesQuestions(options: CommitizenGitOptions) {
if (!Array.isArray(options.types) || options.types.length === 0) {
if (!process.env.VITEST)
log('err', 'Error [types] Option')
Expand All @@ -40,7 +59,7 @@ export const generateAITypesQuestions = (options: CommitizenGitOptions) => {
]
}

export const generateAISubjectsQuestions = (options: CommitizenGitOptions, subjects: string[]) => {
function generateAISubjectsQuestions(options: CommitizenGitOptions, subjects: string[]) {
return [
{
type: 'search-list',
Expand All @@ -53,25 +72,3 @@ export const generateAISubjectsQuestions = (options: CommitizenGitOptions, subje
},
]
}

export const generateAIConfirmQuestions = (options: CommitizenGitOptions, answers: Answers) => {
return [
{
type: 'expand',
name: 'confirmCommit',
choices: [
{ key: 'y', name: 'Yes', value: 'yes' },
{ key: 'n', name: 'Abort commit', value: 'no' },
{ key: 'e', name: 'Edit message(wq: save, cq: exit)', value: 'edit' },
],
default: 0,
message() {
previewMessage(
generateMessage(answers, options, options.confirmColorize),
options.confirmColorize,
)
return options.messages?.confirmCommit
},
},
]
}
129 changes: 80 additions & 49 deletions packages/cz-git/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@
* @copyright: Copyright (c) 2022-present Qiubin Zheng
*/

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

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

let answers
if (options.useAI) {
answers = await cz.prompt(generateAITypesQuestions(options))
console.log(style.green('ℹ'), style.bold(options.messages!.generatingByAI))
// Power By and Modified part of the code: https://github.com/Nutlope/aicommits
const subjects = await generateAISubjects(answers, options)
if (!Array.isArray(subjects))
throw new Error('subjects fetch value failed')

if (subjects.length === 1) {
answers.subject = subjects[0]
}
else {
const { subject } = await cz.prompt(generateAISubjectsQuestions(options, subjects))
answers.subject = subject
if (options.useAI)
answers = await generateAIPrompt(options, cz)
else
answers = await cz.prompt(generateQuestions(options, cz))

answers = await generateConfirmPrompt(options, cz, answers)

await confirmMessage(options, cz, answers, commit)
})
}

async function generateConfirmPrompt(options: CommitizenGitOptions, cz: CommitizenType, answers: Answers) {
const result = answers
if (options.skipQuestions?.includes('confirmCommit')) {
previewMessage(
generateMessage(answers, options, options.confirmColorize),
options.confirmColorize,
)
result.confirmCommit = 'yes'
}
else {
const question: any = [
{
type: 'expand',
name: 'confirmCommit',
choices: [
{ key: 'y', name: 'Yes', value: 'yes' },
{ key: 'n', name: 'Abort commit', value: 'no' },
{ key: 'e', name: 'Edit message(wq: save, cq: exit)', value: 'edit' },
],
default: 0,
message() {
previewMessage(
generateMessage(answers, options, options.confirmColorize),
options.confirmColorize,
)
return options.messages?.confirmCommit
},
},
]
if (options.useAI)
question[0].choices.push({ key: 'm', name: 'Modify and additional message with prompt', value: 'ai-modify' })

const { confirmCommit } = await cz.prompt(question)
result.confirmCommit = confirmCommit
}

return result
}

async function confirmMessage(options: CommitizenGitOptions, cz: CommitizenType, answers: Answers, commit: (message: string) => void) {
switch (answers.confirmCommit) {
case 'edit':
editCommit(answers, options, commit)
break

case 'ai-modify': {
options.defaultType = answers.type
options.defaultSubject = answers.subject
let question = generateQuestions(options, cz)
if (question) {
question.shift()
const scopeIdx = 2
question = [question[scopeIdx], ...question.slice(0, scopeIdx), ...question.slice(scopeIdx + 1)]
}
answers = await cz.prompt(question)

if (options.defaultScope)
answers.scope = options.defaultScope
}
else {
const questions = generateQuestions(options, cz)
answers = await cz.prompt(questions)
options.useAI = false
answers.type = options.defaultType
answers = await generateConfirmPrompt(options, cz, answers)
confirmMessage(options, cz, answers, commit)
break
}

if (options.skipQuestions?.includes('confirmCommit')) {
case 'yes':
commit(generateMessage(answers, options))
previewMessage(
generateMessage(answers, options, options.confirmColorize),
options.confirmColorize,
)
return 0
}
else {
// @ts-expect-error
const { confirmCommit } = await cz.prompt(generateAIConfirmQuestions(options, answers))
answers.confirmCommit = confirmCommit
}
switch (answers.confirmCommit) {
case 'edit':
editCommit(answers, options, commit)
break

case 'yes':
commit(generateMessage(answers, options))
break

default:
log('info', 'Commit has been canceled.')
break
}
})
break

default:
log('info', 'Commit has been canceled.')
break
}
}
10 changes: 10 additions & 0 deletions packages/cz-git/src/shared/utils/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,16 @@ export const resolveListItemPinTop = (
export const isSingleItem = (allowCustom = true, allowEmpty = true, list: Array<any> = []) =>
!allowCustom && !allowEmpty && Array.isArray(list) && list.length === 1

/**
* @description: resolve AI modify mode and normal answer type and default type
*/
export const resolveDefaultType = (options: CommitizenGitOptions, answer: Answers) => {
if (!answer.type && options.useAI)
return options.defaultType

return answer.type || options.defaultType
}

/**
* @description: parse scope configuration option to standard options
*/
Expand Down

0 comments on commit 59b55c7

Please sign in to comment.