@agile-vibe-coding/avc 0.1.1 → 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/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +152 -0
- package/cli/agents/architecture-recommender.md +418 -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/database-deep-dive.md +470 -0
- package/cli/agents/database-recommender.md +634 -0
- package/cli/agents/doc-distributor.md +176 -0
- package/cli/agents/doc-writer-epic.md +42 -0
- package/cli/agents/doc-writer-story.md +43 -0
- package/cli/agents/documentation-updater.md +203 -0
- package/cli/agents/duplicate-detector.md +110 -0
- package/cli/agents/epic-story-decomposer.md +559 -0
- package/cli/agents/feature-context-generator.md +91 -0
- package/cli/agents/gap-checker-epic.md +52 -0
- package/cli/agents/impact-checker-story.md +51 -0
- package/cli/agents/migration-guide-generator.md +305 -0
- package/cli/agents/mission-scope-generator.md +143 -0
- package/cli/agents/mission-scope-validator.md +146 -0
- package/cli/agents/project-context-extractor.md +122 -0
- package/cli/agents/project-documentation-creator.json +226 -0
- package/cli/agents/project-documentation-creator.md +595 -0
- package/cli/agents/question-prefiller.md +269 -0
- package/cli/agents/refiner-epic.md +39 -0
- package/cli/agents/refiner-story.md +42 -0
- package/cli/agents/scaffolding-generator.md +99 -0
- package/cli/agents/seed-validator.md +71 -0
- package/cli/agents/story-doc-enricher.md +133 -0
- package/cli/agents/story-scope-reviewer.md +147 -0
- package/cli/agents/story-splitter.md +83 -0
- package/cli/agents/suggestion-business-analyst.md +88 -0
- package/cli/agents/suggestion-deployment-architect.md +263 -0
- package/cli/agents/suggestion-product-manager.md +129 -0
- package/cli/agents/suggestion-security-specialist.md +156 -0
- package/cli/agents/suggestion-technical-architect.md +269 -0
- package/cli/agents/suggestion-ux-researcher.md +93 -0
- package/cli/agents/task-subtask-decomposer.md +188 -0
- package/cli/agents/validator-documentation.json +183 -0
- package/cli/agents/validator-documentation.md +455 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/api-reference-tool.js +368 -0
- package/cli/build-docs.js +29 -8
- package/cli/ceremony-history.js +369 -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/command-logger.js +49 -12
- package/cli/components/static-output.js +63 -0
- package/cli/console-output-manager.js +94 -0
- package/cli/dependency-checker.js +72 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +659 -0
- package/cli/evaluation-prompts.js +1008 -0
- package/cli/execution-context.js +195 -0
- package/cli/generate-summary-table.js +340 -0
- package/cli/init-model-config.js +704 -0
- package/cli/init.js +1737 -278
- package/cli/kanban-server-manager.js +227 -0
- package/cli/llm-claude.js +150 -1
- package/cli/llm-gemini.js +109 -0
- package/cli/llm-local.js +493 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +454 -0
- package/cli/llm-provider.js +379 -3
- package/cli/llm-token-limits.js +211 -0
- package/cli/llm-verifier.js +662 -0
- package/cli/llm-xiaomi.js +143 -0
- package/cli/message-constants.js +49 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +291 -0
- 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 +192 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +270 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +73 -2
- package/cli/prompt-logger.js +57 -0
- package/cli/repl-ink.js +4625 -1094
- package/cli/repl-old.js +3 -4
- package/cli/seed-processor.js +962 -0
- package/cli/sprint-planning-processor.js +4162 -0
- package/cli/template-processor.js +2149 -105
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +5 -4
- package/cli/token-tracker.js +547 -0
- package/cli/tools/generate-story-validators.js +317 -0
- package/cli/tools/generate-validators.js +669 -0
- package/cli/update-checker.js +19 -17
- package/cli/update-notifier.js +4 -4
- package/cli/validation-router.js +667 -0
- package/cli/verification-tracker.js +563 -0
- package/cli/worktree-runner.js +654 -0
- package/kanban/README.md +386 -0
- package/kanban/client/README.md +205 -0
- package/kanban/client/components.json +20 -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 +16 -0
- package/kanban/client/dist/vite.svg +1 -0
- package/kanban/client/index.html +15 -0
- package/kanban/client/package-lock.json +9442 -0
- package/kanban/client/package.json +44 -0
- package/kanban/client/postcss.config.js +6 -0
- package/kanban/client/public/vite.svg +1 -0
- package/kanban/client/src/App.jsx +651 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
- package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
- package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
- package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
- package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
- package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
- package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
- package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
- 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/layout/LoadingScreen.jsx +82 -0
- package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
- package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
- package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
- package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
- package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
- package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
- package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
- package/kanban/client/src/components/stats/CostModal.jsx +384 -0
- package/kanban/client/src/components/ui/badge.jsx +27 -0
- package/kanban/client/src/components/ui/dialog.jsx +121 -0
- package/kanban/client/src/components/ui/tabs.jsx +85 -0
- package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
- package/kanban/client/src/hooks/useGrouping.js +177 -0
- package/kanban/client/src/hooks/useWebSocket.js +120 -0
- package/kanban/client/src/lib/__tests__/api.test.js +196 -0
- package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
- package/kanban/client/src/lib/api.js +515 -0
- package/kanban/client/src/lib/status-grouping.js +154 -0
- package/kanban/client/src/lib/utils.js +11 -0
- package/kanban/client/src/main.jsx +10 -0
- package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
- package/kanban/client/src/store/ceremonyStore.js +172 -0
- package/kanban/client/src/store/filterStore.js +201 -0
- package/kanban/client/src/store/kanbanStore.js +123 -0
- package/kanban/client/src/store/processStore.js +65 -0
- package/kanban/client/src/store/sprintPlanningStore.js +33 -0
- package/kanban/client/src/styles/globals.css +59 -0
- package/kanban/client/tailwind.config.js +77 -0
- package/kanban/client/vite.config.js +28 -0
- package/kanban/client/vitest.config.js +28 -0
- package/kanban/dev-start.sh +47 -0
- package/kanban/package.json +12 -0
- package/kanban/server/index.js +537 -0
- package/kanban/server/routes/ceremony.js +454 -0
- package/kanban/server/routes/costs.js +163 -0
- package/kanban/server/routes/openai-oauth.js +366 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +736 -0
- package/kanban/server/routes/websocket.js +281 -0
- package/kanban/server/routes/work-items.js +487 -0
- package/kanban/server/services/CeremonyService.js +1441 -0
- package/kanban/server/services/FileSystemScanner.js +95 -0
- package/kanban/server/services/FileWatcher.js +144 -0
- package/kanban/server/services/HierarchyBuilder.js +196 -0
- package/kanban/server/services/ProcessRegistry.js +122 -0
- package/kanban/server/services/TaskRunnerService.js +261 -0
- package/kanban/server/services/WorkItemReader.js +123 -0
- package/kanban/server/services/WorkItemRefineService.js +510 -0
- package/kanban/server/start.js +49 -0
- package/kanban/server/utils/kanban-logger.js +132 -0
- package/kanban/server/utils/markdown.js +91 -0
- package/kanban/server/utils/status-grouping.js +107 -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 +92 -0
- package/kanban/server/workers/sprint-planning-worker.js +212 -0
- package/package.json +19 -7
- package/cli/agents/documentation.md +0 -302
|
@@ -0,0 +1,629 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { AlertTriangle, Settings as SettingsIcon } from 'lucide-react';
|
|
3
|
+
import { getModels, getSettings, generateMission, refineMission } from '../../lib/api';
|
|
4
|
+
import { useCeremonyStore } from '../../store/ceremonyStore';
|
|
5
|
+
|
|
6
|
+
// Maps model provider → apiKeys key returned by getSettings()
|
|
7
|
+
function normalizeProvider(p = '') {
|
|
8
|
+
if (p === 'claude') return 'anthropic';
|
|
9
|
+
return p;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const KEY_LABELS = {
|
|
13
|
+
claude: 'Anthropic API Key (ANTHROPIC_API_KEY)',
|
|
14
|
+
anthropic: 'Anthropic API Key (ANTHROPIC_API_KEY)',
|
|
15
|
+
gemini: 'Google Gemini API Key (GEMINI_API_KEY)',
|
|
16
|
+
openai: 'OpenAI API Key (OPENAI_API_KEY)',
|
|
17
|
+
xiaomi: 'Xiaomi MiMo API Key (XIAOMI_API_KEY)',
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* IssuesPopup
|
|
22
|
+
* Sub-modal that displays all validation issues with full detail.
|
|
23
|
+
* Supports selecting issues and triggering a targeted refine.
|
|
24
|
+
*/
|
|
25
|
+
function IssuesPopup({ issues, onClose, onRefineWithIssues }) {
|
|
26
|
+
const [selectedIssueIndices, setSelectedIssueIndices] = useState(new Set());
|
|
27
|
+
|
|
28
|
+
const severityColor = {
|
|
29
|
+
critical: 'text-red-600',
|
|
30
|
+
major: 'text-orange-600',
|
|
31
|
+
minor: 'text-amber-600',
|
|
32
|
+
};
|
|
33
|
+
const severityBg = {
|
|
34
|
+
critical: 'bg-red-50 border-red-100',
|
|
35
|
+
major: 'bg-orange-50 border-orange-100',
|
|
36
|
+
minor: 'bg-amber-50 border-amber-100',
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function toggleIssue(i) {
|
|
40
|
+
setSelectedIssueIndices(prev => {
|
|
41
|
+
const next = new Set(prev);
|
|
42
|
+
if (next.has(i)) next.delete(i);
|
|
43
|
+
else next.add(i);
|
|
44
|
+
return next;
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<div
|
|
50
|
+
className="fixed inset-0 z-[70] flex items-center justify-center bg-black/40 backdrop-blur-sm p-4"
|
|
51
|
+
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
|
52
|
+
>
|
|
53
|
+
<div className="w-full max-w-lg bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden max-h-[70vh]">
|
|
54
|
+
{/* Header */}
|
|
55
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-slate-100 flex-shrink-0">
|
|
56
|
+
<div>
|
|
57
|
+
<h3 className="text-sm font-semibold text-slate-900">Validation Issues</h3>
|
|
58
|
+
<p className="text-xs text-slate-400 mt-0.5">{issues.length} issue{issues.length !== 1 ? 's' : ''} found during validation</p>
|
|
59
|
+
</div>
|
|
60
|
+
<button
|
|
61
|
+
type="button"
|
|
62
|
+
onClick={onClose}
|
|
63
|
+
className="text-slate-400 hover:text-slate-600 transition-colors text-xl leading-none ml-4"
|
|
64
|
+
aria-label="Close"
|
|
65
|
+
>
|
|
66
|
+
×
|
|
67
|
+
</button>
|
|
68
|
+
</div>
|
|
69
|
+
|
|
70
|
+
{/* Issues list */}
|
|
71
|
+
<ul className="flex-1 overflow-y-auto px-4 py-3 space-y-2">
|
|
72
|
+
{issues.map((issue, i) => (
|
|
73
|
+
<li
|
|
74
|
+
key={i}
|
|
75
|
+
onClick={() => toggleIssue(i)}
|
|
76
|
+
className={`rounded-lg border px-3 py-2.5 cursor-pointer flex items-start gap-2.5 ${selectedIssueIndices.has(i) ? 'ring-2 ring-blue-500' : ''} ${severityBg[issue.severity] ?? 'bg-slate-50 border-slate-100'}`}
|
|
77
|
+
>
|
|
78
|
+
<input
|
|
79
|
+
type="checkbox"
|
|
80
|
+
checked={selectedIssueIndices.has(i)}
|
|
81
|
+
onChange={() => toggleIssue(i)}
|
|
82
|
+
onClick={(e) => e.stopPropagation()}
|
|
83
|
+
className="mt-0.5 flex-shrink-0 accent-blue-600"
|
|
84
|
+
/>
|
|
85
|
+
<div className="flex-1 min-w-0">
|
|
86
|
+
<div className="flex items-center gap-2 mb-1">
|
|
87
|
+
<span className={`text-xs font-semibold uppercase ${severityColor[issue.severity] ?? 'text-slate-600'}`}>
|
|
88
|
+
{issue.severity}
|
|
89
|
+
</span>
|
|
90
|
+
</div>
|
|
91
|
+
<p className="text-sm text-slate-800 leading-snug">{issue.description}</p>
|
|
92
|
+
{issue.suggestion && (
|
|
93
|
+
<p className="text-xs text-slate-500 mt-1 leading-snug">
|
|
94
|
+
<span className="font-medium">Suggestion:</span> {issue.suggestion}
|
|
95
|
+
</p>
|
|
96
|
+
)}
|
|
97
|
+
</div>
|
|
98
|
+
</li>
|
|
99
|
+
))}
|
|
100
|
+
</ul>
|
|
101
|
+
|
|
102
|
+
{/* Footer */}
|
|
103
|
+
<div className="px-5 py-3 border-t border-slate-100 flex-shrink-0 flex items-center justify-between gap-2">
|
|
104
|
+
<button
|
|
105
|
+
type="button"
|
|
106
|
+
onClick={() => {
|
|
107
|
+
onRefineWithIssues(issues.filter((_, i) => selectedIssueIndices.has(i)));
|
|
108
|
+
onClose();
|
|
109
|
+
}}
|
|
110
|
+
disabled={selectedIssueIndices.size === 0}
|
|
111
|
+
className="px-4 py-1.5 text-xs text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-40"
|
|
112
|
+
>
|
|
113
|
+
Refine with selected
|
|
114
|
+
</button>
|
|
115
|
+
<button
|
|
116
|
+
type="button"
|
|
117
|
+
onClick={onClose}
|
|
118
|
+
className="px-4 py-1.5 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
|
|
119
|
+
>
|
|
120
|
+
Close
|
|
121
|
+
</button>
|
|
122
|
+
</div>
|
|
123
|
+
</div>
|
|
124
|
+
</div>
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/**
|
|
129
|
+
* RefinePopup
|
|
130
|
+
* Sub-modal for requesting targeted refinements to the generated mission & scope.
|
|
131
|
+
*/
|
|
132
|
+
function RefinePopup({ onSubmit, onClose }) {
|
|
133
|
+
const [refinementRequest, setRefinementRequest] = useState('');
|
|
134
|
+
|
|
135
|
+
return (
|
|
136
|
+
<div
|
|
137
|
+
className="fixed inset-0 z-[70] flex items-center justify-center bg-black/40 backdrop-blur-sm p-4"
|
|
138
|
+
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
|
139
|
+
>
|
|
140
|
+
<div className="w-full max-w-md bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden">
|
|
141
|
+
{/* Header */}
|
|
142
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-slate-100 flex-shrink-0">
|
|
143
|
+
<div>
|
|
144
|
+
<h3 className="text-sm font-semibold text-slate-900">Refine Mission & Scope</h3>
|
|
145
|
+
<p className="text-xs text-slate-400 mt-0.5">Describe what you'd like to change or improve.</p>
|
|
146
|
+
</div>
|
|
147
|
+
<button
|
|
148
|
+
type="button"
|
|
149
|
+
onClick={onClose}
|
|
150
|
+
className="text-slate-400 hover:text-slate-600 transition-colors text-xl leading-none ml-4"
|
|
151
|
+
aria-label="Close"
|
|
152
|
+
>
|
|
153
|
+
×
|
|
154
|
+
</button>
|
|
155
|
+
</div>
|
|
156
|
+
|
|
157
|
+
{/* Body */}
|
|
158
|
+
<div className="px-5 py-4 flex flex-col gap-3">
|
|
159
|
+
{/* Refinement textarea */}
|
|
160
|
+
<div>
|
|
161
|
+
<label className="block text-xs font-medium text-slate-500 mb-1">
|
|
162
|
+
What would you like to change or improve?
|
|
163
|
+
</label>
|
|
164
|
+
<textarea
|
|
165
|
+
value={refinementRequest}
|
|
166
|
+
onChange={(e) => setRefinementRequest(e.target.value)}
|
|
167
|
+
rows={4}
|
|
168
|
+
placeholder="E.g. Focus more on enterprise teams, make the mission mention mobile specifically…"
|
|
169
|
+
autoFocus
|
|
170
|
+
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"
|
|
171
|
+
/>
|
|
172
|
+
</div>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
{/* Footer */}
|
|
176
|
+
<div className="px-5 py-3 border-t border-slate-100 flex-shrink-0 flex items-center justify-end gap-2">
|
|
177
|
+
<button
|
|
178
|
+
type="button"
|
|
179
|
+
onClick={onClose}
|
|
180
|
+
className="px-4 py-1.5 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
|
|
181
|
+
>
|
|
182
|
+
Cancel
|
|
183
|
+
</button>
|
|
184
|
+
<button
|
|
185
|
+
type="button"
|
|
186
|
+
onClick={() => onSubmit(refinementRequest)}
|
|
187
|
+
disabled={!refinementRequest.trim()}
|
|
188
|
+
className="px-4 py-1.5 text-xs text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-40"
|
|
189
|
+
>
|
|
190
|
+
Refine
|
|
191
|
+
</button>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
</div>
|
|
195
|
+
);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* AskModelPopup
|
|
200
|
+
* Sub-popup for generating a mission statement and initial scope via an LLM.
|
|
201
|
+
* Two-column layout: left = controls/buttons, right = result output.
|
|
202
|
+
*
|
|
203
|
+
* Props:
|
|
204
|
+
* onUse(mission, scope) — called when the user accepts the generated result
|
|
205
|
+
* onClose() — called when the user cancels / closes
|
|
206
|
+
*/
|
|
207
|
+
export function AskModelPopup({ onUse, onClose, onOpenSettings }) {
|
|
208
|
+
const [models, setModels] = useState([]);
|
|
209
|
+
const [selectedModelId, setSelectedModelId] = useState('');
|
|
210
|
+
const [selectedValidatorModelId, setSelectedValidatorModelId] = useState('');
|
|
211
|
+
const [description, setDescription] = useState('');
|
|
212
|
+
const [generating, setGenerating] = useState(false);
|
|
213
|
+
const [result, setResult] = useState(null);
|
|
214
|
+
const [error, setError] = useState('');
|
|
215
|
+
const [showIssuesPopup, setShowIssuesPopup] = useState(false);
|
|
216
|
+
const [showRefinePopup, setShowRefinePopup] = useState(false);
|
|
217
|
+
|
|
218
|
+
const { missionProgressLog, clearMissionProgress } = useCeremonyStore();
|
|
219
|
+
const latestProgress = missionProgressLog[missionProgressLog.length - 1] ?? null;
|
|
220
|
+
|
|
221
|
+
const [apiKeyStatus, setApiKeyStatus] = useState(null); // { anthropic: { isSet }, gemini: { isSet }, openai: { isSet } }
|
|
222
|
+
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
Promise.all([getModels(), getSettings()])
|
|
225
|
+
.then(([data, settings]) => {
|
|
226
|
+
setModels(data);
|
|
227
|
+
setApiKeyStatus(settings.apiKeys ?? {});
|
|
228
|
+
// Auto-select best models: prefer "pro" tier for generator, "flash/lite" for validator
|
|
229
|
+
const ready = data.filter((m) => settings.apiKeys?.[normalizeProvider(m.provider)]?.isSet);
|
|
230
|
+
const isPro = (id) => /pro|opus|sonnet/i.test(id);
|
|
231
|
+
const isLite = (id) => /flash|lite|haiku|mini/i.test(id);
|
|
232
|
+
const generator = ready.find((m) => isPro(m.modelId)) || ready[0];
|
|
233
|
+
const generatorId = generator ? generator.modelId : (data.length > 0 ? data[0].modelId : '');
|
|
234
|
+
setSelectedModelId(generatorId);
|
|
235
|
+
const validator = ready.find((m) => isLite(m.modelId) && m.modelId !== generatorId)
|
|
236
|
+
|| ready.find((m) => m.modelId !== generatorId)
|
|
237
|
+
|| generator;
|
|
238
|
+
setSelectedValidatorModelId(validator ? validator.modelId : generatorId);
|
|
239
|
+
})
|
|
240
|
+
.catch(() => setError('Failed to load available models.'));
|
|
241
|
+
}, []);
|
|
242
|
+
|
|
243
|
+
function recheckKeys() {
|
|
244
|
+
Promise.all([getSettings(), getModels()])
|
|
245
|
+
.then(([s, updatedModels]) => {
|
|
246
|
+
setApiKeyStatus(s.apiKeys ?? {});
|
|
247
|
+
setModels(updatedModels);
|
|
248
|
+
})
|
|
249
|
+
.catch(() => {});
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Auto-recheck when settings are saved from anywhere in the app
|
|
253
|
+
useEffect(() => {
|
|
254
|
+
document.addEventListener('avc:settings-saved', recheckKeys);
|
|
255
|
+
return () => document.removeEventListener('avc:settings-saved', recheckKeys);
|
|
256
|
+
}, []); // eslint-disable-line react-hooks/exhaustive-deps
|
|
257
|
+
|
|
258
|
+
const selectedModel = models.find((m) => m.modelId === selectedModelId);
|
|
259
|
+
const selectedValidatorModel = models.find((m) => m.modelId === selectedValidatorModelId);
|
|
260
|
+
|
|
261
|
+
// Derive missing providers from currently selected models using settings key status
|
|
262
|
+
const missingKeyProviders = (() => {
|
|
263
|
+
if (!apiKeyStatus) return [];
|
|
264
|
+
const missing = new Set();
|
|
265
|
+
if (selectedModel && !apiKeyStatus[normalizeProvider(selectedModel.provider)]?.isSet) {
|
|
266
|
+
missing.add(selectedModel.provider);
|
|
267
|
+
}
|
|
268
|
+
if (selectedValidatorModel && !apiKeyStatus[normalizeProvider(selectedValidatorModel.provider)]?.isSet) {
|
|
269
|
+
missing.add(selectedValidatorModel.provider);
|
|
270
|
+
}
|
|
271
|
+
return [...missing];
|
|
272
|
+
})();
|
|
273
|
+
|
|
274
|
+
async function handleGenerate() {
|
|
275
|
+
if (!description.trim() || !selectedModelId || !selectedModel || !selectedValidatorModelId || !selectedValidatorModel) return;
|
|
276
|
+
setGenerating(true);
|
|
277
|
+
setError('');
|
|
278
|
+
setResult(null);
|
|
279
|
+
setShowIssuesPopup(false);
|
|
280
|
+
clearMissionProgress();
|
|
281
|
+
try {
|
|
282
|
+
const data = await generateMission(
|
|
283
|
+
description.trim(),
|
|
284
|
+
selectedModelId,
|
|
285
|
+
selectedModel.provider,
|
|
286
|
+
selectedValidatorModelId,
|
|
287
|
+
selectedValidatorModel.provider,
|
|
288
|
+
);
|
|
289
|
+
setResult(data);
|
|
290
|
+
} catch (err) {
|
|
291
|
+
setError(err.message || 'Generation failed. Please try again.');
|
|
292
|
+
} finally {
|
|
293
|
+
setGenerating(false);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
async function handleRefine(refinementRequest) {
|
|
298
|
+
const { missionStatement, initialScope } = result;
|
|
299
|
+
setShowRefinePopup(false);
|
|
300
|
+
setResult(null);
|
|
301
|
+
setGenerating(true);
|
|
302
|
+
setError('');
|
|
303
|
+
setShowIssuesPopup(false);
|
|
304
|
+
clearMissionProgress();
|
|
305
|
+
try {
|
|
306
|
+
const data = await refineMission(
|
|
307
|
+
missionStatement, initialScope, refinementRequest,
|
|
308
|
+
selectedModelId, selectedModel.provider,
|
|
309
|
+
selectedValidatorModelId, selectedValidatorModel.provider,
|
|
310
|
+
);
|
|
311
|
+
setResult(data);
|
|
312
|
+
} catch (err) {
|
|
313
|
+
setError(err.message || 'Refinement failed. Please try again.');
|
|
314
|
+
} finally {
|
|
315
|
+
setGenerating(false);
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function handleRefineFromIssues(selectedIssues) {
|
|
320
|
+
setShowIssuesPopup(false);
|
|
321
|
+
const request = 'Fix the following validation issues:\n' +
|
|
322
|
+
selectedIssues.map(i =>
|
|
323
|
+
`- [${i.severity.toUpperCase()}] ${i.field}: ${i.description} → ${i.suggestion}`
|
|
324
|
+
).join('\n');
|
|
325
|
+
handleRefine(request);
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function handleUse() {
|
|
329
|
+
if (!result) return;
|
|
330
|
+
onUse(result.missionStatement, result.initialScope);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
function handleTryAgain() {
|
|
334
|
+
setResult(null);
|
|
335
|
+
setError('');
|
|
336
|
+
setShowIssuesPopup(false);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const providers = [...new Set(models.map((m) => m.provider))];
|
|
340
|
+
const scorePassed = result?.validationScore != null && result.validationScore >= 75;
|
|
341
|
+
|
|
342
|
+
const canGenerate =
|
|
343
|
+
!generating &&
|
|
344
|
+
description.trim().length > 0 &&
|
|
345
|
+
!!selectedModelId &&
|
|
346
|
+
!!selectedValidatorModelId &&
|
|
347
|
+
missingKeyProviders.length === 0;
|
|
348
|
+
|
|
349
|
+
// Shared model dropdown markup
|
|
350
|
+
function ModelSelect({ label, value, onChange }) {
|
|
351
|
+
const chosen = models.find((m) => m.modelId === value);
|
|
352
|
+
return (
|
|
353
|
+
<div>
|
|
354
|
+
<label className="block text-xs font-medium text-slate-500 mb-1">{label}</label>
|
|
355
|
+
<select
|
|
356
|
+
value={value}
|
|
357
|
+
onChange={(e) => onChange(e.target.value)}
|
|
358
|
+
disabled={generating || models.length === 0}
|
|
359
|
+
className="w-full rounded-md border border-slate-300 px-2 py-1.5 text-xs text-slate-900 focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:opacity-60 bg-white"
|
|
360
|
+
>
|
|
361
|
+
{models.length === 0 && <option value="">Loading…</option>}
|
|
362
|
+
{providers.map((p) => (
|
|
363
|
+
<optgroup key={p} label={p.charAt(0).toUpperCase() + p.slice(1)}>
|
|
364
|
+
{models.filter((m) => m.provider === p).map((m) => (
|
|
365
|
+
<option key={m.modelId} value={m.modelId}>
|
|
366
|
+
{m.displayName}{!m.hasApiKey ? ' (no key)' : ''}
|
|
367
|
+
</option>
|
|
368
|
+
))}
|
|
369
|
+
</optgroup>
|
|
370
|
+
))}
|
|
371
|
+
</select>
|
|
372
|
+
{chosen && !chosen.hasApiKey && (
|
|
373
|
+
<p className="text-xs text-amber-600 mt-0.5">
|
|
374
|
+
⚠ No API key — add to <code>.env</code>
|
|
375
|
+
</p>
|
|
376
|
+
)}
|
|
377
|
+
</div>
|
|
378
|
+
);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
return (
|
|
382
|
+
<>
|
|
383
|
+
<div
|
|
384
|
+
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
|
|
385
|
+
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
|
386
|
+
>
|
|
387
|
+
{/* Card — fixed height two-column */}
|
|
388
|
+
<div className="w-full max-w-5xl bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden h-[540px]">
|
|
389
|
+
|
|
390
|
+
{/* Header */}
|
|
391
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-slate-100 flex-shrink-0">
|
|
392
|
+
<div>
|
|
393
|
+
<h3 className="text-sm font-semibold text-slate-900">✨ Ask a Model</h3>
|
|
394
|
+
<p className="text-xs text-slate-400 mt-0.5">Describe your idea — an LLM generates and validates your mission & scope.</p>
|
|
395
|
+
</div>
|
|
396
|
+
<button
|
|
397
|
+
type="button"
|
|
398
|
+
onClick={onClose}
|
|
399
|
+
className="text-slate-400 hover:text-slate-600 transition-colors text-xl leading-none ml-4"
|
|
400
|
+
aria-label="Close"
|
|
401
|
+
>
|
|
402
|
+
×
|
|
403
|
+
</button>
|
|
404
|
+
</div>
|
|
405
|
+
|
|
406
|
+
{/* Body — two columns */}
|
|
407
|
+
<div className="flex flex-1 min-h-0">
|
|
408
|
+
|
|
409
|
+
{/* ── LEFT column: controls ────────────────────────────────────────── */}
|
|
410
|
+
<div className="w-2/5 min-w-0 border-r border-slate-100 flex flex-col px-4 py-4 gap-3">
|
|
411
|
+
|
|
412
|
+
<ModelSelect
|
|
413
|
+
label="Generator Model"
|
|
414
|
+
value={selectedModelId}
|
|
415
|
+
onChange={setSelectedModelId}
|
|
416
|
+
/>
|
|
417
|
+
|
|
418
|
+
<ModelSelect
|
|
419
|
+
label="Validator Model"
|
|
420
|
+
value={selectedValidatorModelId}
|
|
421
|
+
onChange={setSelectedValidatorModelId}
|
|
422
|
+
/>
|
|
423
|
+
|
|
424
|
+
{missingKeyProviders.length > 0 && (
|
|
425
|
+
<div className="flex flex-col gap-2 p-3 bg-amber-50 border border-amber-200 rounded-lg">
|
|
426
|
+
<div className="flex items-start gap-2">
|
|
427
|
+
<AlertTriangle className="w-3.5 h-3.5 text-amber-500 flex-shrink-0 mt-0.5" />
|
|
428
|
+
<div>
|
|
429
|
+
<p className="text-xs font-semibold text-amber-900">API Key Missing</p>
|
|
430
|
+
<ul className="mt-1 space-y-0.5">
|
|
431
|
+
{missingKeyProviders.map((p) => (
|
|
432
|
+
<li key={p} className="flex items-center gap-1.5 text-xs text-amber-800">
|
|
433
|
+
<span className="w-1 h-1 rounded-full bg-amber-400 flex-shrink-0" />
|
|
434
|
+
{KEY_LABELS[p] || p}
|
|
435
|
+
</li>
|
|
436
|
+
))}
|
|
437
|
+
</ul>
|
|
438
|
+
</div>
|
|
439
|
+
</div>
|
|
440
|
+
<div className="flex items-center gap-2">
|
|
441
|
+
{onOpenSettings && (
|
|
442
|
+
<button
|
|
443
|
+
type="button"
|
|
444
|
+
onClick={() => onOpenSettings()}
|
|
445
|
+
className="flex items-center gap-1 text-xs font-medium bg-slate-900 text-white px-2.5 py-1 rounded-md hover:bg-slate-700 transition-colors"
|
|
446
|
+
>
|
|
447
|
+
<SettingsIcon className="w-3 h-3" />
|
|
448
|
+
Settings
|
|
449
|
+
</button>
|
|
450
|
+
)}
|
|
451
|
+
<button
|
|
452
|
+
type="button"
|
|
453
|
+
onClick={recheckKeys}
|
|
454
|
+
className="text-xs text-slate-500 hover:text-slate-800 transition-colors"
|
|
455
|
+
>
|
|
456
|
+
Re-check
|
|
457
|
+
</button>
|
|
458
|
+
</div>
|
|
459
|
+
</div>
|
|
460
|
+
)}
|
|
461
|
+
|
|
462
|
+
<div>
|
|
463
|
+
<label className="block text-xs font-medium text-slate-500 mb-1">
|
|
464
|
+
What are you building?
|
|
465
|
+
</label>
|
|
466
|
+
<textarea
|
|
467
|
+
value={description}
|
|
468
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
469
|
+
rows={4}
|
|
470
|
+
placeholder="E.g. A recipe sharing app for home cooks…"
|
|
471
|
+
disabled={generating}
|
|
472
|
+
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"
|
|
473
|
+
/>
|
|
474
|
+
</div>
|
|
475
|
+
|
|
476
|
+
{/* Error */}
|
|
477
|
+
{error && (
|
|
478
|
+
<p className="text-xs text-red-600 bg-red-50 rounded-md px-2 py-1.5">{error}</p>
|
|
479
|
+
)}
|
|
480
|
+
|
|
481
|
+
{/* Score badge + issues button */}
|
|
482
|
+
{result?.validationScore != null && (
|
|
483
|
+
<div className="flex items-center gap-2 flex-wrap">
|
|
484
|
+
<span
|
|
485
|
+
className={`inline-flex items-center gap-1 text-xs font-medium px-2 py-0.5 rounded-full ${
|
|
486
|
+
scorePassed
|
|
487
|
+
? 'bg-green-50 text-green-700 border border-green-200'
|
|
488
|
+
: 'bg-amber-50 text-amber-700 border border-amber-200'
|
|
489
|
+
}`}
|
|
490
|
+
>
|
|
491
|
+
{scorePassed ? '✓' : '⚠'} {result.validationScore}/100
|
|
492
|
+
·
|
|
493
|
+
{result.iterations} iter{result.iterations !== 1 ? 's' : ''}
|
|
494
|
+
</span>
|
|
495
|
+
{result.issues?.length > 0 && (
|
|
496
|
+
<button
|
|
497
|
+
type="button"
|
|
498
|
+
onClick={() => setShowIssuesPopup(true)}
|
|
499
|
+
className="text-xs text-slate-400 hover:text-slate-600 underline underline-offset-2 transition-colors"
|
|
500
|
+
>
|
|
501
|
+
Issues ({result.issues.length})
|
|
502
|
+
</button>
|
|
503
|
+
)}
|
|
504
|
+
</div>
|
|
505
|
+
)}
|
|
506
|
+
|
|
507
|
+
{/* Spacer pushes buttons to bottom */}
|
|
508
|
+
<div className="flex-1" />
|
|
509
|
+
|
|
510
|
+
{/* Buttons */}
|
|
511
|
+
<div className="flex flex-col gap-2">
|
|
512
|
+
{result ? (
|
|
513
|
+
<>
|
|
514
|
+
<button
|
|
515
|
+
type="button"
|
|
516
|
+
onClick={handleUse}
|
|
517
|
+
className="w-full px-3 py-2 bg-slate-900 text-white text-xs font-medium rounded-lg hover:bg-slate-700 transition-colors"
|
|
518
|
+
>
|
|
519
|
+
Use This
|
|
520
|
+
</button>
|
|
521
|
+
<button
|
|
522
|
+
type="button"
|
|
523
|
+
onClick={() => setShowRefinePopup(true)}
|
|
524
|
+
className="w-full px-3 py-2 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
|
|
525
|
+
>
|
|
526
|
+
Refine
|
|
527
|
+
</button>
|
|
528
|
+
<button
|
|
529
|
+
type="button"
|
|
530
|
+
onClick={handleTryAgain}
|
|
531
|
+
className="w-full px-3 py-2 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
|
|
532
|
+
>
|
|
533
|
+
Try Again
|
|
534
|
+
</button>
|
|
535
|
+
</>
|
|
536
|
+
) : (
|
|
537
|
+
<button
|
|
538
|
+
type="button"
|
|
539
|
+
onClick={handleGenerate}
|
|
540
|
+
disabled={!canGenerate}
|
|
541
|
+
className="w-full px-3 py-2 bg-blue-600 text-white text-xs font-medium rounded-lg disabled:opacity-40 hover:bg-blue-700 transition-colors flex items-center justify-center gap-2"
|
|
542
|
+
>
|
|
543
|
+
{generating ? (
|
|
544
|
+
<>
|
|
545
|
+
<span className="w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
|
546
|
+
Generating…
|
|
547
|
+
</>
|
|
548
|
+
) : 'Generate'}
|
|
549
|
+
</button>
|
|
550
|
+
)}
|
|
551
|
+
<button
|
|
552
|
+
type="button"
|
|
553
|
+
onClick={onClose}
|
|
554
|
+
className="w-full px-3 py-1.5 text-xs text-slate-400 hover:text-slate-600 transition-colors"
|
|
555
|
+
>
|
|
556
|
+
Cancel
|
|
557
|
+
</button>
|
|
558
|
+
</div>
|
|
559
|
+
</div>
|
|
560
|
+
|
|
561
|
+
{/* ── RIGHT column: output ─────────────────────────────────────────── */}
|
|
562
|
+
<div className="flex-1 flex flex-col min-w-0">
|
|
563
|
+
|
|
564
|
+
{/* Generating state */}
|
|
565
|
+
{generating && (
|
|
566
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-3 px-8 text-center">
|
|
567
|
+
<span className="w-8 h-8 border-3 border-blue-200 border-t-blue-600 rounded-full animate-spin" style={{ borderWidth: 3 }} />
|
|
568
|
+
<p className="text-sm font-medium text-slate-700">
|
|
569
|
+
{latestProgress ? latestProgress.message : 'Starting…'}
|
|
570
|
+
</p>
|
|
571
|
+
<p className="text-xs text-slate-400">This may take a moment while the models validate and refine the output.</p>
|
|
572
|
+
</div>
|
|
573
|
+
)}
|
|
574
|
+
|
|
575
|
+
{/* Idle / no result yet */}
|
|
576
|
+
{!generating && !result && (
|
|
577
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-2 px-8 text-center text-slate-300">
|
|
578
|
+
<svg className="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
579
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
|
|
580
|
+
d="M9.813 15.904L9 18.75l-.813-2.846a4.5 4.5 0 00-3.09-3.09L2.25 12l2.846-.813a4.5 4.5 0 003.09-3.09L9 5.25l.813 2.846a4.5 4.5 0 003.09 3.09L15.75 12l-2.846.813a4.5 4.5 0 00-3.09 3.09z" />
|
|
581
|
+
</svg>
|
|
582
|
+
<p className="text-sm font-medium text-slate-400">Generated mission & scope will appear here</p>
|
|
583
|
+
<p className="text-xs text-slate-300">Fill in the form and click Generate</p>
|
|
584
|
+
</div>
|
|
585
|
+
)}
|
|
586
|
+
|
|
587
|
+
{/* Result */}
|
|
588
|
+
{!generating && result && (
|
|
589
|
+
<div className="flex-1 flex flex-col gap-4 px-5 py-4 overflow-y-auto">
|
|
590
|
+
<div>
|
|
591
|
+
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">Mission Statement</p>
|
|
592
|
+
<p className="text-sm text-slate-800 bg-slate-50 rounded-lg px-3 py-2.5 leading-relaxed">
|
|
593
|
+
{result.missionStatement}
|
|
594
|
+
</p>
|
|
595
|
+
</div>
|
|
596
|
+
<div className="flex-1 flex flex-col min-h-0">
|
|
597
|
+
<p className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-2">Initial Scope</p>
|
|
598
|
+
<div className="flex-1 text-sm text-slate-800 bg-slate-50 rounded-lg px-3 py-2.5 leading-relaxed overflow-y-auto min-h-0">
|
|
599
|
+
{result.initialScope.split('\n').map((line, i) => (
|
|
600
|
+
<p key={i} className="mb-1 last:mb-0">{line}</p>
|
|
601
|
+
))}
|
|
602
|
+
</div>
|
|
603
|
+
</div>
|
|
604
|
+
</div>
|
|
605
|
+
)}
|
|
606
|
+
</div>
|
|
607
|
+
</div>
|
|
608
|
+
</div>
|
|
609
|
+
</div>
|
|
610
|
+
|
|
611
|
+
{/* Issues detail popup */}
|
|
612
|
+
{showIssuesPopup && result?.issues?.length > 0 && (
|
|
613
|
+
<IssuesPopup
|
|
614
|
+
issues={result.issues}
|
|
615
|
+
onClose={() => setShowIssuesPopup(false)}
|
|
616
|
+
onRefineWithIssues={handleRefineFromIssues}
|
|
617
|
+
/>
|
|
618
|
+
)}
|
|
619
|
+
|
|
620
|
+
{/* Refine popup */}
|
|
621
|
+
{showRefinePopup && result && (
|
|
622
|
+
<RefinePopup
|
|
623
|
+
onSubmit={handleRefine}
|
|
624
|
+
onClose={() => setShowRefinePopup(false)}
|
|
625
|
+
/>
|
|
626
|
+
)}
|
|
627
|
+
</>
|
|
628
|
+
);
|
|
629
|
+
}
|