kalo-cli 0.1.2 → 0.2.27
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/bin/kalo.ts +1 -24
- package/dist/docs/app.js +42644 -0
- package/dist/docs/index.html +32 -0
- package/generators/{django-app → generator/django-app}/index.ts +18 -16
- package/generators/generator/django-app/templates/admin.py.hbs +6 -0
- package/generators/{django-app → generator/django-app}/templates/apps.py.hbs +2 -2
- package/generators/generator/django-app/templates/urls.py.hbs +8 -0
- package/generators/generator/django-app/templates/views.py.hbs +5 -0
- package/generators/{django-channel → generator/django-channel}/index.ts +8 -7
- package/generators/{django-channel → generator/django-channel}/templates/consumer.py.hbs +2 -2
- package/generators/{django-channel → generator/django-channel}/templates/routing.py.hbs +2 -2
- package/generators/{django-form → generator/django-form}/index.ts +6 -5
- package/generators/{django-form → generator/django-form}/templates/form.py.hbs +5 -2
- package/generators/generator/django-form/templates/forms_file.py.hbs +6 -0
- package/generators/{django-form → generator/django-form}/templates/model_form.py.hbs +3 -2
- package/generators/{django-view → generator/django-view}/index.ts +8 -8
- package/generators/{django-view → generator/django-view}/templates/view_cbv.py.hbs +6 -3
- package/generators/{django-view → generator/django-view}/templates/view_fbv.py.hbs +10 -7
- package/generators/generator/django-view/templates/view_template.html.hbs +8 -0
- package/generators/generator/main/index.ts +70 -0
- package/generators/{wagtail-admin → generator/wagtail-admin}/index.ts +12 -11
- package/generators/{wagtail-admin → generator/wagtail-admin}/templates/admin_view.html.hbs +2 -2
- package/generators/{wagtail-admin → generator/wagtail-admin}/templates/admin_view.py.hbs +2 -2
- package/generators/{wagtail-admin → generator/wagtail-admin}/templates/component.html.hbs +2 -2
- package/generators/{wagtail-admin → generator/wagtail-admin}/templates/component.py.hbs +2 -2
- package/generators/{wagtail-admin → generator/wagtail-admin}/templates/wagtail_hooks.py.hbs +2 -2
- package/generators/{wagtail-block → generator/wagtail-block}/index.ts +7 -6
- package/generators/{wagtail-block → generator/wagtail-block}/templates/block_class.py.hbs +5 -2
- package/generators/generator/wagtail-block/templates/block_template.html.hbs +5 -0
- package/generators/{wagtail-page → generator/wagtail-page}/actions/model.ts +5 -4
- package/generators/{wagtail-page → generator/wagtail-page}/actions/orderable.ts +5 -4
- package/generators/{wagtail-page → generator/wagtail-page}/actions/page.ts +7 -6
- package/generators/{wagtail-page → generator/wagtail-page}/actions/snippet.ts +4 -4
- package/generators/{wagtail-page → generator/wagtail-page}/index.ts +6 -6
- package/generators/{wagtail-page → generator/wagtail-page}/templates/django_model.py.hbs +6 -3
- package/generators/{wagtail-page → generator/wagtail-page}/templates/orderable_model.py.hbs +9 -3
- package/generators/{wagtail-page → generator/wagtail-page}/templates/page_pair_model.py.hbs +16 -9
- package/generators/{wagtail-page → generator/wagtail-page}/templates/page_template.html.hbs +2 -2
- package/generators/{wagtail-page → generator/wagtail-page}/templates/snippet_model.py.hbs +7 -2
- package/generators/{ai-enhancer → ia/ai-enhancer}/index.ts +319 -281
- package/generators/{help → ia/help}/index.ts +4 -3
- package/generators/main/index.ts +6 -5
- package/generators/utils/analysis.ts +5 -9
- package/generators/utils/code-manipulation.ts +145 -35
- package/generators/utils/filesystem.ts +31 -8
- package/generators/utils/index.ts +0 -12
- package/generators/utils/plop-actions.ts +1 -1
- package/package.json +6 -3
- package/plopfile.ts +25 -25
- package/generators/django-app/templates/admin.py.hbs +0 -6
- package/generators/django-app/templates/urls.py.hbs +0 -8
- package/generators/django-app/templates/views.py.hbs +0 -5
- package/generators/django-form/templates/forms_file.py.hbs +0 -6
- package/generators/django-view/templates/view_template.html.hbs +0 -8
- package/generators/wagtail-block/templates/block_template.html.hbs +0 -5
- /package/generators/{django-app → generator/django-app}/templates/init.py.hbs +0 -0
- /package/generators/{django-app → generator/django-app}/templates/models_init.py.hbs +0 -0
- /package/generators/{docs → ia/docs}/index.ts +0 -0
- /package/generators/{ai-enhancer → ia/docs}/keywords.json +0 -0
|
@@ -1,281 +1,319 @@
|
|
|
1
|
-
|
|
2
|
-
import { PlopGeneratorConfig } from 'plop';
|
|
3
|
-
import { generateWithModel } from '
|
|
4
|
-
import { injectMarkers, applyGeneratedCode } from '
|
|
5
|
-
import { Glob } from "bun";
|
|
6
|
-
import { searchChoices } from '
|
|
7
|
-
import {
|
|
8
|
-
import
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
let
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
currentLine
|
|
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
|
-
choices.push({ name: '
|
|
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
|
-
const
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
{ name: '
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
//
|
|
214
|
-
const
|
|
215
|
-
|
|
216
|
-
//
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
"
|
|
241
|
-
"
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
1
|
+
|
|
2
|
+
import { PlopGeneratorConfig } from 'plop';
|
|
3
|
+
import { generateWithModel } from '../../utils/ai/common.ts';
|
|
4
|
+
import { injectMarkers, applyGeneratedCode } from '../../utils/code-manipulation.ts';
|
|
5
|
+
import { Glob } from "bun";
|
|
6
|
+
import { searchChoices } from '../../utils/search.ts';
|
|
7
|
+
import { findProjectRoot } from '../../utils/filesystem.ts';
|
|
8
|
+
import { AI_PROVIDER_TYPES, AI_TEMP_TYPES, BACK_VALUE, EXIT_VALUE } from '../../constants.ts';
|
|
9
|
+
import keywordConfig from '../docs/keywords.json';
|
|
10
|
+
|
|
11
|
+
const getHelpItems = (input: string | undefined): any[] => {
|
|
12
|
+
const items: any[] = [];
|
|
13
|
+
|
|
14
|
+
if (input) {
|
|
15
|
+
const inputLower = input.toLowerCase();
|
|
16
|
+
let lastMatchIndex = -1;
|
|
17
|
+
let matchedInstruction = null;
|
|
18
|
+
|
|
19
|
+
const keywords = Array.isArray(keywordConfig) ? keywordConfig : (keywordConfig as any).keywords || [];
|
|
20
|
+
|
|
21
|
+
for (const entry of keywords) {
|
|
22
|
+
if (entry.terms && Array.isArray(entry.terms)) {
|
|
23
|
+
for (const term of entry.terms) {
|
|
24
|
+
const index = inputLower.lastIndexOf(term.toLowerCase());
|
|
25
|
+
if (index !== -1 && index >= lastMatchIndex) {
|
|
26
|
+
lastMatchIndex = index;
|
|
27
|
+
matchedInstruction = entry.instruction;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (matchedInstruction) {
|
|
34
|
+
// Split instruction into lines of max 80 chars for better visibility
|
|
35
|
+
const words = matchedInstruction.split(' ');
|
|
36
|
+
let currentLine = '💡 ';
|
|
37
|
+
|
|
38
|
+
for (const word of words) {
|
|
39
|
+
if ((currentLine + word).length > 80) {
|
|
40
|
+
items.push({ name: currentLine, value: 'INFO', disabled: true });
|
|
41
|
+
currentLine = ' ' + word + ' ';
|
|
42
|
+
} else {
|
|
43
|
+
currentLine += word + ' ';
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
if (currentLine.trim()) {
|
|
47
|
+
items.push({ name: currentLine, value: 'INFO', disabled: true });
|
|
48
|
+
}
|
|
49
|
+
items.push({ name: '────────────────────────────────────────', value: 'SEP', disabled: true });
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return items;
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
export const helpGenerator: PlopGeneratorConfig = {
|
|
56
|
+
description: 'Search for AI instructions and keywords',
|
|
57
|
+
prompts: [
|
|
58
|
+
{
|
|
59
|
+
type: 'autocomplete',
|
|
60
|
+
name: 'query',
|
|
61
|
+
message: 'Type a keyword to see associated instructions (e.g. "model", "field"):',
|
|
62
|
+
suggestOnly: true,
|
|
63
|
+
source: async (answers, input) => {
|
|
64
|
+
const choices: any[] = [];
|
|
65
|
+
|
|
66
|
+
const helpItems = getHelpItems(input);
|
|
67
|
+
if (helpItems.length > 0) {
|
|
68
|
+
choices.push(...helpItems);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
if (!input) {
|
|
72
|
+
choices.push({ name: 'Type to search...', value: 'INFO', disabled: true });
|
|
73
|
+
choices.push({ name: 'Exit', value: EXIT_VALUE });
|
|
74
|
+
return choices;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
choices.push({ name: 'Exit', value: EXIT_VALUE });
|
|
78
|
+
return choices;
|
|
79
|
+
},
|
|
80
|
+
validate: (value) => {
|
|
81
|
+
if (value === EXIT_VALUE || value?.toLowerCase() === 'exit') {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
return true;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
],
|
|
88
|
+
actions: []
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
const handleInput = (value: any) => {
|
|
92
|
+
if (value === EXIT_VALUE || (typeof value === 'string' && value.toLowerCase() === 'exit')) {
|
|
93
|
+
return EXIT_VALUE;
|
|
94
|
+
}
|
|
95
|
+
return value;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
const isBack = (answers: any) => {
|
|
99
|
+
return Object.values(answers).includes(BACK_VALUE) || Object.values(answers).includes(EXIT_VALUE);
|
|
100
|
+
};
|
|
101
|
+
|
|
102
|
+
export const aiEnhancerGenerator: PlopGeneratorConfig = {
|
|
103
|
+
description: 'Enhance existing models with AI generated code',
|
|
104
|
+
prompts: [
|
|
105
|
+
{
|
|
106
|
+
type: 'autocomplete',
|
|
107
|
+
name: 'filePath',
|
|
108
|
+
message: 'Select the model file to enhance:',
|
|
109
|
+
source: async (answers, input) => {
|
|
110
|
+
const glob = new Glob("**/*.py");
|
|
111
|
+
const files: string[] = [];
|
|
112
|
+
const rootDir = findProjectRoot();
|
|
113
|
+
// Scans the project root
|
|
114
|
+
for await (const file of glob.scan(rootDir)) {
|
|
115
|
+
// Filter for likely python model files or snippets
|
|
116
|
+
if (file.endsWith('.py') && (file.includes("models") || file.includes("snippet"))) {
|
|
117
|
+
files.push(file);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const choices = files.map(f => ({ name: f, value: f }));
|
|
121
|
+
const results = await searchChoices(input, choices);
|
|
122
|
+
results.push({ name: 'Exit', value: EXIT_VALUE });
|
|
123
|
+
return results;
|
|
124
|
+
},
|
|
125
|
+
filter: handleInput
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
type: 'autocomplete',
|
|
129
|
+
name: 'instruction',
|
|
130
|
+
message: 'What modification/addition do you want to generate?',
|
|
131
|
+
when: (answers) => !isBack(answers),
|
|
132
|
+
suggestOnly: true,
|
|
133
|
+
source: async (answers, input) => {
|
|
134
|
+
// We provide a "Exit" option even for input by using autocomplete with suggestOnly
|
|
135
|
+
if (!input) return [{ name: 'Exit', value: EXIT_VALUE }];
|
|
136
|
+
return [{ name: 'Exit', value: EXIT_VALUE }];
|
|
137
|
+
},
|
|
138
|
+
validate: (value) => {
|
|
139
|
+
if (value === EXIT_VALUE || value?.toLowerCase() === 'exit') return true;
|
|
140
|
+
return value ? true : 'Instruction is required';
|
|
141
|
+
},
|
|
142
|
+
filter: handleInput
|
|
143
|
+
},
|
|
144
|
+
{
|
|
145
|
+
type: 'autocomplete',
|
|
146
|
+
name: 'aiProvider',
|
|
147
|
+
message: 'Select AI Provider:',
|
|
148
|
+
default: 'Qwen',
|
|
149
|
+
when: (answers: any) => !isBack(answers) && !answers.config?.aiProvider,
|
|
150
|
+
source: async (answers, input) => {
|
|
151
|
+
const choices = AI_PROVIDER_TYPES.map(p => ({ name: p, value: p }));
|
|
152
|
+
const results = await searchChoices(input, choices);
|
|
153
|
+
results.push({ name: 'Exit', value: EXIT_VALUE });
|
|
154
|
+
return results;
|
|
155
|
+
},
|
|
156
|
+
filter: handleInput
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
type: 'autocomplete',
|
|
160
|
+
name: 'temperature',
|
|
161
|
+
message: 'Select Creativity Level:',
|
|
162
|
+
default: 0.5,
|
|
163
|
+
when: (answers: any) => !isBack(answers) && answers.config?.temperature === undefined,
|
|
164
|
+
source: async (answers, input) => {
|
|
165
|
+
const results = await searchChoices(input, AI_TEMP_TYPES);
|
|
166
|
+
results.push({ name: 'Exit', value: EXIT_VALUE });
|
|
167
|
+
return results;
|
|
168
|
+
},
|
|
169
|
+
filter: handleInput
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
type: 'autocomplete',
|
|
173
|
+
name: 'verbosity',
|
|
174
|
+
message: 'Select Verbosity Level:',
|
|
175
|
+
default: 'standard',
|
|
176
|
+
when: (answers: any) => !isBack(answers) && !answers.config?.verbosity,
|
|
177
|
+
source: async (answers, input) => {
|
|
178
|
+
const choices = [
|
|
179
|
+
{ name: 'Minimal (Code only)', value: 'minimal' },
|
|
180
|
+
{ name: 'Standard (Code + basic comments)', value: 'standard' },
|
|
181
|
+
{ name: 'Verbose (Detailed docs)', value: 'verbose' }
|
|
182
|
+
];
|
|
183
|
+
const results = await searchChoices(input, choices);
|
|
184
|
+
results.push({ name: 'Exit', value: EXIT_VALUE });
|
|
185
|
+
return results;
|
|
186
|
+
},
|
|
187
|
+
filter: handleInput
|
|
188
|
+
},
|
|
189
|
+
{
|
|
190
|
+
type: 'input',
|
|
191
|
+
name: 'constraints',
|
|
192
|
+
message: 'Terms to listen to / Constraints (comma separated, optional):',
|
|
193
|
+
when: (answers) => !isBack(answers),
|
|
194
|
+
filter: (value) => {
|
|
195
|
+
if (value === EXIT_VALUE || (typeof value === 'string' && value.toLowerCase() === 'exit')) {
|
|
196
|
+
return EXIT_VALUE;
|
|
197
|
+
}
|
|
198
|
+
return value.split(',').map((s: string) => s.trim()).filter((s: string) => s.length > 0);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
],
|
|
202
|
+
actions: (data) => {
|
|
203
|
+
// Handle "Back" action (Abort generator)
|
|
204
|
+
if (Object.values(data).includes(BACK_VALUE) || Object.values(data).includes(EXIT_VALUE)) {
|
|
205
|
+
return [];
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return [
|
|
209
|
+
{
|
|
210
|
+
type: 'modify',
|
|
211
|
+
path: '{{filePath}}',
|
|
212
|
+
transform: async (fileContent, data) => {
|
|
213
|
+
// Extract the class name from the file path or use a default
|
|
214
|
+
const className = data.targetClass || extractClassNameFromFile(fileContent, data.filePath || '') || 'AI_GENERATED';
|
|
215
|
+
|
|
216
|
+
// 1. Inject Markers if missing
|
|
217
|
+
const contentWithMarkers = injectMarkers(fileContent, className);
|
|
218
|
+
|
|
219
|
+
// Keyword analysis for enhanced context
|
|
220
|
+
const keywordInstructions: string[] = [];
|
|
221
|
+
const instructionLower = data.instruction.toLowerCase();
|
|
222
|
+
|
|
223
|
+
const keywords = Array.isArray(keywordConfig) ? keywordConfig : (keywordConfig as any).keywords || [];
|
|
224
|
+
for (const keyword of keywords) {
|
|
225
|
+
if (keyword.term && instructionLower.includes(keyword.term)) {
|
|
226
|
+
keywordInstructions.push(keyword.instruction);
|
|
227
|
+
} else if (keyword.terms) {
|
|
228
|
+
if (keyword.terms.some((t: string) => instructionLower.includes(t))) {
|
|
229
|
+
keywordInstructions.push(keyword.instruction);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Build options object
|
|
235
|
+
const options = {
|
|
236
|
+
temperature: data.temperature,
|
|
237
|
+
constraints: [
|
|
238
|
+
...(data.constraints || []),
|
|
239
|
+
`Target Class: ${className}`,
|
|
240
|
+
"Output Format: JSON with keys: 'imports' (string[]), 'replacements' ({search, replace}[]), 'code' (string).",
|
|
241
|
+
"Rules:",
|
|
242
|
+
"1. 'imports': New import statements. Only include imports that are actually needed for the new code.",
|
|
243
|
+
"2. 'replacements': exact 'search' string to find and 'replace' string to overwrite. Use for modifying existing lists/dicts. The 'search' string must match exactly as it appears in the file, including all whitespace and formatting.",
|
|
244
|
+
"3. 'code': New methods/logic to be injected between the AI markers. Maintain the same indentation level as the surrounding code.",
|
|
245
|
+
"4. When modifying existing code, provide the smallest possible change that accomplishes the task.",
|
|
246
|
+
"5. Preserve existing comments and code structure unless specifically instructed to change them.",
|
|
247
|
+
"6. Follow Python PEP 8 style guidelines.",
|
|
248
|
+
"7. Make sure all code is syntactically correct and properly indented.",
|
|
249
|
+
...keywordInstructions
|
|
250
|
+
],
|
|
251
|
+
verbosity: data.verbosity,
|
|
252
|
+
plopContext: data,
|
|
253
|
+
responseFormat: 'json'
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
let generatedResponseString = '';
|
|
257
|
+
|
|
258
|
+
try {
|
|
259
|
+
const providerName = data.aiProvider || 'Qwen';
|
|
260
|
+
const modelCommand = providerName.toLowerCase();
|
|
261
|
+
|
|
262
|
+
generatedResponseString = await generateWithModel(
|
|
263
|
+
contentWithMarkers, // context
|
|
264
|
+
data.instruction, // instruction
|
|
265
|
+
modelCommand,
|
|
266
|
+
providerName,
|
|
267
|
+
options as any
|
|
268
|
+
);
|
|
269
|
+
} catch (error) {
|
|
270
|
+
console.error('AI Generation Failed:', error);
|
|
271
|
+
return fileContent;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
let generatedData: any;
|
|
275
|
+
try {
|
|
276
|
+
generatedData = JSON.parse(generatedResponseString);
|
|
277
|
+
} catch (e) {
|
|
278
|
+
console.error('Failed to parse AI response as JSON.');
|
|
279
|
+
return contentWithMarkers;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 2. Apply Changes
|
|
283
|
+
return applyGeneratedCode(contentWithMarkers, generatedData, className);
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
]
|
|
287
|
+
}
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
// Helper function to extract class name from Python file
|
|
291
|
+
function extractClassNameFromFile(fileContent: string, filePath: string): string | null {
|
|
292
|
+
// Try to extract class name from the file path first
|
|
293
|
+
const pathParts = filePath.split('/');
|
|
294
|
+
const fileName = pathParts[pathParts.length - 1]?.split('\\').pop(); // Handle both separators
|
|
295
|
+
if (fileName) {
|
|
296
|
+
const nameWithoutExt = fileName.replace('.py', '');
|
|
297
|
+
// Convert snake_case to PascalCase if needed
|
|
298
|
+
const pascalCaseName = nameWithoutExt
|
|
299
|
+
.split('_')
|
|
300
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
301
|
+
.join('');
|
|
302
|
+
|
|
303
|
+
// Check if this class exists in the file
|
|
304
|
+
const classRegex = new RegExp(`^\\s*class\\s+(${pascalCaseName})\\b`, 'm');
|
|
305
|
+
const match = fileContent.match(classRegex);
|
|
306
|
+
if (match) {
|
|
307
|
+
return match[1];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// If not found by filename, try to find any class in the file
|
|
312
|
+
const classRegex = /^(\s*)class\s+(\w+)\s*(\(|:)/m;
|
|
313
|
+
const match = fileContent.match(classRegex);
|
|
314
|
+
if (match) {
|
|
315
|
+
return match[2];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
import { PlopGeneratorConfig } from 'plop';
|
|
3
|
-
import keywordConfig from '../
|
|
4
|
-
import { EXIT_VALUE } from '
|
|
3
|
+
import keywordConfig from '../docs/keywords.json';
|
|
4
|
+
import { EXIT_VALUE } from '../../constants.ts';
|
|
5
5
|
|
|
6
6
|
const getHelpItems = (input: string | undefined): any[] => {
|
|
7
7
|
const items: any[] = [];
|
|
@@ -74,7 +74,8 @@ export const helpGenerator: PlopGeneratorConfig = {
|
|
|
74
74
|
},
|
|
75
75
|
validate: (value) => {
|
|
76
76
|
if (value === EXIT_VALUE || value?.toLowerCase() === 'exit') {
|
|
77
|
-
process.exit(0);
|
|
77
|
+
// process.exit(0); // Avoiding process.exit in library code, returning true to let plop handle it
|
|
78
|
+
return true;
|
|
78
79
|
}
|
|
79
80
|
return true;
|
|
80
81
|
}
|