流程图:

痛点:
- ChatGPT单次对话有长度限制
- 生成内容的不可控性,可能会生成不准确、误导性或者不合适的内容,需要进行后续处理
- 多轮对话时,要确保有效的维护和处理对话的上下文
- 接口吞吐过程中的错误处理和反馈
整体代码:
const fs = require('fs').promises
const path = require('path')
const base = process.cwd()
const axios = require('axios')
const categoryToFilter = ['node_modules', '.git', '.husky', '.vscode']
let FILE_NAME = 'packages'
let globalSystemPrompt = null
async function init(state) {
await import('inquirer').then(async (inquirerModule) => {
const inquirer = inquirerModule.default
const globalSystemRolePrompt = [
{
name: '你是一名前端开发工程师',
value: '你是一名前端开发工程师'
},
{
name: '你是一名后端开发工程师',
value: '你是一名后端开发工程师'
},
{
name: '自定义角色',
value: ''
}
]
const arr = [
{
type: 'list',
name: 'globalSystemRolePrompt',
message: '⚡️⚡️⚡️预制您的角色(多选)⚡️⚡️⚡️',
choices: globalSystemRolePrompt,
default: ''
},
{
type: 'input',
name: 'globalSystemPromptContent',
message: '请输入您对于本次的预制Prompt:',
default: '你的工作主要是,基于antd组件进行了二次开发组件的单元测试代码的输出。'
}
]
await inquirer.prompt(state ? arr : [arr[arr.length - 1]]).then(async (answer) => {
if (!answer.globalSystemRolePrompt && !answer.globalSystemPromptContent) {
return
}
globalSystemPrompt = ` ${globalSystemPrompt || ''} ${answer.globalSystemRolePrompt || ''} ${
answer.globalSystemPromptContent || ''
}`
await use_ai('你是什么角色?你都能做什么事情?回答是否可以开始工作', false, null)
})
})
}
const service = axios.create({
timeout: 500000
})
service.defaults.retry = 0
async function use_ai(question, filePath, allItem) {
const url = API 地址
const api_key = 'API 密钥'
const headers = {
'Content-Type': 'application/json',
'api-key': api_key
}
const data = {
temperature: 0,
messages: [
{ role: 'system', content: globalSystemPrompt },
{ role: 'user', content: question }
]
}
console.log('\x1b[31muser:==>', `\x1b[0m${question}`)
let spinner = null
import('ora').then((ora) => {
spinner = ora.default('Loading...').start()
})
await axios
.post(url, data, { headers })
.then(async (response) => {
const mes = allItem?.name ? `${allItem?.name} 组件` : ''
spinner.succeed(`已完成 ${mes} `)
const response_json = response.data
const regex = /```javascript([\s\S]*?)```/
const content = response_json.choices[0]?.message?.content.replace(regex, (match, code) => {
return code.trim()
})
console.log('\x1b[32mGPT:==>', `\x1b[0m${response_json.choices[0]?.message?.content}`)
if (filePath) {
await fs.writeFile(filePath, content, 'utf-8')
} else {
import('inquirer').then(async (inquirerModule) => {
const inquirer = inquirerModule.default
await inquirer
.prompt([
{
type: 'list',
name: 'isInquire',
message: '⚡️⚡️⚡️是否要继续补充Prompt⚡️⚡️⚡️',
choices: [
{
name: '是',
value: 1
},
{
name: '否',
value: 0
}
],
default: ''
}
])
.then(async (answer) => {
console.log('\x1b[31m预制Prompt:==>', `\x1b[0m${globalSystemPrompt}`)
console.log('\x1b[32mGPT:==>', `\x1b[0m${content}`)
if (answer.isInquire) {
await init(0)
} else {
await inquirerFun()
}
})
})
}
})
.catch((error) => {
spinner.stop()
console.error('Error:', error)
})
}
async function fetchDataAndWriteToFile(vueFiles, choicesQuestion) {
for (const item of vueFiles) {
try {
const folderPath = `${base}/tests/${item.name}`
const filePath = path.join(folderPath, `${item.name}.test.js`)
await fs.mkdir(folderPath, { recursive: true })
let pathMes = `单测组件的引入路径为${item.componentPath}`
await use_ai(`${item.content} ${choicesQuestion} ${pathMes}`, filePath, item)
} catch (error) {
console.error('Error:', error)
}
}
}
async function readVueFilesRecursively(directory) {
try {
const items = await fs.readdir(directory, { withFileTypes: true })
const vueFiles = []
for (const item of items) {
const itemPath = path.join(directory, item.name)
if (item.isDirectory()) {
const nestedVueFiles = await readVueFilesRecursively(itemPath)
vueFiles.push(...nestedVueFiles)
} else if (item.isFile() && path.extname(item.name) === '.vue') {
const segments = itemPath.split(path.sep)
const index = segments.indexOf('src') + 1
const vueFilesName = segments[index].split('.')[0]
const fileContent = await fs.readFile(itemPath, 'utf-8')
const withoutStyle = fileContent.replace(/<style[^>]*>[\s\S]*?<\/style>/g, '')
vueFiles.push({ name: vueFilesName, componentPath: itemPath.split(':')[1] || '', content: withoutStyle })
}
}
return vueFiles
} catch (error) {
console.error(`Error reading directory ${directory}: ${error}`)
return []
}
}
async function inquirerFun() {
import('inquirer').then(async (inquirerModule) => {
const inquirer = inquirerModule.default
let filesChoices = null
let choicesQuestion = null
const questionInformation = [
{
name: '断言是否成功挂载组件',
value: '断言是否成功挂载组件'
},
{
name: '断言是否正确传递了属性',
value: '断言是否正确传递了属性'
},
{
name: '断言插槽内容是否被正确渲染',
value: '断言插槽内容是否被正确渲染'
},
{
name: '断言组件 DOM 是否包含指定的类名',
value: '断言组件 DOM 是否包含指定的类名'
},
{
name: '断言点击事件是否被触发',
value: '断言点击事件是否被触发'
},
{
name: '只输出单测代码,禁止输出文字',
value: '只输出单测代码,禁止输出文字'
}
]
try {
const items = await fs.readdir(base, { withFileTypes: true })
filesChoices = items
.filter((item) => item.isDirectory() && !categoryToFilter.includes(item.name))
.map((item) => {
return {
name: item.name,
value: item.name
}
})
} catch (error) {
console.error(`Error reading directory ${base}: ${error}`)
}
await inquirer
.prompt([
{
type: 'list',
name: 'ExecutionTest',
message: '⚡️⚡️⚡️选择自动化生成单测用例方案⚡️⚡️⚡️',
choices: [
{ name: '全量自动化用例', value: 1 },
{ name: '单文件自动化用例', value: 2 },
{ name: '放弃生成用例', value: 0 }
],
default: true
},
{
type: 'list',
name: 'ExecutionTestFile',
message: '⚡️⚡️⚡️请选择要访问的文件目录⚡️⚡️⚡️',
choices: filesChoices,
default: '',
when: (answers) => {
return answers.ExecutionTest
}
},
{
type: 'checkbox',
name: 'ExecutionTestQuestion',
message: '⚡️⚡️⚡️预制的提问信息(多选)⚡️⚡️⚡️',
choices: questionInformation,
default: '',
when: (answers) => {
return answers.ExecutionTest && answers.ExecutionTestFile
}
},
{
type: 'input',
name: 'customQuestion',
message: '请输入您自定义的提问信息:',
when: (answers) => {
return answers.ExecutionTest
}
}
])
.then(async (answer) => {
if (answer.ExecutionTest == 0) {
return
}
FILE_NAME = answer.ExecutionTestFile
choicesQuestion = `${answer.ExecutionTestQuestion.join(',')}${
answer.ExecutionTestQuestion.length && answer.customQuestion ? ',' : '。'
} ${answer.customQuestion}`
if (!choicesQuestion) {
console.error('***请选择或者输入Prompt***')
return
}
if (answer.ExecutionTest == 1) {
readVueFilesRecursively(`${base}/${FILE_NAME}`)
.then(async (vueFiles) => {
if (!vueFiles.length) {
return
}
await fetchDataAndWriteToFile(vueFiles, choicesQuestion)
})
.catch((error) => {
console.error('Error:', error)
})
}
if (answer.ExecutionTest == 2) {
readVueFilesRecursively(`${base}/${FILE_NAME}`)
.then(async (vueFiles) => {
if (!vueFiles.length) {
return
}
await inquirer
.prompt([
{
type: 'checkbox',
name: 'aloneFileName',
message: '⚡️⚡️⚡️请选择单测组件⚡️⚡️⚡️',
choices: vueFiles.map((item) => {
return {
name: item.name,
value: item.name
}
}),
default: ''
}
])
.then(async (item) => {
if (!item.aloneFileName) {
return
}
const aloneFileArr = vueFiles.filter((file) => file.name == item.aloneFileName)
await fetchDataAndWriteToFile(aloneFileArr, choicesQuestion)
})
})
.catch((error) => {
console.error('Error:', error)
})
}
})
})
}
init(1)