package tui import ( "fmt" "strings" tea "github.com/charmbracelet/bubbletea" "github.com/charmbracelet/lipgloss" "github.com/muyue/muyue/internal/workflow" ) func (m Model) handleWorkflowKey(msg tea.KeyMsg) (tea.Model, tea.Cmd) { if m.orch == nil || m.orch.Workflow == nil { return m, nil } wf := m.orch.Workflow switch msg.String() { case "a": if wf.Phase == workflow.PhaseReviewing { m.chatLog = append(m.chatLog, userMsgStyle.Render("you: [Plan approved]")) m.chatLoading = true m.viewport.SetContent(m.renderContent()) return m, reviewPlanCmd(m.orch, true, "") } case "r": if wf.Phase == workflow.PhaseReviewing { m.chatInput = "" m.chatLog = append(m.chatLog, itemWarnStyle.Render("Type your rejection feedback below:")) m.viewport.SetContent(m.renderContent()) } case "g": if wf.Phase == workflow.PhaseGathering && len(wf.Plan.Questions) > 0 && len(wf.Plan.Answers) >= len(wf.Plan.Questions) { m.chatLog = append(m.chatLog, userMsgStyle.Render("you: [Generate plan]")) m.chatLoading = true m.viewport.SetContent(m.renderContent()) return m, generatePlanCmd(m.orch) } case "n": if wf.Phase == workflow.PhaseExecuting { current := wf.CurrentStep() if current != nil { m.chatLoading = true m.viewport.SetContent(m.renderContent()) return m, continueWorkflowCmd(m.orch, "proceeding") } } case "x": wf.Reset() m.chatLog = append(m.chatLog, itemWarnStyle.Render("Workflow reset.")) m.viewport.SetContent(m.renderContent()) } return m, nil } func (m Model) renderWorkflow() string { var b strings.Builder if m.orch == nil || m.orch.Workflow == nil { b.WriteString("Workflow engine not available.") return b.String() } wf := m.orch.Workflow b.WriteString(sectionStyle.Render("Workflow")) b.WriteString(" ") phaseColors := map[workflow.Phase]lipgloss.Style{ workflow.PhaseIdle: lipgloss.NewStyle().Foreground(mutedColor), workflow.PhaseGathering: lipgloss.NewStyle().Foreground(warningColor).Bold(true), workflow.PhasePlanning: lipgloss.NewStyle().Foreground(accentColor).Bold(true), workflow.PhaseReviewing: lipgloss.NewStyle().Foreground(aiColor).Bold(true), workflow.PhaseExecuting: lipgloss.NewStyle().Foreground(baseColor).Bold(true), workflow.PhaseDone: lipgloss.NewStyle().Foreground(successColor).Bold(true), workflow.PhaseError: lipgloss.NewStyle().Foreground(errorColor).Bold(true), } if style, ok := phaseColors[wf.Phase]; ok { b.WriteString(style.Render(string(wf.Phase))) } b.WriteString("\n\n") if wf.Plan.Goal != "" { b.WriteString(fmt.Sprintf("Goal: %s\n\n", wf.Plan.Goal)) } switch wf.Phase { case workflow.PhaseIdle: b.WriteString("No active workflow.\n") b.WriteString("Type /plan to start a structured workflow.\n") b.WriteString("Example: /plan Create a REST API in Go\n") case workflow.PhaseGathering: b.WriteString(sectionStyle.Render("Gathering Requirements")) b.WriteString("\n") for i, q := range wf.Plan.Questions { icon := itemPendingStyle.Render(" ") if i < len(wf.Plan.Answers) { icon = itemOKStyle.Render(" ") b.WriteString(fmt.Sprintf(" %s Q: %s\n", icon, q)) b.WriteString(fmt.Sprintf(" A: %s\n", wf.Plan.Answers[i])) } else { b.WriteString(fmt.Sprintf(" %s Q: %s\n", icon, q)) } } if len(wf.Plan.Answers) >= len(wf.Plan.Questions) && len(wf.Plan.Questions) > 0 { b.WriteString("\n ") b.WriteString(itemOKStyle.Render("[g] Generate plan")) b.WriteString("\n") } case workflow.PhasePlanning: b.WriteString(m.spinner.View()) b.WriteString(" ") b.WriteString(itemWarnStyle.Render("Generating plan...")) b.WriteString("\n") case workflow.PhaseReviewing: b.WriteString(sectionStyle.Render("Plan (review before execution)")) b.WriteString("\n\n") for i, s := range wf.Plan.Steps { numStyle := lipgloss.NewStyle().Foreground(accentColor).Bold(true) icon := stepPendingStyle.Render(" ") b.WriteString(fmt.Sprintf(" %s %s %s\n", icon, numStyle.Render("#"+s.ID+":"), s.Title)) b.WriteString(fmt.Sprintf(" %s\n", lipgloss.NewStyle().Foreground(lipgloss.Color("#E0E0E0")).Render(s.Description))) agentStyle := lipgloss.NewStyle().Foreground(aiColor).Render(s.Agent) b.WriteString(fmt.Sprintf(" Agent: %s\n", agentStyle)) if i < len(wf.Plan.Steps)-1 { b.WriteString("\n") } } b.WriteString("\n ") b.WriteString(itemOKStyle.Render("[a] Approve plan")) b.WriteString(" ") b.WriteString(itemMissingStyle.Render("[r] Reject with feedback")) b.WriteString("\n") if len(wf.Plan.PreviewFiles) > 0 { b.WriteString("\n ") b.WriteString(itemWarnStyle.Render("Preview files available (opened in browser)")) b.WriteString("\n") } case workflow.PhaseExecuting: b.WriteString(sectionStyle.Render("Executing Plan")) b.WriteString("\n\n") done, total := wf.Progress() m.progressBar.SetPercent(float64(done) / float64(max(total, 1))) fmt.Fprintf(&b, " %s %d/%d\n\n", m.progressBar.View(), done, total) for _, s := range wf.Plan.Steps { var icon string switch s.Status { case "done": icon = stepDoneStyle.Render(" ") case "error": icon = stepErrorStyle.Render(" ") default: if wf.Plan.Steps[wf.Plan.StepIndex].ID == s.ID { icon = stepCurrentStyle.Render(">") } else { icon = stepPendingStyle.Render(" ") } } b.WriteString(fmt.Sprintf(" %s %s: %s\n", icon, s.ID, s.Title)) if s.Output != "" { output := s.Output if len(output) > 80 { output = output[:80] + "..." } b.WriteString(fmt.Sprintf(" %s\n", output)) } } b.WriteString("\n ") b.WriteString(itemOKStyle.Render("[n] Next step")) b.WriteString(" ") b.WriteString(itemMissingStyle.Render("[x] Cancel workflow")) b.WriteString("\n") case workflow.PhaseDone: b.WriteString(itemOKStyle.Render("Workflow completed!")) b.WriteString("\n\n") for _, s := range wf.Plan.Steps { icon := stepDoneStyle.Render(" ") b.WriteString(fmt.Sprintf(" %s %s: %s\n", icon, s.ID, s.Title)) } b.WriteString("\n [x] Reset workflow\n") case workflow.PhaseError: b.WriteString(itemMissingStyle.Render("Workflow encountered an error.")) b.WriteString("\n [x] Reset workflow\n") } b.WriteString("\n\n") b.WriteString(lipgloss.NewStyle().Foreground(dimColor).Render(strings.Repeat("─", max(m.width-4, 10)))) b.WriteString("\n") b.WriteString(sectionStyle.Render("Chat")) b.WriteString("\n") for _, msg := range m.chatLog { lines := strings.Split(msg, "\n") for _, line := range lines { if len(line) > m.width-4 { line = line[:m.width-7] + "..." } b.WriteString(" " + line + "\n") } } return b.String() }