wiggum-cli 0.9.7 → 0.10.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/dist/ai/providers.d.ts +8 -0
- package/dist/ai/providers.d.ts.map +1 -1
- package/dist/ai/providers.js +25 -1
- package/dist/ai/providers.js.map +1 -1
- package/dist/commands/init.d.ts +8 -19
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +5 -347
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/new.d.ts +21 -13
- package/dist/commands/new.d.ts.map +1 -1
- package/dist/commands/new.js +10 -267
- package/dist/commands/new.js.map +1 -1
- package/dist/generator/config.d.ts.map +1 -1
- package/dist/generator/config.js +4 -2
- package/dist/generator/config.js.map +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +92 -88
- package/dist/index.js.map +1 -1
- package/dist/repl/index.d.ts +3 -3
- package/dist/repl/index.d.ts.map +1 -1
- package/dist/repl/index.js +3 -3
- package/dist/repl/index.js.map +1 -1
- package/dist/tui/app.d.ts +1 -5
- package/dist/tui/app.d.ts.map +1 -1
- package/dist/tui/app.js +7 -12
- package/dist/tui/app.js.map +1 -1
- package/dist/tui/components/Confirm.d.ts +39 -0
- package/dist/tui/components/Confirm.d.ts.map +1 -0
- package/dist/tui/components/Confirm.js +60 -0
- package/dist/tui/components/Confirm.js.map +1 -0
- package/dist/tui/components/PasswordInput.d.ts +39 -0
- package/dist/tui/components/PasswordInput.d.ts.map +1 -0
- package/dist/tui/components/PasswordInput.js +56 -0
- package/dist/tui/components/PasswordInput.js.map +1 -0
- package/dist/tui/components/Select.d.ts +55 -0
- package/dist/tui/components/Select.d.ts.map +1 -0
- package/dist/tui/components/Select.js +63 -0
- package/dist/tui/components/Select.js.map +1 -0
- package/dist/tui/components/index.d.ts +6 -0
- package/dist/tui/components/index.d.ts.map +1 -1
- package/dist/tui/components/index.js +3 -0
- package/dist/tui/components/index.js.map +1 -1
- package/dist/tui/hooks/index.d.ts +3 -0
- package/dist/tui/hooks/index.d.ts.map +1 -1
- package/dist/tui/hooks/index.js +2 -0
- package/dist/tui/hooks/index.js.map +1 -1
- package/dist/tui/hooks/useInit.d.ts +130 -0
- package/dist/tui/hooks/useInit.d.ts.map +1 -0
- package/dist/tui/hooks/useInit.js +326 -0
- package/dist/tui/hooks/useInit.js.map +1 -0
- package/dist/tui/hooks/useSpecGenerator.d.ts +4 -0
- package/dist/tui/hooks/useSpecGenerator.d.ts.map +1 -1
- package/dist/tui/hooks/useSpecGenerator.js +12 -1
- package/dist/tui/hooks/useSpecGenerator.js.map +1 -1
- package/dist/tui/screens/InitScreen.d.ts +17 -10
- package/dist/tui/screens/InitScreen.d.ts.map +1 -1
- package/dist/tui/screens/InitScreen.js +317 -18
- package/dist/tui/screens/InitScreen.js.map +1 -1
- package/dist/tui/screens/InterviewScreen.d.ts.map +1 -1
- package/dist/tui/screens/InterviewScreen.js +2 -2
- package/dist/tui/screens/InterviewScreen.js.map +1 -1
- package/dist/tui/screens/index.d.ts +6 -0
- package/dist/tui/screens/index.d.ts.map +1 -1
- package/dist/tui/screens/index.js +3 -0
- package/dist/tui/screens/index.js.map +1 -1
- package/package.json +1 -1
- package/src/ai/providers.ts +28 -1
- package/src/commands/init.ts +8 -428
- package/src/commands/new.ts +13 -319
- package/src/generator/config.ts +4 -2
- package/src/index.ts +104 -96
- package/src/repl/index.ts +3 -3
- package/src/tui/app.tsx +7 -15
- package/src/tui/components/Confirm.tsx +109 -0
- package/src/tui/components/PasswordInput.tsx +106 -0
- package/src/tui/components/Select.tsx +132 -0
- package/src/tui/components/index.ts +9 -0
- package/src/tui/hooks/index.ts +9 -0
- package/src/tui/hooks/useInit.ts +472 -0
- package/src/tui/hooks/useSpecGenerator.ts +17 -1
- package/src/tui/screens/InitScreen.tsx +562 -29
- package/src/tui/screens/InterviewScreen.tsx +2 -1
- package/src/tui/screens/index.ts +9 -0
- package/src/cli.ts +0 -274
- package/src/repl/repl-loop.ts +0 -389
- package/src/utils/repl-prompts.ts +0 -381
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* useInit - State management hook for the init workflow
|
|
3
|
+
*
|
|
4
|
+
* Manages the initialization flow phases:
|
|
5
|
+
* 1. scanning - Scan project structure
|
|
6
|
+
* 2. provider-select - Select AI provider (if no key available)
|
|
7
|
+
* 3. key-input - Enter API key
|
|
8
|
+
* 4. key-save - Confirm saving key to .env.local
|
|
9
|
+
* 5. model-select - Select model
|
|
10
|
+
* 6. ai-analysis - Run AI analysis
|
|
11
|
+
* 7. confirm - Confirm file generation
|
|
12
|
+
* 8. generating - Generate configuration files
|
|
13
|
+
* 9. complete - Done
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import { useState, useCallback, useRef } from 'react';
|
|
17
|
+
import type { AIProvider } from '../../ai/providers.js';
|
|
18
|
+
import type { ScanResult } from '../../scanner/types.js';
|
|
19
|
+
import type { EnhancedScanResult } from '../../ai/index.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Init workflow phases
|
|
23
|
+
*/
|
|
24
|
+
export type InitPhase =
|
|
25
|
+
| 'scanning'
|
|
26
|
+
| 'provider-select'
|
|
27
|
+
| 'key-input'
|
|
28
|
+
| 'key-save'
|
|
29
|
+
| 'model-select'
|
|
30
|
+
| 'ai-analysis'
|
|
31
|
+
| 'confirm'
|
|
32
|
+
| 'generating'
|
|
33
|
+
| 'complete'
|
|
34
|
+
| 'error';
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Phase configuration for display
|
|
38
|
+
*/
|
|
39
|
+
export interface InitPhaseConfig {
|
|
40
|
+
/** Phase number for progress display */
|
|
41
|
+
number: number;
|
|
42
|
+
/** Human-readable phase name */
|
|
43
|
+
name: string;
|
|
44
|
+
/** Description of what happens in this phase */
|
|
45
|
+
description: string;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Phase configurations for display
|
|
50
|
+
*/
|
|
51
|
+
export const INIT_PHASE_CONFIGS: Record<InitPhase, InitPhaseConfig> = {
|
|
52
|
+
scanning: {
|
|
53
|
+
number: 1,
|
|
54
|
+
name: 'Scanning',
|
|
55
|
+
description: 'Analyzing project structure',
|
|
56
|
+
},
|
|
57
|
+
'provider-select': {
|
|
58
|
+
number: 2,
|
|
59
|
+
name: 'Provider',
|
|
60
|
+
description: 'Select AI provider',
|
|
61
|
+
},
|
|
62
|
+
'key-input': {
|
|
63
|
+
number: 2,
|
|
64
|
+
name: 'API Key',
|
|
65
|
+
description: 'Enter API key',
|
|
66
|
+
},
|
|
67
|
+
'key-save': {
|
|
68
|
+
number: 2,
|
|
69
|
+
name: 'Save Key',
|
|
70
|
+
description: 'Save API key to .env.local',
|
|
71
|
+
},
|
|
72
|
+
'model-select': {
|
|
73
|
+
number: 3,
|
|
74
|
+
name: 'Model',
|
|
75
|
+
description: 'Select AI model',
|
|
76
|
+
},
|
|
77
|
+
'ai-analysis': {
|
|
78
|
+
number: 4,
|
|
79
|
+
name: 'Analysis',
|
|
80
|
+
description: 'AI-powered codebase analysis',
|
|
81
|
+
},
|
|
82
|
+
confirm: {
|
|
83
|
+
number: 5,
|
|
84
|
+
name: 'Confirm',
|
|
85
|
+
description: 'Confirm file generation',
|
|
86
|
+
},
|
|
87
|
+
generating: {
|
|
88
|
+
number: 5,
|
|
89
|
+
name: 'Generating',
|
|
90
|
+
description: 'Creating configuration files',
|
|
91
|
+
},
|
|
92
|
+
complete: {
|
|
93
|
+
number: 6,
|
|
94
|
+
name: 'Complete',
|
|
95
|
+
description: 'Initialization complete',
|
|
96
|
+
},
|
|
97
|
+
error: {
|
|
98
|
+
number: 0,
|
|
99
|
+
name: 'Error',
|
|
100
|
+
description: 'An error occurred',
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
/**
|
|
105
|
+
* Total display phases for progress bar
|
|
106
|
+
*/
|
|
107
|
+
export const INIT_TOTAL_PHASES = 5;
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* State managed by the useInit hook
|
|
111
|
+
*/
|
|
112
|
+
export interface InitState {
|
|
113
|
+
/** Current phase of the init workflow */
|
|
114
|
+
phase: InitPhase;
|
|
115
|
+
/** Project root directory */
|
|
116
|
+
projectRoot: string;
|
|
117
|
+
/** Scan result from project analysis */
|
|
118
|
+
scanResult: ScanResult | null;
|
|
119
|
+
/** Enhanced scan result after AI analysis */
|
|
120
|
+
enhancedResult: EnhancedScanResult | null;
|
|
121
|
+
/** Selected AI provider */
|
|
122
|
+
provider: AIProvider | null;
|
|
123
|
+
/** Selected model */
|
|
124
|
+
model: string | null;
|
|
125
|
+
/** API key entered (not persisted in state for security) */
|
|
126
|
+
hasApiKey: boolean;
|
|
127
|
+
/** Whether API key was entered this session (needs save prompt) */
|
|
128
|
+
apiKeyEnteredThisSession: boolean;
|
|
129
|
+
/** Whether user wants to save key to .env.local */
|
|
130
|
+
saveKeyToEnv: boolean;
|
|
131
|
+
/** Whether AI analysis is in progress */
|
|
132
|
+
isWorking: boolean;
|
|
133
|
+
/** Status message for working indicator */
|
|
134
|
+
workingStatus: string;
|
|
135
|
+
/** Error message if something went wrong */
|
|
136
|
+
error: string | null;
|
|
137
|
+
/** Generated files list */
|
|
138
|
+
generatedFiles: string[];
|
|
139
|
+
/** Token usage from AI analysis */
|
|
140
|
+
tokenUsage: { inputTokens: number; outputTokens: number; totalTokens: number } | null;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
/**
|
|
144
|
+
* Initial state
|
|
145
|
+
*/
|
|
146
|
+
const initialState: InitState = {
|
|
147
|
+
phase: 'scanning',
|
|
148
|
+
projectRoot: '',
|
|
149
|
+
scanResult: null,
|
|
150
|
+
enhancedResult: null,
|
|
151
|
+
provider: null,
|
|
152
|
+
model: null,
|
|
153
|
+
hasApiKey: false,
|
|
154
|
+
apiKeyEnteredThisSession: false,
|
|
155
|
+
saveKeyToEnv: false,
|
|
156
|
+
isWorking: false,
|
|
157
|
+
workingStatus: '',
|
|
158
|
+
error: null,
|
|
159
|
+
generatedFiles: [],
|
|
160
|
+
tokenUsage: null,
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Return value from useInit hook
|
|
165
|
+
*/
|
|
166
|
+
export interface UseInitReturn {
|
|
167
|
+
/** Current state */
|
|
168
|
+
state: InitState;
|
|
169
|
+
|
|
170
|
+
/** Initialize with project root */
|
|
171
|
+
initialize: (projectRoot: string) => void;
|
|
172
|
+
|
|
173
|
+
/** Set scan result and advance to next phase */
|
|
174
|
+
setScanResult: (result: ScanResult) => void;
|
|
175
|
+
|
|
176
|
+
/** Set that an existing API key is available */
|
|
177
|
+
setExistingProvider: (provider: AIProvider) => void;
|
|
178
|
+
|
|
179
|
+
/** Set selected provider */
|
|
180
|
+
selectProvider: (provider: AIProvider) => void;
|
|
181
|
+
|
|
182
|
+
/** Set API key (marks as entered this session) */
|
|
183
|
+
setApiKey: (key: string) => void;
|
|
184
|
+
|
|
185
|
+
/** Set whether to save key to .env.local */
|
|
186
|
+
setSaveKey: (save: boolean) => void;
|
|
187
|
+
|
|
188
|
+
/** Select model and advance to AI analysis */
|
|
189
|
+
selectModel: (model: string) => void;
|
|
190
|
+
|
|
191
|
+
/** Set AI analysis progress */
|
|
192
|
+
setAiProgress: (status: string) => void;
|
|
193
|
+
|
|
194
|
+
/** Set enhanced result from AI analysis */
|
|
195
|
+
setEnhancedResult: (result: EnhancedScanResult, tokenUsage?: { inputTokens: number; outputTokens: number; totalTokens: number }) => void;
|
|
196
|
+
|
|
197
|
+
/** Set AI analysis error */
|
|
198
|
+
setAiError: (error: string) => void;
|
|
199
|
+
|
|
200
|
+
/** Confirm file generation */
|
|
201
|
+
confirmGeneration: (confirmed: boolean) => void;
|
|
202
|
+
|
|
203
|
+
/** Set generation in progress */
|
|
204
|
+
setGenerating: (status: string) => void;
|
|
205
|
+
|
|
206
|
+
/** Set generation complete */
|
|
207
|
+
setGenerationComplete: (files: string[]) => void;
|
|
208
|
+
|
|
209
|
+
/** Set error state */
|
|
210
|
+
setError: (error: string) => void;
|
|
211
|
+
|
|
212
|
+
/** Reset to initial state */
|
|
213
|
+
reset: () => void;
|
|
214
|
+
|
|
215
|
+
/** Go back to previous phase */
|
|
216
|
+
goBack: () => void;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* useInit - React hook for managing init workflow state
|
|
221
|
+
*/
|
|
222
|
+
export function useInit(): UseInitReturn {
|
|
223
|
+
const [state, setState] = useState<InitState>(initialState);
|
|
224
|
+
|
|
225
|
+
// Store API key in ref (not in state for security)
|
|
226
|
+
const apiKeyRef = useRef<string | null>(null);
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* Initialize with project root
|
|
230
|
+
*/
|
|
231
|
+
const initialize = useCallback((projectRoot: string) => {
|
|
232
|
+
setState({
|
|
233
|
+
...initialState,
|
|
234
|
+
projectRoot,
|
|
235
|
+
phase: 'scanning',
|
|
236
|
+
isWorking: true,
|
|
237
|
+
workingStatus: 'Scanning project structure...',
|
|
238
|
+
});
|
|
239
|
+
}, []);
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Set scan result and determine next phase
|
|
243
|
+
*/
|
|
244
|
+
const setScanResult = useCallback((result: ScanResult) => {
|
|
245
|
+
setState((prev) => ({
|
|
246
|
+
...prev,
|
|
247
|
+
scanResult: result,
|
|
248
|
+
isWorking: false,
|
|
249
|
+
workingStatus: '',
|
|
250
|
+
// Will be set to correct phase by setExistingProvider or continue to provider-select
|
|
251
|
+
}));
|
|
252
|
+
}, []);
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Set that an existing API key is available
|
|
256
|
+
*/
|
|
257
|
+
const setExistingProvider = useCallback((provider: AIProvider) => {
|
|
258
|
+
setState((prev) => ({
|
|
259
|
+
...prev,
|
|
260
|
+
provider,
|
|
261
|
+
hasApiKey: true,
|
|
262
|
+
apiKeyEnteredThisSession: false,
|
|
263
|
+
phase: 'model-select',
|
|
264
|
+
}));
|
|
265
|
+
}, []);
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* Set selected provider (when no existing key)
|
|
269
|
+
*/
|
|
270
|
+
const selectProvider = useCallback((provider: AIProvider) => {
|
|
271
|
+
setState((prev) => ({
|
|
272
|
+
...prev,
|
|
273
|
+
provider,
|
|
274
|
+
phase: 'key-input',
|
|
275
|
+
}));
|
|
276
|
+
}, []);
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Set API key
|
|
280
|
+
*/
|
|
281
|
+
const setApiKey = useCallback((key: string) => {
|
|
282
|
+
apiKeyRef.current = key;
|
|
283
|
+
setState((prev) => ({
|
|
284
|
+
...prev,
|
|
285
|
+
hasApiKey: true,
|
|
286
|
+
apiKeyEnteredThisSession: true,
|
|
287
|
+
phase: 'key-save',
|
|
288
|
+
}));
|
|
289
|
+
}, []);
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Set whether to save key to .env.local
|
|
293
|
+
*/
|
|
294
|
+
const setSaveKey = useCallback((save: boolean) => {
|
|
295
|
+
setState((prev) => ({
|
|
296
|
+
...prev,
|
|
297
|
+
saveKeyToEnv: save,
|
|
298
|
+
phase: 'model-select',
|
|
299
|
+
}));
|
|
300
|
+
}, []);
|
|
301
|
+
|
|
302
|
+
/**
|
|
303
|
+
* Select model and advance to AI analysis
|
|
304
|
+
*/
|
|
305
|
+
const selectModel = useCallback((model: string) => {
|
|
306
|
+
setState((prev) => ({
|
|
307
|
+
...prev,
|
|
308
|
+
model,
|
|
309
|
+
phase: 'ai-analysis',
|
|
310
|
+
isWorking: true,
|
|
311
|
+
workingStatus: 'Initializing AI analysis...',
|
|
312
|
+
}));
|
|
313
|
+
}, []);
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Set AI analysis progress
|
|
317
|
+
*/
|
|
318
|
+
const setAiProgress = useCallback((status: string) => {
|
|
319
|
+
setState((prev) => ({
|
|
320
|
+
...prev,
|
|
321
|
+
workingStatus: status,
|
|
322
|
+
}));
|
|
323
|
+
}, []);
|
|
324
|
+
|
|
325
|
+
/**
|
|
326
|
+
* Set enhanced result from AI analysis
|
|
327
|
+
*/
|
|
328
|
+
const setEnhancedResult = useCallback(
|
|
329
|
+
(result: EnhancedScanResult, tokenUsage?: { inputTokens: number; outputTokens: number; totalTokens: number }) => {
|
|
330
|
+
setState((prev) => ({
|
|
331
|
+
...prev,
|
|
332
|
+
enhancedResult: result,
|
|
333
|
+
tokenUsage: tokenUsage || null,
|
|
334
|
+
isWorking: false,
|
|
335
|
+
workingStatus: '',
|
|
336
|
+
phase: 'confirm' as const,
|
|
337
|
+
}));
|
|
338
|
+
},
|
|
339
|
+
[]
|
|
340
|
+
);
|
|
341
|
+
|
|
342
|
+
/**
|
|
343
|
+
* Set AI analysis error
|
|
344
|
+
*/
|
|
345
|
+
const setAiError = useCallback((error: string) => {
|
|
346
|
+
setState((prev) => ({
|
|
347
|
+
...prev,
|
|
348
|
+
error,
|
|
349
|
+
isWorking: false,
|
|
350
|
+
workingStatus: '',
|
|
351
|
+
// Continue to confirm even with AI error (will use non-enhanced scan result)
|
|
352
|
+
phase: 'confirm',
|
|
353
|
+
}));
|
|
354
|
+
}, []);
|
|
355
|
+
|
|
356
|
+
/**
|
|
357
|
+
* Confirm file generation
|
|
358
|
+
*/
|
|
359
|
+
const confirmGeneration = useCallback((confirmed: boolean) => {
|
|
360
|
+
if (confirmed) {
|
|
361
|
+
setState((prev) => ({
|
|
362
|
+
...prev,
|
|
363
|
+
phase: 'generating',
|
|
364
|
+
isWorking: true,
|
|
365
|
+
workingStatus: 'Generating configuration files...',
|
|
366
|
+
}));
|
|
367
|
+
} else {
|
|
368
|
+
// User cancelled
|
|
369
|
+
setState((prev) => ({
|
|
370
|
+
...prev,
|
|
371
|
+
phase: 'error',
|
|
372
|
+
error: 'Cancelled by user',
|
|
373
|
+
}));
|
|
374
|
+
}
|
|
375
|
+
}, []);
|
|
376
|
+
|
|
377
|
+
/**
|
|
378
|
+
* Set generation in progress
|
|
379
|
+
*/
|
|
380
|
+
const setGenerating = useCallback((status: string) => {
|
|
381
|
+
setState((prev) => ({
|
|
382
|
+
...prev,
|
|
383
|
+
workingStatus: status,
|
|
384
|
+
}));
|
|
385
|
+
}, []);
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Set generation complete
|
|
389
|
+
*/
|
|
390
|
+
const setGenerationComplete = useCallback((files: string[]) => {
|
|
391
|
+
setState((prev) => ({
|
|
392
|
+
...prev,
|
|
393
|
+
generatedFiles: files,
|
|
394
|
+
isWorking: false,
|
|
395
|
+
workingStatus: '',
|
|
396
|
+
phase: 'complete',
|
|
397
|
+
}));
|
|
398
|
+
}, []);
|
|
399
|
+
|
|
400
|
+
/**
|
|
401
|
+
* Set error state
|
|
402
|
+
*/
|
|
403
|
+
const setError = useCallback((error: string) => {
|
|
404
|
+
setState((prev) => ({
|
|
405
|
+
...prev,
|
|
406
|
+
error,
|
|
407
|
+
isWorking: false,
|
|
408
|
+
phase: 'error',
|
|
409
|
+
}));
|
|
410
|
+
}, []);
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* Reset to initial state
|
|
414
|
+
*/
|
|
415
|
+
const reset = useCallback(() => {
|
|
416
|
+
apiKeyRef.current = null;
|
|
417
|
+
setState(initialState);
|
|
418
|
+
}, []);
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Go back to previous phase
|
|
422
|
+
*/
|
|
423
|
+
const goBack = useCallback(() => {
|
|
424
|
+
setState((prev) => {
|
|
425
|
+
// Define phase transitions for going back
|
|
426
|
+
const backTransitions: Partial<Record<InitPhase, InitPhase>> = {
|
|
427
|
+
'provider-select': 'scanning', // Can't really go back from provider select
|
|
428
|
+
'key-input': 'provider-select',
|
|
429
|
+
'key-save': 'key-input',
|
|
430
|
+
'model-select': prev.apiKeyEnteredThisSession ? 'key-save' : 'provider-select',
|
|
431
|
+
'ai-analysis': 'model-select',
|
|
432
|
+
confirm: 'ai-analysis',
|
|
433
|
+
generating: 'confirm',
|
|
434
|
+
};
|
|
435
|
+
|
|
436
|
+
const prevPhase = backTransitions[prev.phase];
|
|
437
|
+
if (prevPhase) {
|
|
438
|
+
return { ...prev, phase: prevPhase };
|
|
439
|
+
}
|
|
440
|
+
return prev;
|
|
441
|
+
});
|
|
442
|
+
}, []);
|
|
443
|
+
|
|
444
|
+
return {
|
|
445
|
+
state,
|
|
446
|
+
initialize,
|
|
447
|
+
setScanResult,
|
|
448
|
+
setExistingProvider,
|
|
449
|
+
selectProvider,
|
|
450
|
+
setApiKey,
|
|
451
|
+
setSaveKey,
|
|
452
|
+
selectModel,
|
|
453
|
+
setAiProgress,
|
|
454
|
+
setEnhancedResult,
|
|
455
|
+
setAiError,
|
|
456
|
+
confirmGeneration,
|
|
457
|
+
setGenerating,
|
|
458
|
+
setGenerationComplete,
|
|
459
|
+
setError,
|
|
460
|
+
reset,
|
|
461
|
+
goBack,
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* Get API key from the ref (for use in the screen component)
|
|
467
|
+
* This is a workaround since we don't store API key in state
|
|
468
|
+
*/
|
|
469
|
+
export function getApiKeyFromRef(): string | null {
|
|
470
|
+
// This will be managed by the screen component directly
|
|
471
|
+
return null;
|
|
472
|
+
}
|
|
@@ -153,6 +153,11 @@ export interface UseSpecGeneratorReturn {
|
|
|
153
153
|
*/
|
|
154
154
|
addMessage: (role: 'user' | 'assistant' | 'system', content: string, toolCalls?: ToolCall[]) => void;
|
|
155
155
|
|
|
156
|
+
/**
|
|
157
|
+
* Add a streaming message (assistant) that will be updated
|
|
158
|
+
*/
|
|
159
|
+
addStreamingMessage: (initialContent?: string, toolCalls?: ToolCall[]) => string;
|
|
160
|
+
|
|
156
161
|
/**
|
|
157
162
|
* Update the streaming message content
|
|
158
163
|
*/
|
|
@@ -490,7 +495,17 @@ export function useSpecGenerator(): UseSpecGeneratorReturn {
|
|
|
490
495
|
error,
|
|
491
496
|
};
|
|
492
497
|
}
|
|
493
|
-
|
|
498
|
+
// If this message was only for tool calls and has no content, stop streaming
|
|
499
|
+
const allDone = updatedToolCalls.every(tc => tc.status !== 'running');
|
|
500
|
+
const shouldStopStreaming = msg.isStreaming && allDone && msg.content.trim() === '';
|
|
501
|
+
if (shouldStopStreaming && streamingMessageIdRef.current === msg.id) {
|
|
502
|
+
streamingMessageIdRef.current = null;
|
|
503
|
+
}
|
|
504
|
+
return {
|
|
505
|
+
...msg,
|
|
506
|
+
toolCalls: updatedToolCalls,
|
|
507
|
+
isStreaming: shouldStopStreaming ? false : msg.isStreaming,
|
|
508
|
+
};
|
|
494
509
|
}
|
|
495
510
|
return msg;
|
|
496
511
|
}),
|
|
@@ -648,6 +663,7 @@ export function useSpecGenerator(): UseSpecGeneratorReturn {
|
|
|
648
663
|
reset,
|
|
649
664
|
initialize,
|
|
650
665
|
addMessage,
|
|
666
|
+
addStreamingMessage,
|
|
651
667
|
updateStreamingMessage,
|
|
652
668
|
completeStreamingMessage,
|
|
653
669
|
setReady,
|