@agile-vibe-coding/avc 0.2.3 → 0.3.1
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/cli/agents/agent-selector.md +23 -0
- package/cli/agents/code-implementer.md +117 -0
- package/cli/agents/code-validator.md +80 -0
- package/cli/agents/context-reviewer-epic.md +101 -0
- package/cli/agents/context-reviewer-story.md +92 -0
- package/cli/agents/context-writer-epic.md +145 -0
- package/cli/agents/context-writer-story.md +111 -0
- package/cli/agents/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +318 -39
- package/cli/agents/mission-scope-generator.md +68 -4
- package/cli/agents/mission-scope-validator.md +40 -6
- package/cli/agents/project-context-extractor.md +21 -6
- package/cli/agents/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -0
- package/cli/agents/validator-documentation.json +31 -0
- package/cli/agents/validator-documentation.md +3 -1
- package/cli/api-reference-tool.js +368 -0
- package/cli/checks/catalog.json +76 -0
- package/cli/checks/code/quality.json +26 -0
- package/cli/checks/code/testing.json +14 -0
- package/cli/checks/code/traceability.json +26 -0
- package/cli/checks/cross-refs/epic.json +171 -0
- package/cli/checks/cross-refs/story.json +149 -0
- package/cli/checks/epic/api.json +114 -0
- package/cli/checks/epic/backend.json +126 -0
- package/cli/checks/epic/cloud.json +126 -0
- package/cli/checks/epic/data.json +102 -0
- package/cli/checks/epic/database.json +114 -0
- package/cli/checks/epic/developer.json +182 -0
- package/cli/checks/epic/devops.json +174 -0
- package/cli/checks/epic/frontend.json +162 -0
- package/cli/checks/epic/mobile.json +102 -0
- package/cli/checks/epic/qa.json +90 -0
- package/cli/checks/epic/security.json +184 -0
- package/cli/checks/epic/solution-architect.json +192 -0
- package/cli/checks/epic/test-architect.json +90 -0
- package/cli/checks/epic/ui.json +102 -0
- package/cli/checks/epic/ux.json +90 -0
- package/cli/checks/fixes/epic-fix-template.md +10 -0
- package/cli/checks/fixes/story-fix-template.md +10 -0
- package/cli/checks/story/api.json +186 -0
- package/cli/checks/story/backend.json +102 -0
- package/cli/checks/story/cloud.json +102 -0
- package/cli/checks/story/data.json +210 -0
- package/cli/checks/story/database.json +102 -0
- package/cli/checks/story/developer.json +168 -0
- package/cli/checks/story/devops.json +102 -0
- package/cli/checks/story/frontend.json +174 -0
- package/cli/checks/story/mobile.json +102 -0
- package/cli/checks/story/qa.json +210 -0
- package/cli/checks/story/security.json +198 -0
- package/cli/checks/story/solution-architect.json +230 -0
- package/cli/checks/story/test-architect.json +210 -0
- package/cli/checks/story/ui.json +102 -0
- package/cli/checks/story/ux.json +102 -0
- package/cli/coding-order.js +401 -0
- package/cli/dependency-checker.js +72 -0
- package/cli/epic-story-validator.js +284 -799
- package/cli/index.js +0 -0
- package/cli/init-model-config.js +17 -10
- package/cli/init.js +514 -92
- package/cli/kanban-server-manager.js +1 -2
- package/cli/llm-claude.js +98 -31
- package/cli/llm-gemini.js +29 -5
- package/cli/llm-local.js +493 -0
- package/cli/llm-openai.js +262 -41
- package/cli/llm-provider.js +147 -8
- package/cli/llm-token-limits.js +113 -4
- package/cli/llm-verifier.js +209 -1
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +3 -12
- package/cli/messaging-api.js +6 -12
- package/cli/micro-check-fixer.js +335 -0
- package/cli/micro-check-runner.js +449 -0
- package/cli/micro-check-scorer.js +148 -0
- package/cli/micro-check-validator.js +538 -0
- package/cli/model-pricing.js +23 -0
- package/cli/model-selector.js +3 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +106 -346
- package/cli/repl-old.js +1 -2
- package/cli/seed-processor.js +194 -24
- package/cli/sprint-planning-processor.js +2638 -289
- package/cli/template-processor.js +50 -3
- package/cli/token-tracker.js +50 -23
- package/cli/tools/generate-story-validators.js +1 -1
- package/cli/validation-router.js +70 -8
- package/cli/worktree-runner.js +654 -0
- package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
- package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
- package/kanban/client/dist/index.html +2 -2
- package/kanban/client/src/App.jsx +43 -14
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +7 -3
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +23 -10
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +320 -133
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +80 -13
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +156 -22
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +11 -11
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +3 -21
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +214 -10
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +23 -2
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +97 -10
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +7 -1
- package/kanban/client/src/components/kanban/KanbanCard.jsx +23 -14
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +9 -14
- package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
- package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +103 -75
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +31 -2
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +9 -2
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +3 -2
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +72 -7
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +4 -4
- package/kanban/client/src/components/stats/CostModal.jsx +34 -3
- package/kanban/client/src/hooks/useGrouping.js +59 -0
- package/kanban/client/src/lib/api.js +118 -4
- package/kanban/client/src/lib/status-grouping.js +10 -0
- package/kanban/client/src/store/kanbanStore.js +8 -0
- package/kanban/server/index.js +23 -2
- package/kanban/server/routes/ceremony.js +153 -4
- package/kanban/server/routes/costs.js +9 -3
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/settings.js +447 -14
- package/kanban/server/routes/websocket.js +7 -2
- package/kanban/server/routes/work-items.js +141 -1
- package/kanban/server/services/CeremonyService.js +275 -24
- package/kanban/server/services/TaskRunnerService.js +261 -0
- package/kanban/server/workers/run-task-worker.js +121 -0
- package/kanban/server/workers/seed-worker.js +94 -0
- package/kanban/server/workers/sponsor-call-worker.js +14 -6
- package/kanban/server/workers/sprint-planning-worker.js +94 -12
- package/package.json +2 -3
- package/cli/agents/solver-epic-api.json +0 -15
- package/cli/agents/solver-epic-api.md +0 -39
- package/cli/agents/solver-epic-backend.json +0 -15
- package/cli/agents/solver-epic-backend.md +0 -39
- package/cli/agents/solver-epic-cloud.json +0 -15
- package/cli/agents/solver-epic-cloud.md +0 -39
- package/cli/agents/solver-epic-data.json +0 -15
- package/cli/agents/solver-epic-data.md +0 -39
- package/cli/agents/solver-epic-database.json +0 -15
- package/cli/agents/solver-epic-database.md +0 -39
- package/cli/agents/solver-epic-developer.json +0 -15
- package/cli/agents/solver-epic-developer.md +0 -39
- package/cli/agents/solver-epic-devops.json +0 -15
- package/cli/agents/solver-epic-devops.md +0 -39
- package/cli/agents/solver-epic-frontend.json +0 -15
- package/cli/agents/solver-epic-frontend.md +0 -39
- package/cli/agents/solver-epic-mobile.json +0 -15
- package/cli/agents/solver-epic-mobile.md +0 -39
- package/cli/agents/solver-epic-qa.json +0 -15
- package/cli/agents/solver-epic-qa.md +0 -39
- package/cli/agents/solver-epic-security.json +0 -15
- package/cli/agents/solver-epic-security.md +0 -39
- package/cli/agents/solver-epic-solution-architect.json +0 -15
- package/cli/agents/solver-epic-solution-architect.md +0 -39
- package/cli/agents/solver-epic-test-architect.json +0 -15
- package/cli/agents/solver-epic-test-architect.md +0 -39
- package/cli/agents/solver-epic-ui.json +0 -15
- package/cli/agents/solver-epic-ui.md +0 -39
- package/cli/agents/solver-epic-ux.json +0 -15
- package/cli/agents/solver-epic-ux.md +0 -39
- package/cli/agents/solver-story-api.json +0 -15
- package/cli/agents/solver-story-api.md +0 -39
- package/cli/agents/solver-story-backend.json +0 -15
- package/cli/agents/solver-story-backend.md +0 -39
- package/cli/agents/solver-story-cloud.json +0 -15
- package/cli/agents/solver-story-cloud.md +0 -39
- package/cli/agents/solver-story-data.json +0 -15
- package/cli/agents/solver-story-data.md +0 -39
- package/cli/agents/solver-story-database.json +0 -15
- package/cli/agents/solver-story-database.md +0 -39
- package/cli/agents/solver-story-developer.json +0 -15
- package/cli/agents/solver-story-developer.md +0 -39
- package/cli/agents/solver-story-devops.json +0 -15
- package/cli/agents/solver-story-devops.md +0 -39
- package/cli/agents/solver-story-frontend.json +0 -15
- package/cli/agents/solver-story-frontend.md +0 -39
- package/cli/agents/solver-story-mobile.json +0 -15
- package/cli/agents/solver-story-mobile.md +0 -39
- package/cli/agents/solver-story-qa.json +0 -15
- package/cli/agents/solver-story-qa.md +0 -39
- package/cli/agents/solver-story-security.json +0 -15
- package/cli/agents/solver-story-security.md +0 -39
- package/cli/agents/solver-story-solution-architect.json +0 -15
- package/cli/agents/solver-story-solution-architect.md +0 -39
- package/cli/agents/solver-story-test-architect.json +0 -15
- package/cli/agents/solver-story-test-architect.md +0 -39
- package/cli/agents/solver-story-ui.json +0 -15
- package/cli/agents/solver-story-ui.md +0 -39
- package/cli/agents/solver-story-ux.json +0 -15
- package/cli/agents/solver-story-ux.md +0 -39
- package/cli/agents/validator-epic-api.json +0 -93
- package/cli/agents/validator-epic-api.md +0 -137
- package/cli/agents/validator-epic-backend.json +0 -93
- package/cli/agents/validator-epic-backend.md +0 -130
- package/cli/agents/validator-epic-cloud.json +0 -93
- package/cli/agents/validator-epic-cloud.md +0 -137
- package/cli/agents/validator-epic-data.json +0 -93
- package/cli/agents/validator-epic-data.md +0 -130
- package/cli/agents/validator-epic-database.json +0 -93
- package/cli/agents/validator-epic-database.md +0 -137
- package/cli/agents/validator-epic-developer.json +0 -74
- package/cli/agents/validator-epic-developer.md +0 -153
- package/cli/agents/validator-epic-devops.json +0 -74
- package/cli/agents/validator-epic-devops.md +0 -153
- package/cli/agents/validator-epic-frontend.json +0 -74
- package/cli/agents/validator-epic-frontend.md +0 -153
- package/cli/agents/validator-epic-mobile.json +0 -93
- package/cli/agents/validator-epic-mobile.md +0 -130
- package/cli/agents/validator-epic-qa.json +0 -93
- package/cli/agents/validator-epic-qa.md +0 -130
- package/cli/agents/validator-epic-security.json +0 -74
- package/cli/agents/validator-epic-security.md +0 -154
- package/cli/agents/validator-epic-solution-architect.json +0 -74
- package/cli/agents/validator-epic-solution-architect.md +0 -156
- package/cli/agents/validator-epic-test-architect.json +0 -93
- package/cli/agents/validator-epic-test-architect.md +0 -130
- package/cli/agents/validator-epic-ui.json +0 -93
- package/cli/agents/validator-epic-ui.md +0 -130
- package/cli/agents/validator-epic-ux.json +0 -93
- package/cli/agents/validator-epic-ux.md +0 -130
- package/cli/agents/validator-story-api.json +0 -104
- package/cli/agents/validator-story-api.md +0 -152
- package/cli/agents/validator-story-backend.json +0 -104
- package/cli/agents/validator-story-backend.md +0 -152
- package/cli/agents/validator-story-cloud.json +0 -104
- package/cli/agents/validator-story-cloud.md +0 -152
- package/cli/agents/validator-story-data.json +0 -104
- package/cli/agents/validator-story-data.md +0 -152
- package/cli/agents/validator-story-database.json +0 -104
- package/cli/agents/validator-story-database.md +0 -152
- package/cli/agents/validator-story-developer.json +0 -104
- package/cli/agents/validator-story-developer.md +0 -152
- package/cli/agents/validator-story-devops.json +0 -104
- package/cli/agents/validator-story-devops.md +0 -152
- package/cli/agents/validator-story-frontend.json +0 -104
- package/cli/agents/validator-story-frontend.md +0 -152
- package/cli/agents/validator-story-mobile.json +0 -104
- package/cli/agents/validator-story-mobile.md +0 -152
- package/cli/agents/validator-story-qa.json +0 -104
- package/cli/agents/validator-story-qa.md +0 -152
- package/cli/agents/validator-story-security.json +0 -104
- package/cli/agents/validator-story-security.md +0 -152
- package/cli/agents/validator-story-solution-architect.json +0 -104
- package/cli/agents/validator-story-solution-architect.md +0 -152
- package/cli/agents/validator-story-test-architect.json +0 -104
- package/cli/agents/validator-story-test-architect.md +0 -152
- package/cli/agents/validator-story-ui.json +0 -104
- package/cli/agents/validator-story-ui.md +0 -152
- package/cli/agents/validator-story-ux.json +0 -104
- package/cli/agents/validator-story-ux.md +0 -152
- package/kanban/client/dist/assets/index-CiD8PS2e.js +0 -306
- package/kanban/client/dist/assets/index-nLh0m82Q.css +0 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { useEffect, useRef, useState } from 'react';
|
|
2
|
-
import { X,
|
|
2
|
+
import { X, Settings, ArrowDownToLine } from 'lucide-react';
|
|
3
3
|
import { useSprintPlanningStore } from '../../store/sprintPlanningStore';
|
|
4
|
-
import { runSprintPlanning, getSettings, getModels, saveCeremonies, pauseCeremony, resumeCeremony, cancelCeremony, resetCeremony } from '../../lib/api';
|
|
4
|
+
import { runSprintPlanning, getSprintPlanningResumable, getSettings, getModels, saveCeremonies, pauseCeremony, resumeCeremony, cancelCeremony, resetCeremony } from '../../lib/api';
|
|
5
5
|
import { CeremonyWorkflowModal } from './CeremonyWorkflowModal';
|
|
6
6
|
|
|
7
7
|
// ── Step progress header ─────────────────────────────────────────────────────
|
|
@@ -55,7 +55,7 @@ function Stat({ label, value }) {
|
|
|
55
55
|
|
|
56
56
|
// ── Step 1: Ready ─────────────────────────────────────────────────────────────
|
|
57
57
|
|
|
58
|
-
function ReadyStep({ onStart }) {
|
|
58
|
+
function ReadyStep({ onStart, onResume, resumeInfo }) {
|
|
59
59
|
return (
|
|
60
60
|
<div className="space-y-6">
|
|
61
61
|
<div>
|
|
@@ -65,12 +65,39 @@ function ReadyStep({ onStart }) {
|
|
|
65
65
|
</p>
|
|
66
66
|
</div>
|
|
67
67
|
|
|
68
|
-
|
|
68
|
+
{resumeInfo && (
|
|
69
|
+
<div className="bg-amber-50 border border-amber-200 rounded-lg p-4 space-y-2">
|
|
70
|
+
<p className="text-sm font-medium text-amber-800">Previous run can be resumed</p>
|
|
71
|
+
<p className="text-xs text-amber-600">
|
|
72
|
+
Stopped at: <span className="font-medium">{resumeInfo.checkpointLabel}</span>
|
|
73
|
+
{resumeInfo.epics > 0 && <> · {resumeInfo.epics} epics on disk</>}
|
|
74
|
+
</p>
|
|
75
|
+
{resumeInfo.timestamp && (
|
|
76
|
+
<p className="text-xs text-amber-500">
|
|
77
|
+
{new Date(resumeInfo.timestamp).toLocaleString()}
|
|
78
|
+
</p>
|
|
79
|
+
)}
|
|
80
|
+
</div>
|
|
81
|
+
)}
|
|
82
|
+
|
|
83
|
+
<div className="flex items-center justify-end gap-3 pt-2">
|
|
84
|
+
{resumeInfo && (
|
|
85
|
+
<button
|
|
86
|
+
onClick={onResume}
|
|
87
|
+
className="px-5 py-2 text-sm font-medium bg-amber-600 text-white rounded-lg hover:bg-amber-500 transition-colors"
|
|
88
|
+
>
|
|
89
|
+
Resume
|
|
90
|
+
</button>
|
|
91
|
+
)}
|
|
69
92
|
<button
|
|
70
93
|
onClick={onStart}
|
|
71
|
-
className=
|
|
94
|
+
className={`px-5 py-2 text-sm font-medium rounded-lg transition-colors ${
|
|
95
|
+
resumeInfo
|
|
96
|
+
? 'border border-slate-200 text-slate-700 hover:bg-slate-50'
|
|
97
|
+
: 'bg-slate-900 text-white hover:bg-slate-700'
|
|
98
|
+
}`}
|
|
72
99
|
>
|
|
73
|
-
Start
|
|
100
|
+
{resumeInfo ? 'Start Fresh' : 'Start'}
|
|
74
101
|
</button>
|
|
75
102
|
</div>
|
|
76
103
|
</div>
|
|
@@ -454,9 +481,74 @@ function CompleteStep({ onClose }) {
|
|
|
454
481
|
);
|
|
455
482
|
}
|
|
456
483
|
|
|
484
|
+
// ── Quota-limit pause overlay ─────────────────────────────────────────────────
|
|
485
|
+
|
|
486
|
+
function QuotaLimitOverlay({ quotaLimitPending, onContinueAfterQuota, onCancel, onConfigureModels }) {
|
|
487
|
+
const [resuming, setResuming] = useState(false);
|
|
488
|
+
|
|
489
|
+
// Resume reads the current settings so any model change made via Configure Models is picked up.
|
|
490
|
+
async function handleResume() {
|
|
491
|
+
setResuming(true);
|
|
492
|
+
try {
|
|
493
|
+
const s = await getSettings();
|
|
494
|
+
const ceremony = s.ceremonies?.find(c => c.name === 'sprint-planning');
|
|
495
|
+
const newProvider = ceremony?.stages?.validation?.provider || null;
|
|
496
|
+
const newModel = ceremony?.stages?.validation?.model || null;
|
|
497
|
+
await onContinueAfterQuota(newProvider, newModel);
|
|
498
|
+
} finally {
|
|
499
|
+
setResuming(false);
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
return (
|
|
504
|
+
<div className="absolute inset-0 z-20 flex items-center justify-center bg-white/90 rounded-2xl">
|
|
505
|
+
<div className="bg-white border border-red-200 rounded-xl shadow-lg p-6 max-w-sm mx-4 text-center space-y-4">
|
|
506
|
+
<div className="text-3xl">⚠️</div>
|
|
507
|
+
<p className="text-base font-semibold text-slate-900">API Quota Exceeded</p>
|
|
508
|
+
<p className="text-sm text-slate-600">
|
|
509
|
+
<span className="font-mono font-medium">{quotaLimitPending.provider}</span>
|
|
510
|
+
{' / '}
|
|
511
|
+
<span className="font-mono text-xs">{quotaLimitPending.model}</span>
|
|
512
|
+
{' returned a quota error.'}
|
|
513
|
+
</p>
|
|
514
|
+
{quotaLimitPending.validatorName && (
|
|
515
|
+
<p className="text-xs text-slate-400">
|
|
516
|
+
Validator: {quotaLimitPending.validatorName.replace('validator-story-', '').replace('validator-epic-', '')}
|
|
517
|
+
</p>
|
|
518
|
+
)}
|
|
519
|
+
<p className="text-sm text-slate-500">The ceremony is paused. What would you like to do?</p>
|
|
520
|
+
<div className="flex flex-col gap-2 pt-1">
|
|
521
|
+
<button
|
|
522
|
+
onClick={handleResume}
|
|
523
|
+
disabled={resuming}
|
|
524
|
+
className="px-4 py-2 text-sm rounded-lg bg-slate-900 text-white hover:bg-slate-700 disabled:opacity-50"
|
|
525
|
+
>
|
|
526
|
+
{resuming ? 'Resuming…' : 'Resume'}
|
|
527
|
+
</button>
|
|
528
|
+
<button
|
|
529
|
+
onClick={onConfigureModels}
|
|
530
|
+
className="px-4 py-2 text-sm rounded-lg border border-slate-200 text-slate-700 hover:bg-slate-50"
|
|
531
|
+
>
|
|
532
|
+
Configure Models
|
|
533
|
+
</button>
|
|
534
|
+
<button
|
|
535
|
+
onClick={onCancel}
|
|
536
|
+
className="px-4 py-2 text-sm rounded-lg border border-red-200 text-red-600 hover:bg-red-50"
|
|
537
|
+
>
|
|
538
|
+
Cancel Ceremony
|
|
539
|
+
</button>
|
|
540
|
+
</div>
|
|
541
|
+
<p className="text-xs text-slate-400">
|
|
542
|
+
Use <strong>Configure Models</strong> to switch provider, then <strong>Resume</strong>.
|
|
543
|
+
</p>
|
|
544
|
+
</div>
|
|
545
|
+
</div>
|
|
546
|
+
);
|
|
547
|
+
}
|
|
548
|
+
|
|
457
549
|
// ── Main modal ────────────────────────────────────────────────────────────────
|
|
458
550
|
|
|
459
|
-
export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastCostLimit, onCancelFromCostLimit }) {
|
|
551
|
+
export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastCostLimit, onCancelFromCostLimit, quotaLimitPending, onContinueAfterQuota, onCancelFromQuota }) {
|
|
460
552
|
const {
|
|
461
553
|
isOpen,
|
|
462
554
|
step,
|
|
@@ -473,8 +565,19 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
|
|
|
473
565
|
const [workflowCeremony, setWorkflowCeremony] = useState(null);
|
|
474
566
|
const [workflowModels, setWorkflowModels] = useState([]);
|
|
475
567
|
const [workflowAllCeremonies, setWorkflowAllCeremonies] = useState([]);
|
|
568
|
+
const [workflowApiKeys, setWorkflowApiKeys] = useState({});
|
|
476
569
|
const [showCancelConfirm, setShowCancelConfirm] = useState(false);
|
|
477
570
|
const [transitioning, setTransitioning] = useState(null); // null | 'pausing' | 'cancelling'
|
|
571
|
+
const [resumeInfo, setResumeInfo] = useState(null);
|
|
572
|
+
|
|
573
|
+
// Fetch resumable state when modal opens at step 1
|
|
574
|
+
useEffect(() => {
|
|
575
|
+
if (isOpen && step === 1 && status !== 'running') {
|
|
576
|
+
getSprintPlanningResumable()
|
|
577
|
+
.then(data => setResumeInfo(data?.resumable ? data : null))
|
|
578
|
+
.catch(() => setResumeInfo(null));
|
|
579
|
+
}
|
|
580
|
+
}, [isOpen, step, status]);
|
|
478
581
|
|
|
479
582
|
if (!isOpen) return null;
|
|
480
583
|
|
|
@@ -486,11 +589,12 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
|
|
|
486
589
|
onClose?.();
|
|
487
590
|
};
|
|
488
591
|
|
|
489
|
-
const handleStart = async () => {
|
|
592
|
+
const handleStart = async (resumeFromCheckpoint = null) => {
|
|
490
593
|
setStatus('running');
|
|
491
594
|
setStep(2);
|
|
595
|
+
setResumeInfo(null);
|
|
492
596
|
try {
|
|
493
|
-
const result = await runSprintPlanning();
|
|
597
|
+
const result = await runSprintPlanning(resumeFromCheckpoint);
|
|
494
598
|
if (result?.processId) setProcessId(result.processId);
|
|
495
599
|
// Completion is handled via WebSocket in App.jsx
|
|
496
600
|
} catch (err) {
|
|
@@ -506,6 +610,7 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
|
|
|
506
610
|
setWorkflowCeremony(sc);
|
|
507
611
|
setWorkflowModels(m);
|
|
508
612
|
setWorkflowAllCeremonies(s.ceremonies || []);
|
|
613
|
+
setWorkflowApiKeys(s.apiKeys || {});
|
|
509
614
|
setWorkflowOpen(true);
|
|
510
615
|
} catch {}
|
|
511
616
|
};
|
|
@@ -527,10 +632,10 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
|
|
|
527
632
|
try { await resumeCeremony(); } catch (_) {}
|
|
528
633
|
};
|
|
529
634
|
|
|
530
|
-
const handleConfirmCancel = async () => {
|
|
635
|
+
const handleConfirmCancel = async (keepItems = false) => {
|
|
531
636
|
setShowCancelConfirm(false);
|
|
532
637
|
setTransitioning('cancelling');
|
|
533
|
-
try { await cancelCeremony(); } catch (_) {}
|
|
638
|
+
try { await cancelCeremony(keepItems); } catch (_) {}
|
|
534
639
|
};
|
|
535
640
|
|
|
536
641
|
// Clear transitioning state when WS events arrive (isPaused / status change)
|
|
@@ -544,7 +649,13 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
|
|
|
544
649
|
|
|
545
650
|
const renderStep = () => {
|
|
546
651
|
switch (step) {
|
|
547
|
-
case 1: return
|
|
652
|
+
case 1: return (
|
|
653
|
+
<ReadyStep
|
|
654
|
+
onStart={() => handleStart(null)}
|
|
655
|
+
onResume={() => handleStart(resumeInfo?.checkpoint)}
|
|
656
|
+
resumeInfo={resumeInfo}
|
|
657
|
+
/>
|
|
658
|
+
);
|
|
548
659
|
case 2: return (
|
|
549
660
|
<RunningStep
|
|
550
661
|
transitioning={transitioning}
|
|
@@ -611,15 +722,25 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
|
|
|
611
722
|
</div>
|
|
612
723
|
)}
|
|
613
724
|
|
|
725
|
+
{/* Quota-limit pause overlay */}
|
|
726
|
+
{quotaLimitPending && (
|
|
727
|
+
<QuotaLimitOverlay
|
|
728
|
+
quotaLimitPending={quotaLimitPending}
|
|
729
|
+
onContinueAfterQuota={onContinueAfterQuota}
|
|
730
|
+
onCancel={onCancelFromQuota}
|
|
731
|
+
onConfigureModels={openWorkflow}
|
|
732
|
+
/>
|
|
733
|
+
)}
|
|
734
|
+
|
|
614
735
|
{/* Cancel confirmation overlay */}
|
|
615
736
|
{showCancelConfirm && (
|
|
616
737
|
<div className="absolute inset-0 z-10 flex items-center justify-center bg-white/90 rounded-2xl">
|
|
617
738
|
<div className="bg-white border border-slate-200 rounded-xl shadow-lg p-6 max-w-sm mx-4 text-center space-y-4">
|
|
618
739
|
<p className="text-base font-semibold text-slate-900">Cancel sprint planning?</p>
|
|
619
740
|
<p className="text-sm text-slate-500">
|
|
620
|
-
|
|
741
|
+
What should happen with epics and stories already created in this run?
|
|
621
742
|
</p>
|
|
622
|
-
<div className="flex
|
|
743
|
+
<div className="flex flex-col gap-2 pt-1">
|
|
623
744
|
<button
|
|
624
745
|
onClick={() => setShowCancelConfirm(false)}
|
|
625
746
|
className="px-4 py-2 text-sm rounded-lg border border-slate-200 text-slate-700 hover:bg-slate-50"
|
|
@@ -627,10 +748,16 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
|
|
|
627
748
|
Keep Running
|
|
628
749
|
</button>
|
|
629
750
|
<button
|
|
630
|
-
onClick={handleConfirmCancel}
|
|
751
|
+
onClick={() => handleConfirmCancel(true)}
|
|
752
|
+
className="px-4 py-2 text-sm rounded-lg border border-amber-300 bg-amber-50 text-amber-700 hover:bg-amber-100"
|
|
753
|
+
>
|
|
754
|
+
Cancel & Keep Items
|
|
755
|
+
</button>
|
|
756
|
+
<button
|
|
757
|
+
onClick={() => handleConfirmCancel(false)}
|
|
631
758
|
className="px-4 py-2 text-sm rounded-lg bg-red-600 text-white hover:bg-red-700"
|
|
632
759
|
>
|
|
633
|
-
Cancel
|
|
760
|
+
Cancel & Delete Items
|
|
634
761
|
</button>
|
|
635
762
|
</div>
|
|
636
763
|
</div>
|
|
@@ -650,11 +777,11 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
|
|
|
650
777
|
<button
|
|
651
778
|
type="button"
|
|
652
779
|
onClick={openWorkflow}
|
|
653
|
-
className="flex items-center gap-1 text-xs text-slate-
|
|
654
|
-
title="
|
|
780
|
+
className="flex items-center gap-1.5 text-xs font-medium text-slate-500 hover:text-blue-600 bg-slate-50 hover:bg-blue-50 border border-slate-200 hover:border-blue-200 rounded-md px-2.5 py-1.5 transition-colors whitespace-nowrap"
|
|
781
|
+
title="Configure ceremony models"
|
|
655
782
|
>
|
|
656
|
-
<
|
|
657
|
-
|
|
783
|
+
<Settings className="w-3.5 h-3.5" />
|
|
784
|
+
Select Model(s)
|
|
658
785
|
</button>
|
|
659
786
|
)}
|
|
660
787
|
{!isBlocked && (
|
|
@@ -693,10 +820,17 @@ export function SprintPlanningModal({ onClose, costLimitPending, onContinuePastC
|
|
|
693
820
|
{workflowOpen && workflowCeremony && (
|
|
694
821
|
<CeremonyWorkflowModal
|
|
695
822
|
ceremony={workflowCeremony}
|
|
823
|
+
allCeremonies={workflowAllCeremonies}
|
|
824
|
+
apiKeys={workflowApiKeys}
|
|
696
825
|
models={workflowModels}
|
|
697
|
-
readOnly={status === 'running'}
|
|
698
|
-
onSave={status !== 'running' ? handleWorkflowSave : undefined}
|
|
826
|
+
readOnly={status === 'running' && !quotaLimitPending}
|
|
827
|
+
onSave={(status !== 'running' || quotaLimitPending) ? handleWorkflowSave : undefined}
|
|
699
828
|
onClose={() => setWorkflowOpen(false)}
|
|
829
|
+
onCeremoniesUpdated={(updated) => {
|
|
830
|
+
setWorkflowAllCeremonies(updated);
|
|
831
|
+
const sc = updated.find((c) => c.name === 'sprint-planning');
|
|
832
|
+
if (sc) setWorkflowCeremony(sc);
|
|
833
|
+
}}
|
|
700
834
|
/>
|
|
701
835
|
)}
|
|
702
836
|
</div>
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { useState } from 'react';
|
|
2
|
-
import { Wand2 } from 'lucide-react';
|
|
3
2
|
import { useCeremonyStore } from '../../../store/ceremonyStore';
|
|
4
3
|
import { AskArchPopup } from '../AskArchPopup';
|
|
5
4
|
|
|
@@ -73,7 +72,17 @@ export function ArchitectureStep({ onNext, onBack, analyzing, onOpenSettings })
|
|
|
73
72
|
return (
|
|
74
73
|
<div className="space-y-6">
|
|
75
74
|
<div>
|
|
76
|
-
<
|
|
75
|
+
<div className="flex items-center justify-between">
|
|
76
|
+
<h2 className="text-xl font-semibold text-slate-900">Architecture Selection</h2>
|
|
77
|
+
<button
|
|
78
|
+
type="button"
|
|
79
|
+
onClick={() => setShowAskArch(true)}
|
|
80
|
+
disabled={analyzing}
|
|
81
|
+
className="text-xs text-blue-600 hover:text-blue-700 flex items-center gap-1 transition-colors disabled:opacity-40"
|
|
82
|
+
>
|
|
83
|
+
✨ Ask a Model
|
|
84
|
+
</button>
|
|
85
|
+
</div>
|
|
77
86
|
<p className="text-sm text-slate-500 mt-1">
|
|
78
87
|
Choose the deployment architecture that fits your project best.
|
|
79
88
|
</p>
|
|
@@ -100,15 +109,6 @@ export function ArchitectureStep({ onNext, onBack, analyzing, onOpenSettings })
|
|
|
100
109
|
← Back
|
|
101
110
|
</button>
|
|
102
111
|
<div className="flex items-center gap-3">
|
|
103
|
-
<button
|
|
104
|
-
type="button"
|
|
105
|
-
onClick={() => setShowAskArch(true)}
|
|
106
|
-
disabled={analyzing}
|
|
107
|
-
className="flex items-center gap-1.5 px-3 py-2 text-sm text-violet-700 bg-violet-50 border border-violet-200 rounded-lg hover:bg-violet-100 disabled:opacity-40 transition-colors"
|
|
108
|
-
>
|
|
109
|
-
<Wand2 className="w-3.5 h-3.5" />
|
|
110
|
-
Ask a Model
|
|
111
|
-
</button>
|
|
112
112
|
<button
|
|
113
113
|
type="button"
|
|
114
114
|
onClick={onNext}
|
|
@@ -1,13 +1,5 @@
|
|
|
1
1
|
import { useCeremonyStore } from '../../../store/ceremonyStore';
|
|
2
2
|
|
|
3
|
-
const EXAMPLE_ISSUES = [
|
|
4
|
-
{ stage: 'Project Documentation', ruleId: 'fix-header-formatting', name: 'Fix Header Spacing', severity: 'major' },
|
|
5
|
-
{ stage: 'Project Documentation', ruleId: 'add-section-spacing', name: 'Add Section Spacing', severity: 'minor' },
|
|
6
|
-
{ stage: 'Project Context', ruleId: 'token-count-too-short', name: 'Expand If Too Short', severity: 'major' },
|
|
7
|
-
{ stage: 'Project Context', ruleId: 'no-redundant-info', name: 'Remove Truly Redundant Information', severity: 'minor' },
|
|
8
|
-
{ stage: 'Context Validation', ruleId: 'fix-unclosed-code-blocks', name: 'Fix Unclosed Code Blocks', severity: 'major' },
|
|
9
|
-
];
|
|
10
|
-
|
|
11
3
|
function IssueTag({ severity }) {
|
|
12
4
|
const cls =
|
|
13
5
|
severity === 'critical' ? 'bg-red-100 text-red-700' :
|
|
@@ -46,20 +38,12 @@ export function CompleteStep({ onClose }) {
|
|
|
46
38
|
const tokenInput = r.tokenUsage?.input || r.tokenUsage?.inputTokens || 0;
|
|
47
39
|
const tokenOutput = r.tokenUsage?.output || r.tokenUsage?.outputTokens || 0;
|
|
48
40
|
const tokenTotal = r.tokenUsage?.total || r.tokenUsage?.totalTokens || tokenInput + tokenOutput;
|
|
49
|
-
const costTotal =
|
|
50
|
-
r.cost?.total != null
|
|
51
|
-
? `$${r.cost.total.toFixed(4)}`
|
|
52
|
-
: r.cost
|
|
53
|
-
? `$${Object.values(r.cost).reduce((a, v) => a + (typeof v === 'number' ? v : 0), 0).toFixed(4)}`
|
|
54
|
-
: '—';
|
|
55
|
-
|
|
56
41
|
const files = r.outputPath && r.contextPath
|
|
57
42
|
? [r.outputPath, r.contextPath]
|
|
58
43
|
: r.filesGenerated || [];
|
|
59
44
|
|
|
60
|
-
//
|
|
61
|
-
const
|
|
62
|
-
const rawIssues = r.validationIssues ?? EXAMPLE_ISSUES;
|
|
45
|
+
// Only show real validation issues; never show the example preview
|
|
46
|
+
const rawIssues = Array.isArray(r.validationIssues) ? r.validationIssues : [];
|
|
63
47
|
|
|
64
48
|
// Group duplicate rule applications by ruleId
|
|
65
49
|
const issueMap = {};
|
|
@@ -96,10 +80,9 @@ export function CompleteStep({ onClose }) {
|
|
|
96
80
|
</div>
|
|
97
81
|
)}
|
|
98
82
|
|
|
99
|
-
<div className="grid grid-cols-
|
|
83
|
+
<div className="grid grid-cols-2 gap-3">
|
|
100
84
|
<Stat label="Input tokens" value={tokenInput.toLocaleString()} />
|
|
101
85
|
<Stat label="Output tokens" value={tokenOutput.toLocaleString()} />
|
|
102
|
-
<Stat label="Estimated cost" value={costTotal} />
|
|
103
86
|
</div>
|
|
104
87
|
|
|
105
88
|
{r.model && (
|
|
@@ -112,7 +95,6 @@ export function CompleteStep({ onClose }) {
|
|
|
112
95
|
<div>
|
|
113
96
|
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">
|
|
114
97
|
Quality fixes applied
|
|
115
|
-
{isExample && <span className="ml-2 normal-case font-normal text-slate-300">(example preview)</span>}
|
|
116
98
|
</p>
|
|
117
99
|
<div className="space-y-1.5">
|
|
118
100
|
{issues.map((issue, i) => (
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { Sparkles } from 'lucide-react';
|
|
1
3
|
import { useCeremonyStore } from '../../../store/ceremonyStore';
|
|
4
|
+
import { getModels, getSettings, refineField } from '../../../lib/api';
|
|
2
5
|
|
|
3
6
|
const FIELDS = [
|
|
4
7
|
{
|
|
@@ -40,14 +43,176 @@ const FIELDS = [
|
|
|
40
43
|
},
|
|
41
44
|
];
|
|
42
45
|
|
|
46
|
+
// Maps model provider → apiKeys key returned by getSettings()
|
|
47
|
+
function normalizeProvider(p = '') {
|
|
48
|
+
if (p === 'claude') return 'anthropic';
|
|
49
|
+
return p;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Inline "Ask a Model" panel for a single field.
|
|
54
|
+
*/
|
|
55
|
+
function AskModelPanel({ fieldKey, fieldLabel, currentValue, context, onApply, onClose }) {
|
|
56
|
+
const [models, setModels] = useState([]);
|
|
57
|
+
const [selectedModelId, setSelectedModelId] = useState('');
|
|
58
|
+
const [instruction, setInstruction] = useState('');
|
|
59
|
+
const [loading, setLoading] = useState(false);
|
|
60
|
+
const [result, setResult] = useState(null);
|
|
61
|
+
const [error, setError] = useState('');
|
|
62
|
+
|
|
63
|
+
useEffect(() => {
|
|
64
|
+
Promise.all([getModels(), getSettings()])
|
|
65
|
+
.then(([data, settings]) => {
|
|
66
|
+
setModels(data);
|
|
67
|
+
const apiKeys = settings.apiKeys ?? {};
|
|
68
|
+
const ready = data.filter((m) => apiKeys[normalizeProvider(m.provider)]?.isSet);
|
|
69
|
+
const isPro = (id) => /pro|opus|sonnet/i.test(id);
|
|
70
|
+
const best = ready.find((m) => isPro(m.modelId)) || ready[0];
|
|
71
|
+
setSelectedModelId(best ? best.modelId : (data[0]?.modelId || ''));
|
|
72
|
+
})
|
|
73
|
+
.catch(() => setError('Failed to load models.'));
|
|
74
|
+
}, []);
|
|
75
|
+
|
|
76
|
+
const selectedModel = models.find((m) => m.modelId === selectedModelId);
|
|
77
|
+
const providers = [...new Set(models.map((m) => m.provider))];
|
|
78
|
+
|
|
79
|
+
async function handleRefine() {
|
|
80
|
+
if (!selectedModel || !instruction.trim()) return;
|
|
81
|
+
setLoading(true);
|
|
82
|
+
setError('');
|
|
83
|
+
setResult(null);
|
|
84
|
+
try {
|
|
85
|
+
const data = await refineField(
|
|
86
|
+
fieldKey, fieldLabel, currentValue,
|
|
87
|
+
instruction.trim(), context,
|
|
88
|
+
selectedModelId, selectedModel.provider,
|
|
89
|
+
);
|
|
90
|
+
setResult(data.value);
|
|
91
|
+
} catch (err) {
|
|
92
|
+
setError(err.message || 'Refinement failed.');
|
|
93
|
+
} finally {
|
|
94
|
+
setLoading(false);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
return (
|
|
99
|
+
<div className="mt-2 rounded-lg border border-blue-200 bg-blue-50/50 p-3 space-y-2">
|
|
100
|
+
<div className="flex items-center justify-between">
|
|
101
|
+
<span className="text-xs font-semibold text-blue-700">Ask a Model — {fieldLabel}</span>
|
|
102
|
+
<button type="button" onClick={onClose} className="text-xs text-slate-400 hover:text-slate-600">
|
|
103
|
+
×
|
|
104
|
+
</button>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
{/* Model selector + instruction */}
|
|
108
|
+
{!result && (
|
|
109
|
+
<>
|
|
110
|
+
<div className="flex gap-2">
|
|
111
|
+
<select
|
|
112
|
+
value={selectedModelId}
|
|
113
|
+
onChange={(e) => setSelectedModelId(e.target.value)}
|
|
114
|
+
disabled={loading || models.length === 0}
|
|
115
|
+
className="flex-1 rounded-md border border-slate-300 px-2 py-1 text-xs text-slate-900 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-60 bg-white"
|
|
116
|
+
>
|
|
117
|
+
{models.length === 0 && <option value="">Loading…</option>}
|
|
118
|
+
{providers.map((p) => (
|
|
119
|
+
<optgroup key={p} label={p.charAt(0).toUpperCase() + p.slice(1)}>
|
|
120
|
+
{models.filter((m) => m.provider === p).map((m) => (
|
|
121
|
+
<option key={m.modelId} value={m.modelId}>
|
|
122
|
+
{m.displayName}{!m.hasApiKey ? ' (no key)' : ''}
|
|
123
|
+
</option>
|
|
124
|
+
))}
|
|
125
|
+
</optgroup>
|
|
126
|
+
))}
|
|
127
|
+
</select>
|
|
128
|
+
</div>
|
|
129
|
+
|
|
130
|
+
<textarea
|
|
131
|
+
value={instruction}
|
|
132
|
+
onChange={(e) => setInstruction(e.target.value)}
|
|
133
|
+
rows={2}
|
|
134
|
+
placeholder="What would you like to improve? E.g. Be more specific about enterprise users…"
|
|
135
|
+
disabled={loading}
|
|
136
|
+
className="w-full rounded-md border border-slate-300 px-2 py-1.5 text-xs text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none disabled:opacity-60"
|
|
137
|
+
/>
|
|
138
|
+
|
|
139
|
+
{error && <p className="text-xs text-red-600">{error}</p>}
|
|
140
|
+
|
|
141
|
+
<div className="flex justify-end gap-2">
|
|
142
|
+
<button type="button" onClick={onClose}
|
|
143
|
+
className="px-3 py-1 text-xs text-slate-500 hover:text-slate-700">
|
|
144
|
+
Cancel
|
|
145
|
+
</button>
|
|
146
|
+
<button
|
|
147
|
+
type="button"
|
|
148
|
+
onClick={handleRefine}
|
|
149
|
+
disabled={loading || !instruction.trim() || !selectedModelId}
|
|
150
|
+
className="px-3 py-1 text-xs font-medium text-white bg-blue-600 rounded-md hover:bg-blue-700 disabled:opacity-40 flex items-center gap-1.5"
|
|
151
|
+
>
|
|
152
|
+
{loading ? (
|
|
153
|
+
<>
|
|
154
|
+
<span className="w-3 h-3 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
|
155
|
+
Refining…
|
|
156
|
+
</>
|
|
157
|
+
) : 'Refine'}
|
|
158
|
+
</button>
|
|
159
|
+
</div>
|
|
160
|
+
</>
|
|
161
|
+
)}
|
|
162
|
+
|
|
163
|
+
{/* Result */}
|
|
164
|
+
{result && (
|
|
165
|
+
<>
|
|
166
|
+
<div className="text-xs text-slate-700 bg-white rounded-md border border-slate-200 px-2.5 py-2 whitespace-pre-wrap max-h-40 overflow-y-auto">
|
|
167
|
+
{result}
|
|
168
|
+
</div>
|
|
169
|
+
<div className="flex justify-end gap-2">
|
|
170
|
+
<button type="button" onClick={() => { setResult(null); setInstruction(''); }}
|
|
171
|
+
className="px-3 py-1 text-xs text-slate-500 hover:text-slate-700">
|
|
172
|
+
Try Again
|
|
173
|
+
</button>
|
|
174
|
+
<button type="button" onClick={() => { onApply(result); onClose(); }}
|
|
175
|
+
className="px-3 py-1 text-xs font-medium text-white bg-slate-900 rounded-md hover:bg-slate-700">
|
|
176
|
+
Use This
|
|
177
|
+
</button>
|
|
178
|
+
</div>
|
|
179
|
+
</>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
);
|
|
183
|
+
}
|
|
184
|
+
|
|
43
185
|
export function ReviewAnswersStep({ onNext, onBack }) {
|
|
44
186
|
const { requirements, updateRequirement, mission, setMission, initialScope, setInitialScope } = useCeremonyStore();
|
|
45
187
|
|
|
188
|
+
// Track which field has the "Ask Model" panel open (only one at a time)
|
|
189
|
+
const [askModelField, setAskModelField] = useState(null);
|
|
190
|
+
|
|
191
|
+
const context = { mission, scope: initialScope };
|
|
192
|
+
|
|
46
193
|
const canContinue =
|
|
47
194
|
mission.trim().length > 0 &&
|
|
48
195
|
initialScope.trim().length > 0 &&
|
|
49
196
|
FIELDS.filter((f) => f.required).every((f) => requirements[f.key]?.trim());
|
|
50
197
|
|
|
198
|
+
function AskModelButton({ fieldKey }) {
|
|
199
|
+
return (
|
|
200
|
+
<button
|
|
201
|
+
type="button"
|
|
202
|
+
onClick={() => setAskModelField(askModelField === fieldKey ? null : fieldKey)}
|
|
203
|
+
className={`inline-flex items-center gap-1 text-xs px-2 py-0.5 rounded-md transition-colors ${
|
|
204
|
+
askModelField === fieldKey
|
|
205
|
+
? 'bg-blue-100 text-blue-700'
|
|
206
|
+
: 'text-slate-400 hover:text-blue-600 hover:bg-blue-50'
|
|
207
|
+
}`}
|
|
208
|
+
title="Ask a model to improve this field"
|
|
209
|
+
>
|
|
210
|
+
<Sparkles className="w-3 h-3" />
|
|
211
|
+
Ask Model
|
|
212
|
+
</button>
|
|
213
|
+
);
|
|
214
|
+
}
|
|
215
|
+
|
|
51
216
|
return (
|
|
52
217
|
<div className="space-y-6">
|
|
53
218
|
<div>
|
|
@@ -60,9 +225,12 @@ export function ReviewAnswersStep({ onNext, onBack }) {
|
|
|
60
225
|
<div className="space-y-4">
|
|
61
226
|
{/* Mission & Scope */}
|
|
62
227
|
<div>
|
|
63
|
-
<
|
|
64
|
-
|
|
65
|
-
|
|
228
|
+
<div className="flex items-center justify-between mb-0.5">
|
|
229
|
+
<label className="block text-sm font-medium text-slate-700">
|
|
230
|
+
Mission Statement <span className="text-red-500">*</span>
|
|
231
|
+
</label>
|
|
232
|
+
<AskModelButton fieldKey="MISSION_STATEMENT" />
|
|
233
|
+
</div>
|
|
66
234
|
<textarea
|
|
67
235
|
value={mission}
|
|
68
236
|
onChange={(e) => setMission(e.target.value)}
|
|
@@ -70,11 +238,24 @@ export function ReviewAnswersStep({ onNext, onBack }) {
|
|
|
70
238
|
className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
|
71
239
|
placeholder="Required..."
|
|
72
240
|
/>
|
|
241
|
+
{askModelField === 'MISSION_STATEMENT' && (
|
|
242
|
+
<AskModelPanel
|
|
243
|
+
fieldKey="MISSION_STATEMENT"
|
|
244
|
+
fieldLabel="Mission Statement"
|
|
245
|
+
currentValue={mission}
|
|
246
|
+
context={context}
|
|
247
|
+
onApply={(val) => setMission(val)}
|
|
248
|
+
onClose={() => setAskModelField(null)}
|
|
249
|
+
/>
|
|
250
|
+
)}
|
|
73
251
|
</div>
|
|
74
252
|
<div>
|
|
75
|
-
<
|
|
76
|
-
|
|
77
|
-
|
|
253
|
+
<div className="flex items-center justify-between mb-0.5">
|
|
254
|
+
<label className="block text-sm font-medium text-slate-700">
|
|
255
|
+
Initial Scope <span className="text-red-500">*</span>
|
|
256
|
+
</label>
|
|
257
|
+
<AskModelButton fieldKey="INITIAL_SCOPE" />
|
|
258
|
+
</div>
|
|
78
259
|
<textarea
|
|
79
260
|
value={initialScope}
|
|
80
261
|
onChange={(e) => setInitialScope(e.target.value)}
|
|
@@ -82,16 +263,29 @@ export function ReviewAnswersStep({ onNext, onBack }) {
|
|
|
82
263
|
className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
|
83
264
|
placeholder="Required..."
|
|
84
265
|
/>
|
|
266
|
+
{askModelField === 'INITIAL_SCOPE' && (
|
|
267
|
+
<AskModelPanel
|
|
268
|
+
fieldKey="INITIAL_SCOPE"
|
|
269
|
+
fieldLabel="Initial Scope"
|
|
270
|
+
currentValue={initialScope}
|
|
271
|
+
context={context}
|
|
272
|
+
onApply={(val) => setInitialScope(val)}
|
|
273
|
+
onClose={() => setAskModelField(null)}
|
|
274
|
+
/>
|
|
275
|
+
)}
|
|
85
276
|
</div>
|
|
86
277
|
|
|
87
278
|
<hr className="border-slate-200" />
|
|
88
279
|
|
|
89
280
|
{FIELDS.map((field) => (
|
|
90
281
|
<div key={field.key}>
|
|
91
|
-
<
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
282
|
+
<div className="flex items-center justify-between mb-0.5">
|
|
283
|
+
<label className="block text-sm font-medium text-slate-700">
|
|
284
|
+
{field.label}
|
|
285
|
+
{field.required && <span className="text-red-500 ml-1">*</span>}
|
|
286
|
+
</label>
|
|
287
|
+
<AskModelButton fieldKey={field.key} />
|
|
288
|
+
</div>
|
|
95
289
|
<p className="text-xs text-slate-400 mb-1">{field.description}</p>
|
|
96
290
|
<textarea
|
|
97
291
|
value={requirements[field.key] || ''}
|
|
@@ -100,6 +294,16 @@ export function ReviewAnswersStep({ onNext, onBack }) {
|
|
|
100
294
|
className="w-full rounded-lg border border-slate-300 px-3 py-2 text-sm text-slate-900 placeholder:text-slate-400 focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"
|
|
101
295
|
placeholder={field.required ? `Required...` : 'Optional...'}
|
|
102
296
|
/>
|
|
297
|
+
{askModelField === field.key && (
|
|
298
|
+
<AskModelPanel
|
|
299
|
+
fieldKey={field.key}
|
|
300
|
+
fieldLabel={field.label}
|
|
301
|
+
currentValue={requirements[field.key] || ''}
|
|
302
|
+
context={context}
|
|
303
|
+
onApply={(val) => updateRequirement(field.key, val)}
|
|
304
|
+
onClose={() => setAskModelField(null)}
|
|
305
|
+
/>
|
|
306
|
+
)}
|
|
103
307
|
</div>
|
|
104
308
|
))}
|
|
105
309
|
</div>
|