kalo-cli 0.2.27 → 0.3.0
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/README.md +80 -46
- package/bin/kalo.ts +3 -2
- package/package.json +2 -1
- package/plopfile.ts +182 -25
- package/dist/docs/app.js +0 -42644
- package/dist/docs/index.html +0 -32
- package/generators/constants.ts +0 -52
- package/generators/generator/django-app/index.ts +0 -72
- package/generators/generator/django-app/templates/admin.py.hbs +0 -6
- package/generators/generator/django-app/templates/apps.py.hbs +0 -9
- package/generators/generator/django-app/templates/init.py.hbs +0 -0
- package/generators/generator/django-app/templates/models_init.py.hbs +0 -2
- package/generators/generator/django-app/templates/urls.py.hbs +0 -8
- package/generators/generator/django-app/templates/views.py.hbs +0 -5
- package/generators/generator/django-channel/index.ts +0 -80
- package/generators/generator/django-channel/templates/consumer.py.hbs +0 -47
- package/generators/generator/django-channel/templates/routing.py.hbs +0 -8
- package/generators/generator/django-form/index.ts +0 -64
- package/generators/generator/django-form/templates/form.py.hbs +0 -15
- package/generators/generator/django-form/templates/forms_file.py.hbs +0 -6
- package/generators/generator/django-form/templates/model_form.py.hbs +0 -19
- package/generators/generator/django-view/index.ts +0 -96
- package/generators/generator/django-view/templates/view_cbv.py.hbs +0 -14
- package/generators/generator/django-view/templates/view_fbv.py.hbs +0 -10
- package/generators/generator/django-view/templates/view_template.html.hbs +0 -8
- package/generators/generator/main/index.ts +0 -70
- package/generators/generator/wagtail-admin/index.ts +0 -124
- package/generators/generator/wagtail-admin/templates/admin_view.html.hbs +0 -21
- package/generators/generator/wagtail-admin/templates/admin_view.py.hbs +0 -15
- package/generators/generator/wagtail-admin/templates/component.html.hbs +0 -6
- package/generators/generator/wagtail-admin/templates/component.py.hbs +0 -11
- package/generators/generator/wagtail-admin/templates/wagtail_hooks.py.hbs +0 -18
- package/generators/generator/wagtail-block/index.ts +0 -53
- package/generators/generator/wagtail-block/templates/block_class.py.hbs +0 -16
- package/generators/generator/wagtail-block/templates/block_template.html.hbs +0 -5
- package/generators/generator/wagtail-page/actions/model.ts +0 -20
- package/generators/generator/wagtail-page/actions/orderable.ts +0 -23
- package/generators/generator/wagtail-page/actions/page.ts +0 -42
- package/generators/generator/wagtail-page/actions/snippet.ts +0 -20
- package/generators/generator/wagtail-page/index.ts +0 -63
- package/generators/generator/wagtail-page/templates/django_model.py.hbs +0 -21
- package/generators/generator/wagtail-page/templates/orderable_model.py.hbs +0 -27
- package/generators/generator/wagtail-page/templates/page_pair_model.py.hbs +0 -69
- package/generators/generator/wagtail-page/templates/page_template.html.hbs +0 -14
- package/generators/generator/wagtail-page/templates/snippet_model.py.hbs +0 -29
- package/generators/ia/ai-enhancer/index.ts +0 -319
- package/generators/ia/docs/index.ts +0 -36
- package/generators/ia/docs/keywords.json +0 -1158
- package/generators/ia/help/index.ts +0 -85
- package/generators/main/index.ts +0 -422
- package/generators/utils/ai/common.ts +0 -141
- package/generators/utils/ai/index.ts +0 -2
- package/generators/utils/analysis.ts +0 -88
- package/generators/utils/code-manipulation.ts +0 -229
- package/generators/utils/config.ts +0 -43
- package/generators/utils/filesystem.ts +0 -131
- package/generators/utils/index.ts +0 -35
- package/generators/utils/plop-actions.ts +0 -104
- package/generators/utils/search.ts +0 -24
- package/tsconfig.json +0 -29
|
@@ -1,85 +0,0 @@
|
|
|
1
|
-
|
|
2
|
-
import { PlopGeneratorConfig } from 'plop';
|
|
3
|
-
import keywordConfig from '../docs/keywords.json';
|
|
4
|
-
import { EXIT_VALUE } from '../../constants.ts';
|
|
5
|
-
|
|
6
|
-
const getHelpItems = (input: string | undefined): any[] => {
|
|
7
|
-
const items: any[] = [];
|
|
8
|
-
|
|
9
|
-
if (input) {
|
|
10
|
-
const inputLower = input.toLowerCase();
|
|
11
|
-
let lastMatchIndex = -1;
|
|
12
|
-
let matchedInstruction = null;
|
|
13
|
-
|
|
14
|
-
const keywords = Array.isArray(keywordConfig) ? keywordConfig : (keywordConfig as any).keywords || [];
|
|
15
|
-
|
|
16
|
-
for (const entry of keywords) {
|
|
17
|
-
if (entry.terms && Array.isArray(entry.terms)) {
|
|
18
|
-
for (const term of entry.terms) {
|
|
19
|
-
const index = inputLower.lastIndexOf(term.toLowerCase());
|
|
20
|
-
if (index !== -1 && index >= lastMatchIndex) {
|
|
21
|
-
lastMatchIndex = index;
|
|
22
|
-
matchedInstruction = entry.instruction;
|
|
23
|
-
}
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
if (matchedInstruction) {
|
|
29
|
-
// Split instruction into lines of max 80 chars for better visibility
|
|
30
|
-
const words = matchedInstruction.split(' ');
|
|
31
|
-
let currentLine = '💡 ';
|
|
32
|
-
|
|
33
|
-
for (const word of words) {
|
|
34
|
-
if ((currentLine + word).length > 80) {
|
|
35
|
-
items.push({ name: currentLine, value: 'INFO', disabled: true });
|
|
36
|
-
currentLine = ' ' + word + ' ';
|
|
37
|
-
} else {
|
|
38
|
-
currentLine += word + ' ';
|
|
39
|
-
}
|
|
40
|
-
}
|
|
41
|
-
if (currentLine.trim()) {
|
|
42
|
-
items.push({ name: currentLine, value: 'INFO', disabled: true });
|
|
43
|
-
}
|
|
44
|
-
items.push({ name: '────────────────────────────────────────', value: 'SEP', disabled: true });
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
return items;
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
export const helpGenerator: PlopGeneratorConfig = {
|
|
51
|
-
description: 'Search for AI instructions and keywords',
|
|
52
|
-
prompts: [
|
|
53
|
-
{
|
|
54
|
-
type: 'autocomplete',
|
|
55
|
-
name: 'query',
|
|
56
|
-
message: 'Type a keyword to see associated instructions (e.g. "model", "field"):',
|
|
57
|
-
suggestOnly: true,
|
|
58
|
-
source: async (answers, input) => {
|
|
59
|
-
const choices: any[] = [];
|
|
60
|
-
|
|
61
|
-
const helpItems = getHelpItems(input);
|
|
62
|
-
if (helpItems.length > 0) {
|
|
63
|
-
choices.push(...helpItems);
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
if (!input) {
|
|
67
|
-
choices.push({ name: 'Type to search...', value: 'INFO', disabled: true });
|
|
68
|
-
choices.push({ name: 'Exit', value: EXIT_VALUE });
|
|
69
|
-
return choices;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
choices.push({ name: 'Exit', value: EXIT_VALUE });
|
|
73
|
-
return choices;
|
|
74
|
-
},
|
|
75
|
-
validate: (value) => {
|
|
76
|
-
if (value === EXIT_VALUE || value?.toLowerCase() === 'exit') {
|
|
77
|
-
// process.exit(0); // Avoiding process.exit in library code, returning true to let plop handle it
|
|
78
|
-
return true;
|
|
79
|
-
}
|
|
80
|
-
return true;
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
],
|
|
84
|
-
actions: []
|
|
85
|
-
};
|
package/generators/main/index.ts
DELETED
|
@@ -1,422 +0,0 @@
|
|
|
1
|
-
import { NodePlopAPI, PlopGeneratorConfig } from 'plop';
|
|
2
|
-
import { djangoAppGenerator } from '../django-app';
|
|
3
|
-
import { wagtailPageGenerator } from '../wagtail-page';
|
|
4
|
-
import { wagtailBlockGenerator } from '../wagtail-block';
|
|
5
|
-
import { djangoViewGenerator } from '../django-view';
|
|
6
|
-
import { djangoFormGenerator } from '../django-form';
|
|
7
|
-
import { wagtailAdminGenerator } from '../wagtail-admin';
|
|
8
|
-
import { djangoChannelGenerator } from '../django-channel';
|
|
9
|
-
import { aiEnhancerGenerator } from '../ia/ai-enhancer';
|
|
10
|
-
import keywordConfig from '../ia/ai-enhancer/keywords.json';
|
|
11
|
-
import { resolveAppPaths } from '../utils/filesystem';
|
|
12
|
-
import { analyzeAppFile, analyzeFile } from '../utils/analysis';
|
|
13
|
-
import { searchChoices } from '../utils/search';
|
|
14
|
-
import {
|
|
15
|
-
MAIN_COMPONENT_TYPES,
|
|
16
|
-
VIEW_TYPES,
|
|
17
|
-
FORM_TYPES,
|
|
18
|
-
ADMIN_EXT_TYPES,
|
|
19
|
-
CHANNEL_TYPES,
|
|
20
|
-
AI_PROVIDER_TYPES,
|
|
21
|
-
AI_TEMP_TYPES,
|
|
22
|
-
BACK_VALUE,
|
|
23
|
-
EXIT_VALUE
|
|
24
|
-
} from '../constants';
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Main Generator Configuration (Interactive CLI).
|
|
28
|
-
* Acts as the entry point for the interactive wizard.
|
|
29
|
-
* Routes user choices to specific sub-generators (App, Page, Block, View, etc.).
|
|
30
|
-
* Handles creating new apps, adding components, and modifying existing code.
|
|
31
|
-
*
|
|
32
|
-
* @type {PlopGeneratorConfig}
|
|
33
|
-
*/
|
|
34
|
-
|
|
35
|
-
const getHelpItems = (input: string | undefined): any[] => {
|
|
36
|
-
const items: any[] = [];
|
|
37
|
-
|
|
38
|
-
if (input) {
|
|
39
|
-
const inputLower = input.toLowerCase();
|
|
40
|
-
let lastMatchIndex = -1;
|
|
41
|
-
let matchedInstruction = null;
|
|
42
|
-
|
|
43
|
-
const keywords = Array.isArray(keywordConfig) ? keywordConfig : (keywordConfig as any).keywords || [];
|
|
44
|
-
|
|
45
|
-
for (const entry of keywords) {
|
|
46
|
-
if (entry.terms && Array.isArray(entry.terms)) {
|
|
47
|
-
for (const term of entry.terms) {
|
|
48
|
-
const index = inputLower.lastIndexOf(term.toLowerCase());
|
|
49
|
-
if (index !== -1 && index >= lastMatchIndex) {
|
|
50
|
-
lastMatchIndex = index;
|
|
51
|
-
matchedInstruction = entry.instruction;
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (matchedInstruction) {
|
|
58
|
-
// Split instruction into lines of max 80 chars for better visibility
|
|
59
|
-
const words = matchedInstruction.split(' ');
|
|
60
|
-
let currentLine = '💡 ';
|
|
61
|
-
|
|
62
|
-
for (const word of words) {
|
|
63
|
-
if ((currentLine + word).length > 80) {
|
|
64
|
-
items.push({ name: currentLine, value: 'INFO', disabled: true });
|
|
65
|
-
currentLine = ' ' + word + ' ';
|
|
66
|
-
} else {
|
|
67
|
-
currentLine += word + ' ';
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
if (currentLine.trim()) {
|
|
71
|
-
items.push({ name: currentLine, value: 'INFO', disabled: true });
|
|
72
|
-
}
|
|
73
|
-
items.push({ name: '────────────────────────────────────────', value: 'SEP', disabled: true });
|
|
74
|
-
}
|
|
75
|
-
}
|
|
76
|
-
return items;
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
const helpGenerator: PlopGeneratorConfig = {
|
|
80
|
-
description: 'Search for AI instructions and keywords',
|
|
81
|
-
prompts: [
|
|
82
|
-
{
|
|
83
|
-
type: 'autocomplete',
|
|
84
|
-
name: 'query',
|
|
85
|
-
message: 'Type a keyword to see associated instructions (e.g. "model", "field"):',
|
|
86
|
-
suggestOnly: true,
|
|
87
|
-
source: async (answers, input) => {
|
|
88
|
-
const choices: any[] = [];
|
|
89
|
-
|
|
90
|
-
const helpItems = getHelpItems(input);
|
|
91
|
-
if (helpItems.length > 0) {
|
|
92
|
-
choices.push(...helpItems);
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
if (!input) {
|
|
96
|
-
choices.push({ name: 'Type to search...', value: 'INFO', disabled: true });
|
|
97
|
-
choices.push({ name: 'Exit', value: EXIT_VALUE });
|
|
98
|
-
return choices;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
choices.push({ name: 'Exit', value: EXIT_VALUE });
|
|
102
|
-
return choices;
|
|
103
|
-
},
|
|
104
|
-
validate: (value) => {
|
|
105
|
-
if (value === EXIT_VALUE || value?.toLowerCase() === 'exit') {
|
|
106
|
-
return true;
|
|
107
|
-
}
|
|
108
|
-
return true;
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
],
|
|
112
|
-
actions: []
|
|
113
|
-
};
|
|
114
|
-
|
|
115
|
-
const mainGenerator: PlopGeneratorConfig = {
|
|
116
|
-
description: 'CLI Interactif pour créer ou éditer des composants Django/Wagtail',
|
|
117
|
-
prompts: [
|
|
118
|
-
// Step 1: Mode Selection
|
|
119
|
-
{
|
|
120
|
-
type: 'list',
|
|
121
|
-
name: 'mode',
|
|
122
|
-
message: 'Que voulez-vous faire ?',
|
|
123
|
-
choices: [
|
|
124
|
-
{ name: 'Créer une nouvelle application Django', value: 'create' },
|
|
125
|
-
{ name: 'Ajouter des fichiers/fonctionnalités à une application', value: 'add' },
|
|
126
|
-
{ name: 'Modifier un fichier d\'une application via IA', value: 'modify' },
|
|
127
|
-
{ name: 'Aide / Rechercher des instructions IA', value: 'help' },
|
|
128
|
-
{ name: 'Ouvrir la documentation', value: 'docs' },
|
|
129
|
-
],
|
|
130
|
-
},
|
|
131
|
-
|
|
132
|
-
// Step 2a: Create - App Name
|
|
133
|
-
{
|
|
134
|
-
type: 'input',
|
|
135
|
-
name: 'appName',
|
|
136
|
-
message: 'Nom de la nouvelle application Django :',
|
|
137
|
-
when: (answers) => answers.mode === 'create',
|
|
138
|
-
validate: (value) => (/.+/.test(value) ? true : 'Le nom de l\'application est requis'),
|
|
139
|
-
},
|
|
140
|
-
|
|
141
|
-
// Step 2b: Add/Modify - Select App
|
|
142
|
-
{
|
|
143
|
-
type: 'autocomplete',
|
|
144
|
-
name: 'selectedApp',
|
|
145
|
-
message: 'Sélectionnez l\'application cible :',
|
|
146
|
-
when: (answers) => ['add', 'modify'].includes(answers.mode),
|
|
147
|
-
source: (answers: any, input) => {
|
|
148
|
-
const apps = getAppList(answers.config);
|
|
149
|
-
if (apps.length === 0) {
|
|
150
|
-
return Promise.resolve([{ name: 'Aucune application trouvée', value: null }]);
|
|
151
|
-
}
|
|
152
|
-
const appChoices = apps.map(app => ({ name: app, value: app }));
|
|
153
|
-
return searchChoices(input, appChoices);
|
|
154
|
-
},
|
|
155
|
-
filter: (input: string, answers: any) => {
|
|
156
|
-
if (!input) return null;
|
|
157
|
-
// Only analyze default models.py if we are NOT in modify mode (or as fallback)
|
|
158
|
-
// But for 'add' mode, we might want it. For 'modify', we will select file explicitly.
|
|
159
|
-
const info = analyzeAppFile(input, answers?.config);
|
|
160
|
-
return { name: input, info };
|
|
161
|
-
}
|
|
162
|
-
},
|
|
163
|
-
|
|
164
|
-
// Step 3: Add - Component Type
|
|
165
|
-
{
|
|
166
|
-
type: 'autocomplete',
|
|
167
|
-
name: 'componentType',
|
|
168
|
-
message: 'Que voulez-vous ajouter ?',
|
|
169
|
-
when: (answers) => answers.mode === 'add' && answers.selectedApp !== null,
|
|
170
|
-
source: (answers, input) => {
|
|
171
|
-
// Filter out 'ai_enhancer' as it is covered by 'modify' mode
|
|
172
|
-
const choices = MAIN_COMPONENT_TYPES.filter(c => c.value !== 'ai_enhancer');
|
|
173
|
-
// Add Help option
|
|
174
|
-
choices.push({ name: 'Aide / Rechercher des instructions IA', value: 'help' });
|
|
175
|
-
return searchChoices(input, choices);
|
|
176
|
-
},
|
|
177
|
-
},
|
|
178
|
-
|
|
179
|
-
// --- SUB-TYPE PROMPTS (Only for 'add' mode) ---
|
|
180
|
-
|
|
181
|
-
// View Types
|
|
182
|
-
{
|
|
183
|
-
type: 'autocomplete',
|
|
184
|
-
name: 'viewType',
|
|
185
|
-
message: 'Quel type de vue voulez-vous créer ?',
|
|
186
|
-
when: (answers) => answers.mode === 'add' && answers.componentType === 'view',
|
|
187
|
-
source: (answers, input) => searchChoices(input, VIEW_TYPES),
|
|
188
|
-
},
|
|
189
|
-
|
|
190
|
-
// Form Types
|
|
191
|
-
{
|
|
192
|
-
type: 'autocomplete',
|
|
193
|
-
name: 'formType',
|
|
194
|
-
message: 'Quel type de formulaire voulez-vous créer ?',
|
|
195
|
-
when: (answers) => answers.mode === 'add' && answers.componentType === 'form',
|
|
196
|
-
source: (answers, input) => searchChoices(input, FORM_TYPES),
|
|
197
|
-
},
|
|
198
|
-
{
|
|
199
|
-
type: 'input',
|
|
200
|
-
name: 'associatedModel',
|
|
201
|
-
message: 'Nom du Modèle associé (ex: UserProfile) :',
|
|
202
|
-
when: (answers) => answers.mode === 'add' && answers.componentType === 'form' && answers.formType === 'model_form',
|
|
203
|
-
},
|
|
204
|
-
|
|
205
|
-
// Admin Extension Types
|
|
206
|
-
{
|
|
207
|
-
type: 'autocomplete',
|
|
208
|
-
name: 'adminExtType',
|
|
209
|
-
message: 'Quel type d\'extension voulez-vous créer ?',
|
|
210
|
-
when: (answers) => answers.mode === 'add' && answers.componentType === 'admin_ext',
|
|
211
|
-
source: (answers, input) => searchChoices(input, ADMIN_EXT_TYPES),
|
|
212
|
-
},
|
|
213
|
-
|
|
214
|
-
// Channel Types
|
|
215
|
-
{
|
|
216
|
-
type: 'autocomplete',
|
|
217
|
-
name: 'channelType',
|
|
218
|
-
message: 'Quel type de consumer ?',
|
|
219
|
-
when: (answers) => answers.mode === 'add' && answers.componentType === 'channel',
|
|
220
|
-
source: (answers, input) => searchChoices(input, CHANNEL_TYPES),
|
|
221
|
-
},
|
|
222
|
-
{
|
|
223
|
-
type: 'input',
|
|
224
|
-
name: 'channelRoute',
|
|
225
|
-
message: 'Préfixe de la route URL (ex: chat) :',
|
|
226
|
-
when: (answers) => answers.mode === 'add' && answers.componentType === 'channel',
|
|
227
|
-
default: 'chat',
|
|
228
|
-
},
|
|
229
|
-
|
|
230
|
-
// --- MODIFY (AI ENHANCER) PROMPTS ---
|
|
231
|
-
{
|
|
232
|
-
type: 'autocomplete',
|
|
233
|
-
name: 'selectedFile',
|
|
234
|
-
message: 'Sélectionnez le fichier à modifier :',
|
|
235
|
-
when: (answers) => answers.mode === 'modify' && answers.selectedApp !== null,
|
|
236
|
-
source: (answers: any, input) => {
|
|
237
|
-
const appName = answers.selectedApp.name || answers.selectedApp;
|
|
238
|
-
const files = getAppFiles(appName, answers.config);
|
|
239
|
-
|
|
240
|
-
const choices = [];
|
|
241
|
-
if (files.length > 0) {
|
|
242
|
-
choices.push(...files.map(f => ({ name: f, value: f })));
|
|
243
|
-
} else {
|
|
244
|
-
choices.push({ name: 'Aucun fichier modifiable trouvé', value: null });
|
|
245
|
-
}
|
|
246
|
-
|
|
247
|
-
// Add Help option
|
|
248
|
-
choices.push({ name: 'Aide / Rechercher des instructions IA', value: 'help' });
|
|
249
|
-
|
|
250
|
-
return searchChoices(input, choices);
|
|
251
|
-
},
|
|
252
|
-
},
|
|
253
|
-
{
|
|
254
|
-
type: 'autocomplete',
|
|
255
|
-
name: 'targetClass',
|
|
256
|
-
message: 'Sélectionnez la classe à modifier :',
|
|
257
|
-
when: (answers) => answers.mode === 'modify' && answers.selectedFile !== null && answers.selectedFile !== 'help',
|
|
258
|
-
source: (answers: any, input) => {
|
|
259
|
-
const appName = answers.selectedApp.name || answers.selectedApp;
|
|
260
|
-
const filePath = answers.selectedFile;
|
|
261
|
-
|
|
262
|
-
const info = analyzeFile(appName, filePath, answers.config);
|
|
263
|
-
if (!info || !info.classes || info.classes.length === 0) {
|
|
264
|
-
return Promise.resolve([{ name: 'Aucune classe trouvée (ou fichier vide)', value: null }]);
|
|
265
|
-
}
|
|
266
|
-
const classChoices = info.classes.map((cls: string) => ({ name: cls, value: cls }));
|
|
267
|
-
return searchChoices(input, classChoices);
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
{
|
|
271
|
-
type: 'input',
|
|
272
|
-
name: 'instruction',
|
|
273
|
-
message: 'Quelle modification/ajout voulez-vous générer ?',
|
|
274
|
-
when: (answers) => answers.mode === 'modify' && answers.targetClass !== null && answers.selectedFile !== 'help',
|
|
275
|
-
validate: (value) => value ? true : 'Instruction requise'
|
|
276
|
-
},
|
|
277
|
-
{
|
|
278
|
-
type: 'list',
|
|
279
|
-
name: 'aiProvider',
|
|
280
|
-
message: 'Sélectionnez le fournisseur IA :',
|
|
281
|
-
when: (answers) => answers.mode === 'modify' && answers.targetClass !== null && answers.selectedFile !== 'help' && !answers.config?.aiProvider,
|
|
282
|
-
choices: AI_PROVIDER_TYPES,
|
|
283
|
-
default: (answers: any) => answers.config?.aiProvider || 'Qwen'
|
|
284
|
-
},
|
|
285
|
-
{
|
|
286
|
-
type: 'list',
|
|
287
|
-
name: 'temperature',
|
|
288
|
-
message: 'Niveau de créativité :',
|
|
289
|
-
when: (answers) => answers.mode === 'modify' && answers.targetClass !== null && answers.selectedFile !== 'help' && answers.config?.temperature === undefined,
|
|
290
|
-
choices: AI_TEMP_TYPES,
|
|
291
|
-
default: (answers: any) => answers.config?.temperature !== undefined ? answers.config.temperature : 0.5
|
|
292
|
-
},
|
|
293
|
-
{
|
|
294
|
-
type: 'list',
|
|
295
|
-
name: 'verbosity',
|
|
296
|
-
message: 'Niveau de verbosité :',
|
|
297
|
-
when: (answers) => answers.mode === 'modify' && answers.targetClass !== null && answers.selectedFile !== 'help' && !answers.config?.verbosity,
|
|
298
|
-
choices: [
|
|
299
|
-
{ name: 'Minimal (Code seulement)', value: 'minimal' },
|
|
300
|
-
{ name: 'Standard (Code + commentaires basiques)', value: 'standard' },
|
|
301
|
-
{ name: 'Verbeux (Documentation détaillée)', value: 'verbose' }
|
|
302
|
-
],
|
|
303
|
-
default: (answers: any) => answers.config?.verbosity || 'standard'
|
|
304
|
-
},
|
|
305
|
-
{
|
|
306
|
-
type: 'input',
|
|
307
|
-
name: 'constraints',
|
|
308
|
-
message: 'Contraintes (ex: "pas de commentaires", "utiliser snake_case", "docstring en français") :',
|
|
309
|
-
when: (answers) => answers.mode === 'modify' && answers.targetClass !== null && answers.selectedFile !== 'help',
|
|
310
|
-
filter: (value) => value ? value.split(',').map((s: string) => s.trim()).filter((s: string) => s.length > 0) : []
|
|
311
|
-
},
|
|
312
|
-
|
|
313
|
-
// Step 4: Component Name (For 'add' mode)
|
|
314
|
-
{
|
|
315
|
-
type: 'input',
|
|
316
|
-
name: 'componentName',
|
|
317
|
-
message: (answers) => {
|
|
318
|
-
const type = answers.componentType || 'Composant';
|
|
319
|
-
return `Nom du ${type.charAt(0).toUpperCase() + type.slice(1)} :`;
|
|
320
|
-
},
|
|
321
|
-
when: (answers) => answers.mode === 'add' && answers.selectedApp !== null && answers.componentType !== 'help',
|
|
322
|
-
validate: (value) => (/.+/.test(value) ? true : 'Le nom est requis'),
|
|
323
|
-
},
|
|
324
|
-
|
|
325
|
-
// --- HELP PROMPT (Reused from helpGenerator) ---
|
|
326
|
-
{
|
|
327
|
-
...helpGenerator.prompts[0],
|
|
328
|
-
name: 'helpQuery', // Rename to avoid conflict if needed, though 'query' is fine
|
|
329
|
-
when: (answers) =>
|
|
330
|
-
answers.mode === 'help' ||
|
|
331
|
-
answers.componentType === 'help' ||
|
|
332
|
-
answers.selectedFile === 'help'
|
|
333
|
-
}
|
|
334
|
-
],
|
|
335
|
-
actions: (data) => {
|
|
336
|
-
let generatorActions = [];
|
|
337
|
-
|
|
338
|
-
// Check for help mode first
|
|
339
|
-
if (data.mode === 'help' || data.componentType === 'help' || data.selectedFile === 'help') {
|
|
340
|
-
// Help actions (empty usually, as interaction is in prompt)
|
|
341
|
-
return [];
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
if (data.mode === 'docs') {
|
|
345
|
-
return docsGenerator.actions;
|
|
346
|
-
}
|
|
347
|
-
|
|
348
|
-
if (data.mode === 'create') {
|
|
349
|
-
// --- 1. Create New App ---
|
|
350
|
-
data.name = data.appName;
|
|
351
|
-
const genActions = djangoAppGenerator.actions;
|
|
352
|
-
generatorActions = typeof genActions === 'function' ? genActions(data) : genActions;
|
|
353
|
-
|
|
354
|
-
} else if (data.mode === 'add' && data.selectedApp) {
|
|
355
|
-
// --- 2. Add Component to Existing App ---
|
|
356
|
-
data.app = data.selectedApp.name || data.selectedApp;
|
|
357
|
-
data.name = data.componentName;
|
|
358
|
-
|
|
359
|
-
let targetGenerator;
|
|
360
|
-
|
|
361
|
-
if (['page_pair', 'snippet', 'orderable', 'model'].includes(data.componentType)) {
|
|
362
|
-
targetGenerator = wagtailPageGenerator;
|
|
363
|
-
data.type = data.componentType;
|
|
364
|
-
} else if (data.componentType === 'block') {
|
|
365
|
-
targetGenerator = wagtailBlockGenerator;
|
|
366
|
-
} else if (data.componentType === 'view') {
|
|
367
|
-
targetGenerator = djangoViewGenerator;
|
|
368
|
-
data.type = data.viewType;
|
|
369
|
-
} else if (data.componentType === 'form') {
|
|
370
|
-
targetGenerator = djangoFormGenerator;
|
|
371
|
-
data.type = data.formType;
|
|
372
|
-
data.model = data.associatedModel;
|
|
373
|
-
} else if (data.componentType === 'admin_ext') {
|
|
374
|
-
targetGenerator = wagtailAdminGenerator;
|
|
375
|
-
data.type = data.adminExtType;
|
|
376
|
-
} else if (data.componentType === 'channel') {
|
|
377
|
-
targetGenerator = djangoChannelGenerator;
|
|
378
|
-
data.type = data.channelType;
|
|
379
|
-
data.route = data.channelRoute;
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
if (targetGenerator) {
|
|
383
|
-
const genActions = targetGenerator.actions;
|
|
384
|
-
generatorActions = typeof genActions === 'function' ? genActions(data) : genActions;
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
} else if (data.mode === 'modify' && data.selectedApp && data.selectedFile) {
|
|
388
|
-
// --- 3. Modify Existing App (AI) ---
|
|
389
|
-
data.app = data.selectedApp.name || data.selectedApp;
|
|
390
|
-
|
|
391
|
-
// Use the selected file path. analyzeFile returns relative path in 'fileName' prompt? No.
|
|
392
|
-
// getAppFiles returns relative path from app root.
|
|
393
|
-
// We need full path for the AI generator.
|
|
394
|
-
const appName = data.app;
|
|
395
|
-
const relativePath = data.selectedFile;
|
|
396
|
-
// We need 'filePath' for aiEnhancer.
|
|
397
|
-
|
|
398
|
-
const fileInfo = analyzeFile(appName, relativePath, data.config);
|
|
399
|
-
if (fileInfo) {
|
|
400
|
-
data.filePath = fileInfo.filePath;
|
|
401
|
-
} else {
|
|
402
|
-
// Fallback
|
|
403
|
-
const { appDirName } = resolveAppPaths(data.config);
|
|
404
|
-
data.filePath = `${appDirName}/${appName}/${relativePath}`;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
|
-
const targetGenerator = aiEnhancerGenerator;
|
|
408
|
-
const genActions = targetGenerator.actions;
|
|
409
|
-
generatorActions = typeof genActions === 'function' ? genActions(data) : genActions;
|
|
410
|
-
}
|
|
411
|
-
|
|
412
|
-
return generatorActions;
|
|
413
|
-
},
|
|
414
|
-
};
|
|
415
|
-
|
|
416
|
-
import { docsGenerator } from '../ia/docs';
|
|
417
|
-
|
|
418
|
-
export default function (plop: NodePlopAPI) {
|
|
419
|
-
plop.setGenerator('generator', mainGenerator);
|
|
420
|
-
plop.setGenerator('help', helpGenerator);
|
|
421
|
-
plop.setGenerator('docs', docsGenerator);
|
|
422
|
-
}
|
|
@@ -1,141 +0,0 @@
|
|
|
1
|
-
import { spawn } from 'child_process';
|
|
2
|
-
|
|
3
|
-
export interface GenerateOptions {
|
|
4
|
-
temperature?: number;
|
|
5
|
-
constraints?: string[];
|
|
6
|
-
plopContext?: Record<string, any>;
|
|
7
|
-
verbosity?: 'minimal' | 'standard' | 'verbose';
|
|
8
|
-
responseFormat?: 'text' | 'json';
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export const generateWithModel = async (
|
|
12
|
-
context: string,
|
|
13
|
-
instruction: string,
|
|
14
|
-
modelCommand: string,
|
|
15
|
-
modelDisplayName: string,
|
|
16
|
-
options: GenerateOptions = {}
|
|
17
|
-
) => {
|
|
18
|
-
const { temperature, constraints, plopContext, verbosity, responseFormat } = options;
|
|
19
|
-
|
|
20
|
-
// Determine verbosity rules
|
|
21
|
-
let verbosityRules: string[] = [];
|
|
22
|
-
if (verbosity === 'minimal') {
|
|
23
|
-
verbosityRules = [
|
|
24
|
-
"Output minimal code. No comments, no docstrings unless strictly required.",
|
|
25
|
-
"Do NOT repeat existing code or context.",
|
|
26
|
-
"Return ONLY the specific function/class/snippet requested."
|
|
27
|
-
];
|
|
28
|
-
} else if (verbosity === 'verbose') {
|
|
29
|
-
verbosityRules = [
|
|
30
|
-
"Include detailed docstrings and comments explaining complex logic.",
|
|
31
|
-
"Ensure code is fully documented."
|
|
32
|
-
];
|
|
33
|
-
} else {
|
|
34
|
-
// Standard
|
|
35
|
-
verbosityRules = [
|
|
36
|
-
"Include standard docstrings and necessary comments.",
|
|
37
|
-
"Do NOT repeat existing code unless necessary for context."
|
|
38
|
-
];
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
const isJsonFormat = responseFormat === 'json';
|
|
42
|
-
const outputRule = isJsonFormat
|
|
43
|
-
? "Output MUST be a valid JSON object. Do NOT wrap in markdown."
|
|
44
|
-
: "Output ONLY the raw Python code. Do NOT include markdown formatting (no ```python blocks).";
|
|
45
|
-
|
|
46
|
-
// Construct a structured JSON prompt
|
|
47
|
-
const promptObject = {
|
|
48
|
-
meta: {
|
|
49
|
-
task: "Code Generation",
|
|
50
|
-
language: "Python",
|
|
51
|
-
mode: "Strict",
|
|
52
|
-
description: "Generate precise, deterministic Python code based on the provided context and instruction.",
|
|
53
|
-
rules: [
|
|
54
|
-
outputRule,
|
|
55
|
-
"Do NOT include the markers in the output.",
|
|
56
|
-
"Be strictly deterministic. Avoid creativity unless explicitly requested.",
|
|
57
|
-
"Follow PEP 8 standards.",
|
|
58
|
-
...verbosityRules
|
|
59
|
-
]
|
|
60
|
-
},
|
|
61
|
-
input: {
|
|
62
|
-
instruction: instruction,
|
|
63
|
-
file_context: context,
|
|
64
|
-
generator_context: plopContext || {},
|
|
65
|
-
constraints: constraints || []
|
|
66
|
-
},
|
|
67
|
-
style: {
|
|
68
|
-
temperature: temperature ?? 0.1, // Default to strict (low temperature)
|
|
69
|
-
tone: "Technical, Precise, Concise"
|
|
70
|
-
}
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
// Override style description based on temperature if provided
|
|
74
|
-
if (temperature !== undefined) {
|
|
75
|
-
if (temperature < 0.3) promptObject.style.tone = "Strict, Deterministic, Precise";
|
|
76
|
-
else if (temperature > 0.7) promptObject.style.tone = "Creative, Exploratory";
|
|
77
|
-
else promptObject.style.tone = "Balanced, Standard";
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const promptString = JSON.stringify(promptObject, null, 2);
|
|
81
|
-
|
|
82
|
-
// Wrap the JSON in a clear instruction for the model to parse it
|
|
83
|
-
const finalPrompt = `You are a strict code generator. Parse the following JSON input and execute the task described in 'input.instruction' using the context provided.
|
|
84
|
-
${isJsonFormat ? "Output MUST be a valid JSON string with no extra text or markdown." : "Output ONLY the generated code string, with no markdown."}
|
|
85
|
-
|
|
86
|
-
JSON_INPUT:
|
|
87
|
-
${promptString}
|
|
88
|
-
`;
|
|
89
|
-
|
|
90
|
-
return new Promise((resolve, reject) => {
|
|
91
|
-
console.log(`Executing: ${modelCommand} (with JSON structured input)`);
|
|
92
|
-
|
|
93
|
-
const child = spawn(modelCommand, [], {
|
|
94
|
-
shell: true,
|
|
95
|
-
stdio: ['pipe', 'pipe', 'pipe']
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
let stdout = '';
|
|
99
|
-
let stderr = '';
|
|
100
|
-
|
|
101
|
-
child.stdout.on('data', (data) => {
|
|
102
|
-
stdout += data.toString();
|
|
103
|
-
});
|
|
104
|
-
|
|
105
|
-
child.stderr.on('data', (data) => {
|
|
106
|
-
stderr += data.toString();
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
child.on('error', (error) => {
|
|
110
|
-
console.error(`Error spawning ${modelDisplayName}:`, error);
|
|
111
|
-
reject(error);
|
|
112
|
-
});
|
|
113
|
-
|
|
114
|
-
child.on('close', (code) => {
|
|
115
|
-
if (code === 0) {
|
|
116
|
-
// Try to clean up any potential markdown if the model ignored instructions
|
|
117
|
-
let cleanOutput = stdout.trim();
|
|
118
|
-
if (cleanOutput.startsWith('```json')) {
|
|
119
|
-
cleanOutput = cleanOutput.replace(/^```json\s*/, '').replace(/\s*```$/, '');
|
|
120
|
-
} else if (cleanOutput.startsWith('```python')) {
|
|
121
|
-
cleanOutput = cleanOutput.replace(/^```python\s*/, '').replace(/\s*```$/, '');
|
|
122
|
-
} else if (cleanOutput.startsWith('```')) {
|
|
123
|
-
cleanOutput = cleanOutput.replace(/^```\s*/, '').replace(/\s*```$/, '');
|
|
124
|
-
}
|
|
125
|
-
resolve(cleanOutput);
|
|
126
|
-
} else {
|
|
127
|
-
console.error(`Error in ${modelDisplayName} generation (code ${code}):`, stderr);
|
|
128
|
-
reject(new Error(`Process exited with code ${code}: ${stderr}`));
|
|
129
|
-
}
|
|
130
|
-
});
|
|
131
|
-
|
|
132
|
-
// Write prompt to stdin and close it to signal end of input
|
|
133
|
-
try {
|
|
134
|
-
child.stdin.write(finalPrompt);
|
|
135
|
-
child.stdin.end();
|
|
136
|
-
} catch (error) {
|
|
137
|
-
console.error(`Error writing to stdin of ${modelDisplayName}:`, error);
|
|
138
|
-
reject(error);
|
|
139
|
-
}
|
|
140
|
-
});
|
|
141
|
-
};
|