|
9 | 9 | "github.com/charmbracelet/bubbles/textinput" |
10 | 10 | tea "github.com/charmbracelet/bubbletea" |
11 | 11 | "github.com/charmbracelet/lipgloss" |
| 12 | + "github.com/doplan/cli/internal/generator" |
12 | 13 | "github.com/doplan/cli/pkg/models" |
13 | 14 | ) |
14 | 15 |
|
@@ -75,6 +76,18 @@ type Model struct { |
75 | 76 | errorSuggestion string |
76 | 77 | previousState wizardState // Store previous state for recovery |
77 | 78 | stateHistory []wizardState // History for back navigation |
| 79 | + generationDone bool // Track if generation has completed |
| 80 | + generationErr error // Store generation error if any |
| 81 | +} |
| 82 | + |
| 83 | +// generationCompleteMsg is sent when generation completes |
| 84 | +type generationCompleteMsg struct { |
| 85 | + err error |
| 86 | +} |
| 87 | + |
| 88 | +// stepProgressMsg is sent when a step completes |
| 89 | +type stepProgressMsg struct { |
| 90 | + stepIndex int |
78 | 91 | } |
79 | 92 |
|
80 | 93 | // IDEInfo contains information about an IDE option |
@@ -327,7 +340,8 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { |
327 | 340 | if len(m.steps) > 0 { |
328 | 341 | m.steps[0].Status = stepInProgress |
329 | 342 | } |
330 | | - return m, tickSpinner |
| 343 | + // Start actual generation in background |
| 344 | + return m, tea.Batch(tickSpinner, startGeneration(m)) |
331 | 345 | } |
332 | 346 | } |
333 | 347 | return m, nil |
@@ -377,22 +391,64 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) { |
377 | 391 | // Handle spinner tick |
378 | 392 | if m.state == stateGenerating { |
379 | 393 | m.spinnerFrame++ |
380 | | - // Simulate progress (for demo purposes) |
381 | | - // In real implementation, this would be driven by actual generation progress |
382 | | - // After a few ticks, transition to success (simulating completion) |
383 | | - if m.spinnerFrame > 30 { |
384 | | - // Mark all steps as completed |
385 | | - for i := range m.steps { |
386 | | - m.steps[i].Status = stepCompleted |
| 394 | + // Continue spinner animation while generating |
| 395 | + if m.generationDone { |
| 396 | + // Generation completed, stop spinner |
| 397 | + if m.generationErr != nil { |
| 398 | + // Transition to error state |
| 399 | + m.state = stateError |
| 400 | + m.errorMessage = m.generationErr.Error() |
| 401 | + m.errorSuggestion = "Please check the error message above and try again." |
| 402 | + return m, nil |
| 403 | + } else { |
| 404 | + // Mark all steps as completed and transition to success |
| 405 | + for i := range m.steps { |
| 406 | + m.steps[i].Status = stepCompleted |
| 407 | + } |
| 408 | + m.state = stateSuccess |
| 409 | + return m, nil |
387 | 410 | } |
388 | | - // Transition to success state |
389 | | - m.state = stateSuccess |
390 | | - return m, nil |
391 | 411 | } |
| 412 | + // Update progress based on elapsed time (fallback if no step updates) |
| 413 | + // This provides visual feedback while generation is running |
392 | 414 | return m, tickSpinner |
393 | 415 | } |
394 | 416 | return m, nil |
395 | 417 |
|
| 418 | + case generationCompleteMsg: |
| 419 | + // Generation completed |
| 420 | + m.generationDone = true |
| 421 | + m.generationErr = msg.err |
| 422 | + if msg.err != nil { |
| 423 | + // Transition to error state |
| 424 | + m.state = stateError |
| 425 | + m.errorMessage = msg.err.Error() |
| 426 | + m.errorSuggestion = "Please check the error message above and try again." |
| 427 | + return m, nil |
| 428 | + } else { |
| 429 | + // Mark all steps as completed |
| 430 | + for i := range m.steps { |
| 431 | + m.steps[i].Status = stepCompleted |
| 432 | + } |
| 433 | + // Transition to success state |
| 434 | + m.state = stateSuccess |
| 435 | + return m, nil |
| 436 | + } |
| 437 | + |
| 438 | + case stepProgressMsg: |
| 439 | + // Update step progress |
| 440 | + if msg.stepIndex < len(m.steps) { |
| 441 | + // Mark current step as completed |
| 442 | + if msg.stepIndex > 0 { |
| 443 | + m.steps[msg.stepIndex-1].Status = stepCompleted |
| 444 | + } |
| 445 | + // Mark next step as in progress |
| 446 | + if msg.stepIndex < len(m.steps) { |
| 447 | + m.steps[msg.stepIndex].Status = stepInProgress |
| 448 | + } |
| 449 | + } |
| 450 | + return m, nil |
| 451 | + |
396 | 452 | default: |
397 | 453 | // Handle text input messages |
398 | 454 | if m.state == stateProjectName { |
@@ -1175,3 +1231,32 @@ func Run() (*models.ProjectRequest, error) { |
1175 | 1231 | // User quit or error occurred |
1176 | 1232 | return nil, nil |
1177 | 1233 | } |
| 1234 | + |
| 1235 | +// startGeneration starts the project generation in a goroutine |
| 1236 | +func startGeneration(m Model) tea.Cmd { |
| 1237 | + return func() tea.Msg { |
| 1238 | + // Create project request |
| 1239 | + request := m.toProjectRequest() |
| 1240 | + if err := request.Validate(); err != nil { |
| 1241 | + return generationCompleteMsg{err: fmt.Errorf("invalid project request: %w", err)} |
| 1242 | + } |
| 1243 | + |
| 1244 | + // Run generation in a goroutine to avoid blocking |
| 1245 | + // This allows the UI to continue updating while generation happens |
| 1246 | + errChan := make(chan error, 1) |
| 1247 | + go func() { |
| 1248 | + if err := generator.Orchestrate(request); err != nil { |
| 1249 | + errChan <- fmt.Errorf("generation failed: %w", err) |
| 1250 | + } else { |
| 1251 | + errChan <- nil |
| 1252 | + } |
| 1253 | + }() |
| 1254 | + |
| 1255 | + // Wait for generation to complete |
| 1256 | + if err := <-errChan; err != nil { |
| 1257 | + return generationCompleteMsg{err: err} |
| 1258 | + } |
| 1259 | + |
| 1260 | + return generationCompleteMsg{err: nil} |
| 1261 | + } |
| 1262 | +} |
0 commit comments