/
index.ts
125 lines (121 loc) · 3.76 KB
/
index.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import Ajv from 'ajv';
import { StatusBarAlignment, window } from 'vscode';
import { Success, Error, error, success } from './result';
export async function translate<T extends object>(option: {
schema: string;
request: string;
createChatCompletion: (options: {
messages: {
role: 'system' | 'user' | 'assistant';
content: string;
}[];
handleChunk?: ((data: { text?: string }) => void) | undefined;
showWebview?: boolean;
}) => Promise<string>;
showWebview?: boolean;
/**
* @description 完整的 prompt,若提供则内部不再组合 prompt
* @type {string}
*/
completePrompt?: string;
extendValidate?: (jsonObject: T) => Error | Success<T>;
}) {
let requestPrompt =
option.completePrompt ||
`你是一个根据以下 JSON Schema 定义将用户请求转换为相应 JSON 数据的服务,并且按照 JSON Schema 中 description 的描述对字段进行处理:\n` +
`\`\`\`\n${option.schema}\`\`\`\n` +
`The following is a user request:\n` +
`"""\n${option.request}\n"""\n` +
`The following is the user request translated into a JSON data with 2 spaces of indentation and no properties with the value undefined:\n`;
let tryCount = 0;
// eslint-disable-next-line no-unreachable-loop, no-constant-condition
while (true) {
const statusBarItem = window.createStatusBarItem(StatusBarAlignment.Left);
statusBarItem.text = '$(sync~spin) Ask ChatGPT...';
statusBarItem.show();
// eslint-disable-next-line no-await-in-loop
const responseText = await option
.createChatCompletion({
messages: [{ role: 'user', content: requestPrompt }],
handleChunk: undefined,
showWebview: option.showWebview,
})
.finally(() => {
statusBarItem.hide();
statusBarItem.dispose();
});
let validation = validate<T>(
responseText.replace(/```/g, ''),
option.schema,
);
if (validation.success) {
// 走额外的校验
if (option.extendValidate) {
validation = option.extendValidate(validation.data);
if (validation.success) {
return validation;
}
} else {
return validation;
}
}
if (tryCount > 3) {
return validation;
}
requestPrompt += `${responseText}\n${createRepairPrompt(
validation.message,
)}`;
tryCount++;
}
}
function createRepairPrompt(validationError: string) {
return (
`The JSON object is invalid for the following reason:\n` +
`"""\n${validationError}\n"""\n` +
`The following is a revised JSON object:\n`
);
}
function validate<T extends object>(jsonText: string, schema: string) {
let jsonObject;
try {
jsonObject = JSON.parse(jsonText) as object;
} catch (e) {
return error(e instanceof SyntaxError ? e.message : 'JSON parse error');
}
stripNulls(jsonObject);
const ajv = new Ajv();
const jsonValidate = ajv.compile(JSON.parse(schema));
const valid = jsonValidate(jsonObject);
if (!valid) {
console.log(jsonValidate.errors);
return error(
jsonValidate.errors?.map((s) => s.message || s.keyword).join(',') || '',
);
}
return success<T>(jsonObject);
}
function stripNulls(obj: any) {
let keysToDelete: string[] | undefined;
// eslint-disable-next-line no-restricted-syntax, guard-for-in
for (const k in obj) {
const value = obj[k];
if (value === null) {
(keysToDelete ??= []).push(k);
} else {
if (Array.isArray(value)) {
if (value.some((x) => x === null)) {
obj[k] = value.filter((x) => x !== null);
}
}
if (typeof value === 'object') {
stripNulls(value);
}
}
}
if (keysToDelete) {
// eslint-disable-next-line no-restricted-syntax
for (const k of keysToDelete) {
delete obj[k];
}
}
}