@far-world-labs/verblets 0.1.1
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.
- package/.eslintrc.json +42 -0
- package/.husky/pre-commit +4 -0
- package/.release-it.json +9 -0
- package/.vite.config.examples.js +8 -0
- package/.vite.config.js +8 -0
- package/docker-compose.yml +7 -0
- package/docs/README.md +41 -0
- package/docs/babel.config.js +3 -0
- package/docs/blog/2019-05-28-first-blog-post.md +12 -0
- package/docs/blog/2019-05-29-long-blog-post.md +44 -0
- package/docs/blog/2021-08-01-mdx-blog-post.mdx +20 -0
- package/docs/blog/2021-08-26-welcome/docusaurus-plushie-banner.jpeg +0 -0
- package/docs/blog/2021-08-26-welcome/index.md +25 -0
- package/docs/blog/authors.yml +17 -0
- package/docs/docs/api/bool.md +74 -0
- package/docs/docs/api/search.md +51 -0
- package/docs/docs/intro.md +47 -0
- package/docs/docs/tutorial-basics/_category_.json +8 -0
- package/docs/docs/tutorial-basics/congratulations.md +23 -0
- package/docs/docs/tutorial-basics/create-a-blog-post.md +34 -0
- package/docs/docs/tutorial-basics/create-a-document.md +57 -0
- package/docs/docs/tutorial-basics/create-a-page.md +43 -0
- package/docs/docs/tutorial-basics/deploy-your-site.md +31 -0
- package/docs/docs/tutorial-basics/markdown-features.mdx +152 -0
- package/docs/docs/tutorial-extras/_category_.json +7 -0
- package/docs/docs/tutorial-extras/img/docsVersionDropdown.png +0 -0
- package/docs/docs/tutorial-extras/img/localeDropdown.png +0 -0
- package/docs/docs/tutorial-extras/manage-docs-versions.md +55 -0
- package/docs/docs/tutorial-extras/translate-your-site.md +88 -0
- package/docs/docusaurus.config.js +120 -0
- package/docs/package.json +44 -0
- package/docs/sidebars.js +31 -0
- package/docs/src/components/HomepageFeatures/index.js +61 -0
- package/docs/src/components/HomepageFeatures/styles.module.css +11 -0
- package/docs/src/css/custom.css +30 -0
- package/docs/src/pages/index.js +43 -0
- package/docs/src/pages/index.module.css +23 -0
- package/docs/src/pages/markdown-page.md +7 -0
- package/docs/static/.nojekyll +0 -0
- package/docs/static/img/docusaurus-social-card.jpg +0 -0
- package/docs/static/img/docusaurus.png +0 -0
- package/docs/static/img/favicon.ico +0 -0
- package/docs/static/img/logo.svg +1 -0
- package/docs/static/img/undraw_docusaurus_mountain.svg +171 -0
- package/docs/static/img/undraw_docusaurus_react.svg +170 -0
- package/docs/static/img/undraw_docusaurus_tree.svg +40 -0
- package/package.json +75 -0
- package/scripts/generate-chain/index.js +111 -0
- package/scripts/generate-lib/index.js +68 -0
- package/scripts/generate-test/index.js +111 -0
- package/scripts/generate-verblet/README.md +17 -0
- package/scripts/generate-verblet/index.js +110 -0
- package/scripts/run.sh +15 -0
- package/scripts/runner/index.js +30 -0
- package/scripts/simple-editor/README.md +34 -0
- package/scripts/simple-editor/index.js +68 -0
- package/scripts/summarize-files/index.js +46 -0
- package/src/chains/dismantle/dismantle.examples.js +0 -0
- package/src/chains/dismantle/index.examples.js +30 -0
- package/src/chains/dismantle/index.js +314 -0
- package/src/chains/dismantle/index.spec.js +33 -0
- package/src/chains/list/index.examples.js +72 -0
- package/src/chains/list/index.js +161 -0
- package/src/chains/list/index.spec.js +68 -0
- package/src/chains/list/schema.json +24 -0
- package/src/chains/questions/index.examples.js +68 -0
- package/src/chains/questions/index.js +136 -0
- package/src/chains/questions/index.spec.js +29 -0
- package/src/chains/scan-js/index.js +119 -0
- package/src/chains/sort/index.examples.js +40 -0
- package/src/chains/sort/index.js +113 -0
- package/src/chains/sort/index.spec.js +115 -0
- package/src/chains/summary-map/README.md +33 -0
- package/src/chains/summary-map/index.examples.js +57 -0
- package/src/chains/summary-map/index.js +208 -0
- package/src/chains/summary-map/index.spec.js +78 -0
- package/src/chains/test/index.js +118 -0
- package/src/chains/test-advice/index.js +36 -0
- package/src/constants/common.js +9 -0
- package/src/constants/messages.js +3 -0
- package/src/constants/openai.js +65 -0
- package/src/index.js +33 -0
- package/src/json-schemas/cars-test.json +11 -0
- package/src/json-schemas/index.js +18 -0
- package/src/json-schemas/intent.json +38 -0
- package/src/json-schemas/schema-dot-org-photograph.json +127 -0
- package/src/json-schemas/schema-dot-org-place.json +56 -0
- package/src/lib/any-signal/index.js +28 -0
- package/src/lib/chatgpt/index.js +143 -0
- package/src/lib/editor/index.js +31 -0
- package/src/lib/parse-js-parts/index.js +333 -0
- package/src/lib/parse-js-parts/index.spec.js +156 -0
- package/src/lib/path-aliases/index.js +39 -0
- package/src/lib/path-aliases/index.spec.js +70 -0
- package/src/lib/pave/index.js +34 -0
- package/src/lib/pave/index.spec.js +73 -0
- package/src/lib/prompt-cache/index.js +46 -0
- package/src/lib/retry/index.js +63 -0
- package/src/lib/retry/index.spec.js +86 -0
- package/src/lib/search-best-first/index.js +66 -0
- package/src/lib/search-js-files/code-features-property-definitions.json +123 -0
- package/src/lib/search-js-files/index.examples.js +22 -0
- package/src/lib/search-js-files/index.js +158 -0
- package/src/lib/search-js-files/index.spec.js +34 -0
- package/src/lib/search-js-files/scan-file.js +253 -0
- package/src/lib/shorten-text/index.js +30 -0
- package/src/lib/shorten-text/index.spec.js +68 -0
- package/src/lib/strip-numeric/index.js +5 -0
- package/src/lib/strip-response/index.js +35 -0
- package/src/lib/timed-abort-controller/index.js +41 -0
- package/src/lib/to-bool/index.js +8 -0
- package/src/lib/to-enum/index.js +14 -0
- package/src/lib/to-number/index.js +12 -0
- package/src/lib/to-number-with-units/index.js +51 -0
- package/src/lib/transcribe/index.js +61 -0
- package/src/prompts/README.md +15 -0
- package/src/prompts/as-enum.js +5 -0
- package/src/prompts/as-json-schema.js +9 -0
- package/src/prompts/as-object-with-schema.js +31 -0
- package/src/prompts/as-schema-org-text.js +17 -0
- package/src/prompts/as-schema-org-type.js +1 -0
- package/src/prompts/blog-post.js +7 -0
- package/src/prompts/code-features.js +28 -0
- package/src/prompts/constants.js +101 -0
- package/src/prompts/features-json-schema.js +27 -0
- package/src/prompts/generate-collection.js +26 -0
- package/src/prompts/generate-list.js +48 -0
- package/src/prompts/generate-questions.js +19 -0
- package/src/prompts/index.js +20 -0
- package/src/prompts/intent.js +66 -0
- package/src/prompts/output-succinct-names.js +3 -0
- package/src/prompts/select-from-threshold.js +18 -0
- package/src/prompts/sort.js +35 -0
- package/src/prompts/style.js +41 -0
- package/src/prompts/summarize.js +13 -0
- package/src/prompts/token-budget.js +3 -0
- package/src/prompts/wrap-list.js +14 -0
- package/src/prompts/wrap-variable.js +36 -0
- package/src/services/llm-model/index.js +114 -0
- package/src/services/llm-model/model.js +21 -0
- package/src/services/redis/index.js +84 -0
- package/src/verblets/auto/index.examples.js +28 -0
- package/src/verblets/auto/index.js +28 -0
- package/src/verblets/auto/index.spec.js +34 -0
- package/src/verblets/bool/index.examples.js +28 -0
- package/src/verblets/bool/index.js +28 -0
- package/src/verblets/bool/index.schema.json +14 -0
- package/src/verblets/bool/index.spec.js +35 -0
- package/src/verblets/enum/index.examples.js +33 -0
- package/src/verblets/enum/index.js +15 -0
- package/src/verblets/enum/index.spec.js +35 -0
- package/src/verblets/intent/index.examples.js +51 -0
- package/src/verblets/intent/index.js +72 -0
- package/src/verblets/intent/index.spec.js +31 -0
- package/src/verblets/number/index.examples.js +33 -0
- package/src/verblets/number/index.js +22 -0
- package/src/verblets/number/index.spec.js +35 -0
- package/src/verblets/number-with-units/index.examples.js +34 -0
- package/src/verblets/number-with-units/index.js +19 -0
- package/src/verblets/number-with-units/index.spec.js +46 -0
- package/src/verblets/schema-org/index.examples.js +56 -0
- package/src/verblets/schema-org/index.js +8 -0
- package/src/verblets/schema-org/index.spec.js +39 -0
- package/src/verblets/to-object/README.md +38 -0
- package/src/verblets/to-object/index.examples.js +29 -0
- package/src/verblets/to-object/index.js +136 -0
- package/src/verblets/to-object/index.spec.js +74 -0
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/* eslint-disable no-await-in-loop */
|
|
2
|
+
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import fs from 'fs';
|
|
5
|
+
import { parse } from 'acorn';
|
|
6
|
+
import * as walk from 'acorn-walk';
|
|
7
|
+
import dependencyTree from 'dependency-tree';
|
|
8
|
+
|
|
9
|
+
const stripRootDir = (pathStr, root = process.cwd()) => {
|
|
10
|
+
return pathStr.replace(new RegExp(`^${root}`), '');
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const convertImport = (filePath, importPath) => {
|
|
14
|
+
return stripRootDir(path.resolve(path.dirname(filePath), importPath));
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const flatten = (obj) => {
|
|
18
|
+
let result = Object.keys(obj).reduce(
|
|
19
|
+
(memo, key) => ({
|
|
20
|
+
...memo,
|
|
21
|
+
[key]: 1,
|
|
22
|
+
}),
|
|
23
|
+
{}
|
|
24
|
+
);
|
|
25
|
+
|
|
26
|
+
result = Object.keys(obj).reduce((acc, key) => {
|
|
27
|
+
if (typeof obj[key] === 'object') {
|
|
28
|
+
const flattenedValue = flatten(obj[key]);
|
|
29
|
+
return Object.entries(flattenedValue).reduce(
|
|
30
|
+
(innerAcc, [innerKey, innerVal]) => {
|
|
31
|
+
return {
|
|
32
|
+
...innerAcc,
|
|
33
|
+
[innerKey]: (innerAcc[innerKey] ?? 0) + innerVal,
|
|
34
|
+
};
|
|
35
|
+
},
|
|
36
|
+
acc
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return acc;
|
|
40
|
+
}, result);
|
|
41
|
+
|
|
42
|
+
return result;
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const listFiles = (file, cwd) => {
|
|
46
|
+
const config = {
|
|
47
|
+
filename: file,
|
|
48
|
+
directory: cwd,
|
|
49
|
+
nodeModulesConfig: { entry: 'module' },
|
|
50
|
+
nonExistent: [],
|
|
51
|
+
filter: (p) => p.indexOf('node_modules') === -1,
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
return dependencyTree(config);
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const parseFiles = (files) => {
|
|
58
|
+
const filesMap = {};
|
|
59
|
+
for (const file of files) {
|
|
60
|
+
const functionsMap = {};
|
|
61
|
+
const functionsSeen = {};
|
|
62
|
+
const variablesMap = {};
|
|
63
|
+
const importsMap = {};
|
|
64
|
+
|
|
65
|
+
const code = fs.readFileSync(file, 'utf-8');
|
|
66
|
+
|
|
67
|
+
const comments = [];
|
|
68
|
+
|
|
69
|
+
const ast = parse(code, {
|
|
70
|
+
sourceType: 'module',
|
|
71
|
+
ecmaVersion: 'latest',
|
|
72
|
+
onComment: comments,
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
const commentsMap = comments.reduce((acc, comment) => {
|
|
76
|
+
return { ...acc, [comment.start]: comment };
|
|
77
|
+
}, {});
|
|
78
|
+
|
|
79
|
+
filesMap[stripRootDir(file)] = {
|
|
80
|
+
functionsMap,
|
|
81
|
+
variablesMap,
|
|
82
|
+
importsMap,
|
|
83
|
+
commentsMap,
|
|
84
|
+
};
|
|
85
|
+
|
|
86
|
+
walk.simple(ast, {
|
|
87
|
+
ImportDeclaration(node) {
|
|
88
|
+
const declaration = node.specifiers
|
|
89
|
+
.filter((s) => s.type === 'ImportDefaultSpecifier')
|
|
90
|
+
.map((s) => {
|
|
91
|
+
return s.local.name;
|
|
92
|
+
})?.[0];
|
|
93
|
+
const specifiers = node.specifiers
|
|
94
|
+
.filter((s) => s.type === 'ImportSpecifier')
|
|
95
|
+
.map((s) => {
|
|
96
|
+
return { ...s.imported }.name; // also has local name
|
|
97
|
+
});
|
|
98
|
+
const source = node.source.value;
|
|
99
|
+
const importKey = source.startsWith('.')
|
|
100
|
+
? convertImport(file, source)
|
|
101
|
+
: source;
|
|
102
|
+
importsMap[importKey] = {
|
|
103
|
+
start: node.start,
|
|
104
|
+
end: node.end,
|
|
105
|
+
declaration,
|
|
106
|
+
specifiers,
|
|
107
|
+
source,
|
|
108
|
+
};
|
|
109
|
+
},
|
|
110
|
+
|
|
111
|
+
FunctionDeclaration(node) {
|
|
112
|
+
functionsMap[`${node.type}:${node.id.name}`] = {
|
|
113
|
+
start: node.start,
|
|
114
|
+
end: node.end,
|
|
115
|
+
name: node.id.name,
|
|
116
|
+
type: node.type,
|
|
117
|
+
async: node.async,
|
|
118
|
+
generator: node.generator,
|
|
119
|
+
};
|
|
120
|
+
},
|
|
121
|
+
|
|
122
|
+
VariableDeclaration(node) {
|
|
123
|
+
const arrowDeclarations = node.declarations.filter((d) => {
|
|
124
|
+
return (
|
|
125
|
+
d.id.type === 'Identifier' &&
|
|
126
|
+
d.init?.type === 'ArrowFunctionExpression' &&
|
|
127
|
+
d.init?.body.type === 'BlockStatement'
|
|
128
|
+
);
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
arrowDeclarations.forEach((d) => {
|
|
132
|
+
functionsSeen[`${d.init.start}:${d.init.end}`] = true;
|
|
133
|
+
|
|
134
|
+
functionsMap[`ArrowFunctionExpression:${d.id.name}`] = {
|
|
135
|
+
start: d.start,
|
|
136
|
+
end: d.end,
|
|
137
|
+
name: d.id.name,
|
|
138
|
+
async: d.init.async,
|
|
139
|
+
generator: d.init.generator,
|
|
140
|
+
type: 'ArrowFunctionExpression',
|
|
141
|
+
};
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
const fnExpDeclarations = node.declarations.filter((d) => {
|
|
145
|
+
return (
|
|
146
|
+
d.id.type === 'Identifier' &&
|
|
147
|
+
d.init?.type === 'FunctionExpression' &&
|
|
148
|
+
d.init?.body.type === 'BlockStatement'
|
|
149
|
+
);
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
fnExpDeclarations.forEach((d) => {
|
|
153
|
+
functionsSeen[`${d.init.start}:${d.init.end}`] = true;
|
|
154
|
+
|
|
155
|
+
functionsMap[`FunctionExpression:${d.id.name}`] = {
|
|
156
|
+
start: d.start,
|
|
157
|
+
end: d.end,
|
|
158
|
+
name: d.id.name,
|
|
159
|
+
async: d.init.async,
|
|
160
|
+
generator: d.init.generator,
|
|
161
|
+
type: 'FunctionExpression',
|
|
162
|
+
};
|
|
163
|
+
});
|
|
164
|
+
},
|
|
165
|
+
|
|
166
|
+
// Property Assignment
|
|
167
|
+
AssignmentExpression(node) {
|
|
168
|
+
if (
|
|
169
|
+
(node.right.type === 'FunctionExpression' ||
|
|
170
|
+
node.right.type === 'ArrowFunctionExpression') &&
|
|
171
|
+
node.value
|
|
172
|
+
) {
|
|
173
|
+
functionsSeen[`${node.right.start}:${node.right.end}`] = true;
|
|
174
|
+
|
|
175
|
+
functionsMap[`${node.value.type}:${node.left.property.name}`] = {
|
|
176
|
+
start: node.start,
|
|
177
|
+
end: node.end,
|
|
178
|
+
name: node.left.property.name,
|
|
179
|
+
async: node.right.async,
|
|
180
|
+
generator: node.right.generator,
|
|
181
|
+
type: node.right.type,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
// Object Definition
|
|
187
|
+
Property(node) {
|
|
188
|
+
if (
|
|
189
|
+
node.value.type === 'FunctionExpression' ||
|
|
190
|
+
node.value.type === 'ArrowFunctionExpression'
|
|
191
|
+
) {
|
|
192
|
+
functionsSeen[`${node.value.start}:${node.value.end}`] = true;
|
|
193
|
+
|
|
194
|
+
functionsMap[`Property:${node.key.name}`] = {
|
|
195
|
+
start: node.start,
|
|
196
|
+
end: node.end,
|
|
197
|
+
name: node.key.name,
|
|
198
|
+
async: node.value.async,
|
|
199
|
+
generator: node.value.generator,
|
|
200
|
+
type: node.value.type,
|
|
201
|
+
};
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
|
|
205
|
+
// Class Definition
|
|
206
|
+
ClassDeclaration(node) {
|
|
207
|
+
const className = node.id.name;
|
|
208
|
+
node.body.body.forEach((classElement) => {
|
|
209
|
+
if (classElement.type === 'MethodDefinition') {
|
|
210
|
+
functionsSeen[
|
|
211
|
+
`${classElement.value.start}:${classElement.value.end}`
|
|
212
|
+
] = true;
|
|
213
|
+
|
|
214
|
+
functionsMap[
|
|
215
|
+
`MethodDefinition:${className}.${classElement.key.name}`
|
|
216
|
+
] = {
|
|
217
|
+
start: node.start,
|
|
218
|
+
end: node.end,
|
|
219
|
+
className: className,
|
|
220
|
+
name: classElement.key.name,
|
|
221
|
+
async: classElement.value.async,
|
|
222
|
+
generator: classElement.value.generator,
|
|
223
|
+
type: 'MethodDefinition',
|
|
224
|
+
};
|
|
225
|
+
}
|
|
226
|
+
});
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
walk.simple(ast, {
|
|
231
|
+
FunctionExpression(node) {
|
|
232
|
+
if (!functionsSeen[`${node.start}:${node.end}`]) {
|
|
233
|
+
functionsMap[`${node.type}:${node.start}`] = {
|
|
234
|
+
start: node.start,
|
|
235
|
+
end: node.end,
|
|
236
|
+
type: node.type,
|
|
237
|
+
async: node.async,
|
|
238
|
+
generator: node.generator,
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
},
|
|
242
|
+
});
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
return filesMap;
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
export default (entryFile) => {
|
|
249
|
+
const filesTree = listFiles(entryFile, process.cwd());
|
|
250
|
+
const filesList = Object.keys(flatten(filesTree));
|
|
251
|
+
const results = parseFiles(filesList);
|
|
252
|
+
return results;
|
|
253
|
+
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import modelService from '../../services/llm-model/index.js';
|
|
2
|
+
|
|
3
|
+
export default (
|
|
4
|
+
text,
|
|
5
|
+
{
|
|
6
|
+
minCharsToRemove = 10,
|
|
7
|
+
model = modelService.getBestAvailableModel(),
|
|
8
|
+
targetTokenCount,
|
|
9
|
+
}
|
|
10
|
+
) => {
|
|
11
|
+
const ellipsis = '...';
|
|
12
|
+
const textToTokenRatio = text.length / model.toTokens(text).length;
|
|
13
|
+
let trimmedText = text;
|
|
14
|
+
let tokenCount = model.toTokens(trimmedText).length;
|
|
15
|
+
|
|
16
|
+
while (tokenCount > targetTokenCount) {
|
|
17
|
+
const charsToRemove = Math.max(
|
|
18
|
+
Math.ceil((tokenCount - targetTokenCount) * textToTokenRatio),
|
|
19
|
+
minCharsToRemove
|
|
20
|
+
);
|
|
21
|
+
const middleIndex = Math.floor(trimmedText.length / 2);
|
|
22
|
+
const startIndex = middleIndex - Math.ceil(charsToRemove / 2);
|
|
23
|
+
const endIndex = middleIndex + Math.floor(charsToRemove / 2);
|
|
24
|
+
trimmedText =
|
|
25
|
+
trimmedText.slice(0, startIndex) + ellipsis + trimmedText.slice(endIndex);
|
|
26
|
+
tokenCount = model.toTokens(trimmedText).length;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
return trimmedText;
|
|
30
|
+
};
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
import modelService from '../../services/llm-model/index.js';
|
|
3
|
+
|
|
4
|
+
import shortenText from './index.js';
|
|
5
|
+
|
|
6
|
+
const examples = [
|
|
7
|
+
{
|
|
8
|
+
name: 'Basic usage',
|
|
9
|
+
inputs: {
|
|
10
|
+
text: 'Hello, world! This is a long text for testing the shortenText function.',
|
|
11
|
+
targetTokenCount: 10,
|
|
12
|
+
},
|
|
13
|
+
want: {
|
|
14
|
+
start: /^Hello, world!/,
|
|
15
|
+
end: /Text function\.$/,
|
|
16
|
+
maxLength: 40,
|
|
17
|
+
},
|
|
18
|
+
},
|
|
19
|
+
{
|
|
20
|
+
name: 'No trimming needed',
|
|
21
|
+
inputs: {
|
|
22
|
+
text: 'This text is short enough.',
|
|
23
|
+
targetTokenCount: 8,
|
|
24
|
+
},
|
|
25
|
+
want: {
|
|
26
|
+
result: 'This text is short enough.',
|
|
27
|
+
},
|
|
28
|
+
},
|
|
29
|
+
{
|
|
30
|
+
name: 'Minimum characters removal',
|
|
31
|
+
inputs: {
|
|
32
|
+
text: 'This is another test to check the minimum characters removal feature.',
|
|
33
|
+
targetTokenCount: 6,
|
|
34
|
+
minCharsToRemove: 5,
|
|
35
|
+
},
|
|
36
|
+
want: {
|
|
37
|
+
start: /^This is/,
|
|
38
|
+
end: /feature\.$/,
|
|
39
|
+
maxLength: 25,
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
];
|
|
43
|
+
|
|
44
|
+
describe('Shorten text', () => {
|
|
45
|
+
examples.forEach((example) => {
|
|
46
|
+
it(example.name, () => {
|
|
47
|
+
const got = shortenText(example.inputs.text, {
|
|
48
|
+
targetTokenCount: example.inputs.targetTokenCount,
|
|
49
|
+
minCharsToRemove: example.inputs.minCharsToRemove,
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
if (example.want.result) {
|
|
53
|
+
expect(got).toEqual(example.want.result);
|
|
54
|
+
}
|
|
55
|
+
if (example.want.start) {
|
|
56
|
+
expect(example.want.start.test(got)).toBe(true);
|
|
57
|
+
}
|
|
58
|
+
if (example.want.start) {
|
|
59
|
+
expect(example.want.end.test(got)).toBe(true);
|
|
60
|
+
}
|
|
61
|
+
if (example.want.maxLength) {
|
|
62
|
+
expect(
|
|
63
|
+
modelService.getBestAvailableModel().toTokens(got).length
|
|
64
|
+
).toBeLessThanOrEqual(example.want.maxLength);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* This regex pattern looks for combinations of:
|
|
3
|
+
*
|
|
4
|
+
* { followed by optional whitespace and ",
|
|
5
|
+
* [ followed by optional whitespace, {, ", a digit [0-9], or [.
|
|
6
|
+
*/
|
|
7
|
+
const jsonStartRegex = /(?:\s*[{[]|[{[]+\s*[{[])/;
|
|
8
|
+
|
|
9
|
+
export default (val) => {
|
|
10
|
+
const [questionPart = '', , answerPart = ''] = val.split(/(=){11,29}/);
|
|
11
|
+
|
|
12
|
+
const answerPartTrimmed = answerPart.trim() ?? '';
|
|
13
|
+
|
|
14
|
+
const answerSection = answerPartTrimmed.length
|
|
15
|
+
? answerPart.trim()
|
|
16
|
+
: questionPart.trim();
|
|
17
|
+
|
|
18
|
+
const answerNoPrefix = answerSection.replace(/[aA]nswer:?/, '').trim();
|
|
19
|
+
|
|
20
|
+
const answerNoPunctuation = answerNoPrefix.replace(/[., ]+$/g, '').trim();
|
|
21
|
+
|
|
22
|
+
const answerNoQuotes = answerNoPunctuation
|
|
23
|
+
.replace(/^['"]/, '')
|
|
24
|
+
.replace(/['"]$/, '')
|
|
25
|
+
.trim();
|
|
26
|
+
|
|
27
|
+
if (answerNoQuotes.startsWith('{') || answerNoQuotes.startsWith('[')) {
|
|
28
|
+
return answerNoQuotes;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const match = answerNoQuotes.match(jsonStartRegex);
|
|
32
|
+
const answerJSONStart = match ? answerNoQuotes.slice(match.index) : undefined;
|
|
33
|
+
|
|
34
|
+
return answerJSONStart ?? answerNoQuotes;
|
|
35
|
+
};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A class that extends AbortController to include a timeout.
|
|
3
|
+
*
|
|
4
|
+
* @class
|
|
5
|
+
* @extends AbortController
|
|
6
|
+
*
|
|
7
|
+
* @example
|
|
8
|
+
* const timeoutController = new TimedAbortController(3000); // Set a 3 second timeout
|
|
9
|
+
* const signal = timeoutController.signal;
|
|
10
|
+
* fetch('https://example.com', { signal })
|
|
11
|
+
* .then(response => response.text())
|
|
12
|
+
* .then(text => console.error(text))
|
|
13
|
+
* .catch(err => {
|
|
14
|
+
* if (err.name === 'AbortError') {
|
|
15
|
+
* console.error('Fetch operation was aborted');
|
|
16
|
+
* } else {
|
|
17
|
+
* console.error('Fetch operation failed', err);
|
|
18
|
+
* }
|
|
19
|
+
* });
|
|
20
|
+
* timeoutController.clearTimeout(); // Manually clear the timeout
|
|
21
|
+
*/
|
|
22
|
+
export default class TimedAbortController extends AbortController {
|
|
23
|
+
/**
|
|
24
|
+
* Creates a new TimedAbortController instance.
|
|
25
|
+
*
|
|
26
|
+
* @param {number} timeout - The time in milliseconds after which to automatically trigger the abort signal.
|
|
27
|
+
*/
|
|
28
|
+
constructor(timeout) {
|
|
29
|
+
super(`AbortError: The operation timed out after ${timeout}ms`);
|
|
30
|
+
this.timeoutId = setTimeout(() => {
|
|
31
|
+
this.abort();
|
|
32
|
+
}, timeout);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Clears the timeout, preventing the abort signal from being automatically triggered.
|
|
37
|
+
*/
|
|
38
|
+
clearTimeout() {
|
|
39
|
+
clearTimeout(this.timeoutId);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import stripResponse from '../strip-response/index.js';
|
|
2
|
+
|
|
3
|
+
export default (value, enumValue) => {
|
|
4
|
+
// Clean up the input by removing whitespace and punctuation
|
|
5
|
+
const valueStripped = stripResponse(value);
|
|
6
|
+
const valueCleaned = valueStripped.replace(/[^\w\s-_/\\]/gi, '').trim();
|
|
7
|
+
|
|
8
|
+
// Map the cleaned input to an enum value
|
|
9
|
+
const foundKey = Object.keys(enumValue).find(
|
|
10
|
+
(key) => key.toLowerCase() === valueCleaned.toLowerCase()
|
|
11
|
+
);
|
|
12
|
+
|
|
13
|
+
return foundKey || undefined;
|
|
14
|
+
};
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import stripResponse from '../strip-response/index.js';
|
|
2
|
+
import stripNumeric from '../strip-numeric/index.js';
|
|
3
|
+
|
|
4
|
+
export default (val) => {
|
|
5
|
+
const valLower = stripResponse(val.toLowerCase());
|
|
6
|
+
if (valLower === 'undefined') return undefined;
|
|
7
|
+
const valParsed = +stripNumeric(val);
|
|
8
|
+
if (Number.isNaN(valParsed)) {
|
|
9
|
+
throw new Error(`ChatGPT output [error]`);
|
|
10
|
+
}
|
|
11
|
+
return valParsed;
|
|
12
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import stripResponse from '../strip-response/index.js';
|
|
2
|
+
import stripNumeric from '../strip-numeric/index.js';
|
|
3
|
+
|
|
4
|
+
const badDatatypeError = 'Bad datatype returned for number query.';
|
|
5
|
+
const valueNotANumberError = 'Value is not a number.';
|
|
6
|
+
|
|
7
|
+
export default (envelope) => {
|
|
8
|
+
const envelopeStripped = stripResponse(envelope);
|
|
9
|
+
let valueExtracted;
|
|
10
|
+
let unitExtracted;
|
|
11
|
+
|
|
12
|
+
if (envelopeStripped === 'undefined') {
|
|
13
|
+
return undefined;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
try {
|
|
17
|
+
const envelopeParsed = JSON.parse(envelopeStripped);
|
|
18
|
+
const { value, unit } = envelopeParsed;
|
|
19
|
+
valueExtracted = value;
|
|
20
|
+
unitExtracted = unit;
|
|
21
|
+
} catch (error) {
|
|
22
|
+
throw new Error(`ChatGPT output [error]: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
let unitParsed = unitExtracted;
|
|
26
|
+
if (unitExtracted === 'undefined') {
|
|
27
|
+
unitParsed = undefined;
|
|
28
|
+
} else if (typeof unitExtracted === 'undefined') {
|
|
29
|
+
unitParsed = undefined;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let valueParsed;
|
|
33
|
+
if (typeof valueExtracted === 'string') {
|
|
34
|
+
const valueLower = valueExtracted.toLowerCase();
|
|
35
|
+
valueParsed = +stripNumeric(valueLower);
|
|
36
|
+
} else if (typeof valueExtracted === 'number') {
|
|
37
|
+
valueParsed = valueExtracted;
|
|
38
|
+
} else if (valueExtracted === 'undefined') {
|
|
39
|
+
valueParsed = undefined;
|
|
40
|
+
} else if (typeof valueExtracted === 'undefined' || valueExtracted === null) {
|
|
41
|
+
valueParsed = undefined;
|
|
42
|
+
} else {
|
|
43
|
+
throw new Error(`ChatGPT output [error]: ${badDatatypeError}`);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (typeof valueParsed !== 'undefined' && Number.isNaN(valueParsed)) {
|
|
47
|
+
throw new Error(`ChatGPT output [error]: ${valueNotANumberError}`);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { value: valueParsed, unit: unitParsed };
|
|
51
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import whisper from 'whisper-node';
|
|
2
|
+
import record from 'node-record-lpcm16';
|
|
3
|
+
|
|
4
|
+
export default class Transcriber {
|
|
5
|
+
|
|
6
|
+
constructor(targetWord, silenceDuration = 5000, wordPauseDuration = 2000) {
|
|
7
|
+
this.targetWord = targetWord;
|
|
8
|
+
this.silenceDuration = silenceDuration;
|
|
9
|
+
this.wordPauseDuration = wordPauseDuration;
|
|
10
|
+
this.transcription = '';
|
|
11
|
+
this.lastTranscribedTime = Date.now();
|
|
12
|
+
this.recording = null;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
startRecording() {
|
|
16
|
+
this.recording = record.record({
|
|
17
|
+
sampleRateHertz: 16000,
|
|
18
|
+
threshold: 0,
|
|
19
|
+
recordProgram: 'rec',
|
|
20
|
+
silence: '5.0',
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
const audioStream = this.recording.stream();
|
|
24
|
+
this.transcribe(audioStream);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
stopRecording() {
|
|
28
|
+
if (this.recording) {
|
|
29
|
+
this.recording.stop();
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
transcribe(stream) {
|
|
34
|
+
whisper.transcribeStream(stream, { streaming: true })
|
|
35
|
+
.then(transcription => {
|
|
36
|
+
this.handleTranscription(transcription);
|
|
37
|
+
})
|
|
38
|
+
.catch(error => {
|
|
39
|
+
console.error(error);
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
handleTranscription(transcription) {
|
|
44
|
+
this.transcription += transcription;
|
|
45
|
+
this.lastTranscribedTime = Date.now();
|
|
46
|
+
|
|
47
|
+
if (transcription.includes(this.targetWord)) {
|
|
48
|
+
setTimeout(() => {
|
|
49
|
+
this.stopRecording();
|
|
50
|
+
}, this.wordPauseDuration);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (Date.now() - this.lastTranscribedTime >= this.silenceDuration) {
|
|
54
|
+
this.stopRecording();
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
getText() {
|
|
59
|
+
return this.transcription;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# Prompts
|
|
2
|
+
|
|
3
|
+
Prompts are functions that produce a single LLM prompt. They can be parameterized in a number of ways in order to fit their problem as closely as possible.
|
|
4
|
+
|
|
5
|
+
## Prompt Functions
|
|
6
|
+
|
|
7
|
+
Currently there isn't much to the structure of these functions, and they're not currently tested.
|
|
8
|
+
|
|
9
|
+
Going forward, functions should output a structured representation whose contents can be validated. An obvious candidate for this representation is HTML.
|
|
10
|
+
|
|
11
|
+
The functions can generate HTML from their inputs and this structure can be transformed into text before sending to ChatGPT. This approach has many benefits. We can use all the tools available for working with HTML. The function outputs can be tested. All varieties of markup problems, like nested content, are trivially solved as well.
|
|
12
|
+
|
|
13
|
+
# Constants
|
|
14
|
+
|
|
15
|
+
Currently it's reasonable to keep the text fragments needed for building prompts in a single constants.js file. It's forseable that the number of text fragments will grow. At that point, it would make sense to maintain them with a CMS such as Strapi.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { contentIsExampleObject, onlyJSON } from './constants.js';
|
|
2
|
+
|
|
3
|
+
const jsonSchemaDefault = {
|
|
4
|
+
type: 'object',
|
|
5
|
+
properties: {
|
|
6
|
+
name: {
|
|
7
|
+
type: 'string',
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export default (jsonSchema = jsonSchemaDefault) => {
|
|
13
|
+
const propertiesJoined = Object.entries(jsonSchema.properties)
|
|
14
|
+
.map(([key, val]) => {
|
|
15
|
+
const annotations = Object.entries(val).filter(
|
|
16
|
+
([annKey, annVal]) =>
|
|
17
|
+
['format', 'description'].includes(annKey) && !!annVal
|
|
18
|
+
);
|
|
19
|
+
const annotationsFormatted = annotations
|
|
20
|
+
.map(([k, v]) => `${k}: ${v}`)
|
|
21
|
+
.join(', ');
|
|
22
|
+
const annotationsWrapped = annotations.length
|
|
23
|
+
? ` (${annotationsFormatted})`
|
|
24
|
+
: '';
|
|
25
|
+
return `"${key}": "<${val.type ?? ''}${annotationsWrapped}>"`;
|
|
26
|
+
})
|
|
27
|
+
.join(', ');
|
|
28
|
+
|
|
29
|
+
return `${contentIsExampleObject} \`{ ${propertiesJoined} }\`. ${onlyJSON}.
|
|
30
|
+
`;
|
|
31
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { onlyJSON } from './constants.js';
|
|
2
|
+
import asSchemaOrgType from './as-schema-org-type.js';
|
|
3
|
+
|
|
4
|
+
const ensureNumbers = 'ensure values meant to be numbers are numbers';
|
|
5
|
+
const ensureSchemaOrgType = 'ensure the type is a real schema.org type';
|
|
6
|
+
const ensureProperties = 'ensure the returned object has @context, name';
|
|
7
|
+
|
|
8
|
+
export default (object, type) => {
|
|
9
|
+
const typeText = `${asSchemaOrgType(type)}`;
|
|
10
|
+
return `Give me "${object}" in schema.org JSON format with a full set of properties. ${
|
|
11
|
+
typeText ? `${typeText}.` : ''
|
|
12
|
+
}
|
|
13
|
+
- ${ensureNumbers}
|
|
14
|
+
- ${ensureSchemaOrgType}
|
|
15
|
+
- ${ensureProperties}
|
|
16
|
+
${onlyJSON}`;
|
|
17
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export default (type) => (type ? `Ensure the type is ${type}. ` : '');
|