@agile-vibe-coding/avc 0.1.0 → 0.2.3
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/README.md +2 -0
- package/cli/agent-loader.js +21 -0
- package/cli/agents/agent-selector.md +129 -0
- package/cli/agents/architecture-recommender.md +418 -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/documentation-updater.md +203 -0
- package/cli/agents/epic-story-decomposer.md +280 -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 +79 -0
- package/cli/agents/mission-scope-validator.md +112 -0
- package/cli/agents/project-context-extractor.md +107 -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/solver-epic-api.json +15 -0
- package/cli/agents/solver-epic-api.md +39 -0
- package/cli/agents/solver-epic-backend.json +15 -0
- package/cli/agents/solver-epic-backend.md +39 -0
- package/cli/agents/solver-epic-cloud.json +15 -0
- package/cli/agents/solver-epic-cloud.md +39 -0
- package/cli/agents/solver-epic-data.json +15 -0
- package/cli/agents/solver-epic-data.md +39 -0
- package/cli/agents/solver-epic-database.json +15 -0
- package/cli/agents/solver-epic-database.md +39 -0
- package/cli/agents/solver-epic-developer.json +15 -0
- package/cli/agents/solver-epic-developer.md +39 -0
- package/cli/agents/solver-epic-devops.json +15 -0
- package/cli/agents/solver-epic-devops.md +39 -0
- package/cli/agents/solver-epic-frontend.json +15 -0
- package/cli/agents/solver-epic-frontend.md +39 -0
- package/cli/agents/solver-epic-mobile.json +15 -0
- package/cli/agents/solver-epic-mobile.md +39 -0
- package/cli/agents/solver-epic-qa.json +15 -0
- package/cli/agents/solver-epic-qa.md +39 -0
- package/cli/agents/solver-epic-security.json +15 -0
- package/cli/agents/solver-epic-security.md +39 -0
- package/cli/agents/solver-epic-solution-architect.json +15 -0
- package/cli/agents/solver-epic-solution-architect.md +39 -0
- package/cli/agents/solver-epic-test-architect.json +15 -0
- package/cli/agents/solver-epic-test-architect.md +39 -0
- package/cli/agents/solver-epic-ui.json +15 -0
- package/cli/agents/solver-epic-ui.md +39 -0
- package/cli/agents/solver-epic-ux.json +15 -0
- package/cli/agents/solver-epic-ux.md +39 -0
- package/cli/agents/solver-story-api.json +15 -0
- package/cli/agents/solver-story-api.md +39 -0
- package/cli/agents/solver-story-backend.json +15 -0
- package/cli/agents/solver-story-backend.md +39 -0
- package/cli/agents/solver-story-cloud.json +15 -0
- package/cli/agents/solver-story-cloud.md +39 -0
- package/cli/agents/solver-story-data.json +15 -0
- package/cli/agents/solver-story-data.md +39 -0
- package/cli/agents/solver-story-database.json +15 -0
- package/cli/agents/solver-story-database.md +39 -0
- package/cli/agents/solver-story-developer.json +15 -0
- package/cli/agents/solver-story-developer.md +39 -0
- package/cli/agents/solver-story-devops.json +15 -0
- package/cli/agents/solver-story-devops.md +39 -0
- package/cli/agents/solver-story-frontend.json +15 -0
- package/cli/agents/solver-story-frontend.md +39 -0
- package/cli/agents/solver-story-mobile.json +15 -0
- package/cli/agents/solver-story-mobile.md +39 -0
- package/cli/agents/solver-story-qa.json +15 -0
- package/cli/agents/solver-story-qa.md +39 -0
- package/cli/agents/solver-story-security.json +15 -0
- package/cli/agents/solver-story-security.md +39 -0
- package/cli/agents/solver-story-solution-architect.json +15 -0
- package/cli/agents/solver-story-solution-architect.md +39 -0
- package/cli/agents/solver-story-test-architect.json +15 -0
- package/cli/agents/solver-story-test-architect.md +39 -0
- package/cli/agents/solver-story-ui.json +15 -0
- package/cli/agents/solver-story-ui.md +39 -0
- package/cli/agents/solver-story-ux.json +15 -0
- package/cli/agents/solver-story-ux.md +39 -0
- package/cli/agents/story-doc-enricher.md +133 -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 +152 -0
- package/cli/agents/validator-documentation.md +453 -0
- package/cli/agents/validator-epic-api.json +93 -0
- package/cli/agents/validator-epic-api.md +137 -0
- package/cli/agents/validator-epic-backend.json +93 -0
- package/cli/agents/validator-epic-backend.md +130 -0
- package/cli/agents/validator-epic-cloud.json +93 -0
- package/cli/agents/validator-epic-cloud.md +137 -0
- package/cli/agents/validator-epic-data.json +93 -0
- package/cli/agents/validator-epic-data.md +130 -0
- package/cli/agents/validator-epic-database.json +93 -0
- package/cli/agents/validator-epic-database.md +137 -0
- package/cli/agents/validator-epic-developer.json +74 -0
- package/cli/agents/validator-epic-developer.md +153 -0
- package/cli/agents/validator-epic-devops.json +74 -0
- package/cli/agents/validator-epic-devops.md +153 -0
- package/cli/agents/validator-epic-frontend.json +74 -0
- package/cli/agents/validator-epic-frontend.md +153 -0
- package/cli/agents/validator-epic-mobile.json +93 -0
- package/cli/agents/validator-epic-mobile.md +130 -0
- package/cli/agents/validator-epic-qa.json +93 -0
- package/cli/agents/validator-epic-qa.md +130 -0
- package/cli/agents/validator-epic-security.json +74 -0
- package/cli/agents/validator-epic-security.md +154 -0
- package/cli/agents/validator-epic-solution-architect.json +74 -0
- package/cli/agents/validator-epic-solution-architect.md +156 -0
- package/cli/agents/validator-epic-test-architect.json +93 -0
- package/cli/agents/validator-epic-test-architect.md +130 -0
- package/cli/agents/validator-epic-ui.json +93 -0
- package/cli/agents/validator-epic-ui.md +130 -0
- package/cli/agents/validator-epic-ux.json +93 -0
- package/cli/agents/validator-epic-ux.md +130 -0
- package/cli/agents/validator-selector.md +211 -0
- package/cli/agents/validator-story-api.json +104 -0
- package/cli/agents/validator-story-api.md +152 -0
- package/cli/agents/validator-story-backend.json +104 -0
- package/cli/agents/validator-story-backend.md +152 -0
- package/cli/agents/validator-story-cloud.json +104 -0
- package/cli/agents/validator-story-cloud.md +152 -0
- package/cli/agents/validator-story-data.json +104 -0
- package/cli/agents/validator-story-data.md +152 -0
- package/cli/agents/validator-story-database.json +104 -0
- package/cli/agents/validator-story-database.md +152 -0
- package/cli/agents/validator-story-developer.json +104 -0
- package/cli/agents/validator-story-developer.md +152 -0
- package/cli/agents/validator-story-devops.json +104 -0
- package/cli/agents/validator-story-devops.md +152 -0
- package/cli/agents/validator-story-frontend.json +104 -0
- package/cli/agents/validator-story-frontend.md +152 -0
- package/cli/agents/validator-story-mobile.json +104 -0
- package/cli/agents/validator-story-mobile.md +152 -0
- package/cli/agents/validator-story-qa.json +104 -0
- package/cli/agents/validator-story-qa.md +152 -0
- package/cli/agents/validator-story-security.json +104 -0
- package/cli/agents/validator-story-security.md +152 -0
- package/cli/agents/validator-story-solution-architect.json +104 -0
- package/cli/agents/validator-story-solution-architect.md +152 -0
- package/cli/agents/validator-story-test-architect.json +104 -0
- package/cli/agents/validator-story-test-architect.md +152 -0
- package/cli/agents/validator-story-ui.json +104 -0
- package/cli/agents/validator-story-ui.md +152 -0
- package/cli/agents/validator-story-ux.json +104 -0
- package/cli/agents/validator-story-ux.md +152 -0
- package/cli/ansi-colors.js +21 -0
- package/cli/build-docs.js +298 -0
- package/cli/ceremony-history.js +369 -0
- package/cli/command-logger.js +245 -0
- package/cli/components/static-output.js +63 -0
- package/cli/console-output-manager.js +94 -0
- package/cli/docs-sync.js +306 -0
- package/cli/epic-story-validator.js +1174 -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/index.js +3 -25
- package/cli/init-model-config.js +697 -0
- package/cli/init.js +1765 -100
- package/cli/kanban-server-manager.js +228 -0
- package/cli/llm-claude.js +109 -0
- package/cli/llm-gemini.js +115 -0
- package/cli/llm-mock.js +233 -0
- package/cli/llm-openai.js +233 -0
- package/cli/llm-provider.js +300 -0
- package/cli/llm-token-limits.js +102 -0
- package/cli/llm-verifier.js +454 -0
- package/cli/logger.js +32 -5
- package/cli/message-constants.js +58 -0
- package/cli/message-manager.js +334 -0
- package/cli/message-types.js +96 -0
- package/cli/messaging-api.js +297 -0
- package/cli/model-pricing.js +169 -0
- package/cli/model-query-engine.js +468 -0
- package/cli/model-recommendation-analyzer.js +495 -0
- package/cli/model-selector.js +269 -0
- package/cli/output-buffer.js +107 -0
- package/cli/process-manager.js +332 -0
- package/cli/repl-ink.js +5840 -504
- package/cli/repl-old.js +4 -4
- package/cli/seed-processor.js +792 -0
- package/cli/sprint-planning-processor.js +1813 -0
- package/cli/template-processor.js +2306 -108
- package/cli/templates/project.md +25 -8
- package/cli/templates/vitepress-config.mts.template +34 -0
- package/cli/token-tracker.js +520 -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 +605 -0
- package/cli/verification-tracker.js +563 -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-CiD8PS2e.js +306 -0
- package/kanban/client/dist/assets/index-nLh0m82Q.css +1 -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 +622 -0
- package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
- package/kanban/client/src/components/ceremony/AskArchPopup.jsx +416 -0
- package/kanban/client/src/components/ceremony/AskModelPopup.jsx +616 -0
- package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +946 -0
- package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
- package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +619 -0
- package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +704 -0
- package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
- package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +154 -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 +125 -0
- package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +228 -0
- package/kanban/client/src/components/kanban/CardDetailModal.jsx +559 -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 +57 -0
- package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
- package/kanban/client/src/components/kanban/KanbanCard.jsx +138 -0
- package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
- package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +789 -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 +353 -0
- package/kanban/client/src/components/settings/ApiKeysTab.jsx +113 -0
- package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +98 -0
- package/kanban/client/src/components/settings/CostThresholdsTab.jsx +94 -0
- package/kanban/client/src/components/settings/ModelPricingTab.jsx +204 -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 +353 -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 +118 -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 +401 -0
- package/kanban/client/src/lib/status-grouping.js +144 -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 +115 -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 +516 -0
- package/kanban/server/routes/ceremony.js +305 -0
- package/kanban/server/routes/costs.js +157 -0
- package/kanban/server/routes/processes.js +50 -0
- package/kanban/server/routes/settings.js +303 -0
- package/kanban/server/routes/websocket.js +276 -0
- package/kanban/server/routes/work-items.js +347 -0
- package/kanban/server/services/CeremonyService.js +1190 -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/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/sponsor-call-worker.js +84 -0
- package/kanban/server/workers/sprint-planning-worker.js +130 -0
- package/package.json +34 -7
|
@@ -0,0 +1,619 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { X, Info, AlertTriangle, Settings as SettingsIcon } from 'lucide-react';
|
|
3
|
+
import { useCeremonyStore } from '../../store/ceremonyStore';
|
|
4
|
+
import {
|
|
5
|
+
analyzeDatabase,
|
|
6
|
+
analyzeArchitecture,
|
|
7
|
+
prefillAnswers,
|
|
8
|
+
runCeremony,
|
|
9
|
+
getSettings,
|
|
10
|
+
getModels,
|
|
11
|
+
saveCeremonies,
|
|
12
|
+
pauseCeremony,
|
|
13
|
+
resumeCeremony,
|
|
14
|
+
cancelCeremony,
|
|
15
|
+
getSponsorCallDraft,
|
|
16
|
+
saveSponsorCallDraft,
|
|
17
|
+
deleteSponsorCallDraft,
|
|
18
|
+
} from '../../lib/api';
|
|
19
|
+
import { CeremonyWorkflowModal } from './CeremonyWorkflowModal';
|
|
20
|
+
|
|
21
|
+
import { DeploymentStep } from './steps/DeploymentStep';
|
|
22
|
+
import { MissionStep } from './steps/MissionStep';
|
|
23
|
+
import { DatabaseStep } from './steps/DatabaseStep';
|
|
24
|
+
import { ArchitectureStep } from './steps/ArchitectureStep';
|
|
25
|
+
import { ReviewAnswersStep } from './steps/ReviewAnswersStep';
|
|
26
|
+
import { RunningStep } from './steps/RunningStep';
|
|
27
|
+
import { CompleteStep } from './steps/CompleteStep';
|
|
28
|
+
|
|
29
|
+
const KEY_LABELS = {
|
|
30
|
+
anthropic: 'Anthropic API Key (ANTHROPIC_API_KEY)',
|
|
31
|
+
gemini: 'Google Gemini API Key (GEMINI_API_KEY)',
|
|
32
|
+
openai: 'OpenAI API Key (OPENAI_API_KEY)',
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
function normalizeProvider(provider = '') {
|
|
36
|
+
const p = provider.toLowerCase();
|
|
37
|
+
if (p === 'claude' || p === 'anthropic') return 'anthropic';
|
|
38
|
+
return p;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function computeMissingProviders(settings) {
|
|
42
|
+
const ceremony = settings.ceremonies?.find((c) => c.name === 'sponsor-call');
|
|
43
|
+
const needed = new Set();
|
|
44
|
+
|
|
45
|
+
// stages is an object: { stageName: { provider, model }, ... }
|
|
46
|
+
if (ceremony?.stages && typeof ceremony.stages === 'object') {
|
|
47
|
+
for (const stage of Object.values(ceremony.stages)) {
|
|
48
|
+
if (stage?.provider) needed.add(normalizeProvider(stage.provider));
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// validation: top-level { model, provider } and/or sub-areas { areaName: { model, provider } }
|
|
53
|
+
if (ceremony?.validation && typeof ceremony.validation === 'object') {
|
|
54
|
+
if (ceremony.validation.provider) needed.add(normalizeProvider(ceremony.validation.provider));
|
|
55
|
+
for (const val of Object.values(ceremony.validation)) {
|
|
56
|
+
if (val && typeof val === 'object' && typeof val.provider === 'string') {
|
|
57
|
+
needed.add(normalizeProvider(val.provider));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const apiKeys = settings.apiKeys ?? {};
|
|
63
|
+
return [...needed].filter((p) => !apiKeys[p]?.isSet);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Step definitions for the progress header (shown steps vary based on hasDb)
|
|
67
|
+
const ALL_STEPS = [
|
|
68
|
+
{ id: 1, label: 'Strategy' },
|
|
69
|
+
{ id: 2, label: 'Mission' },
|
|
70
|
+
{ id: 3, label: 'Database' },
|
|
71
|
+
{ id: 4, label: 'Architecture' },
|
|
72
|
+
{ id: 5, label: 'Review' },
|
|
73
|
+
{ id: 6, label: 'Generate' },
|
|
74
|
+
{ id: 7, label: 'Done' },
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
function StepProgress({ currentStep, hasDb }) {
|
|
78
|
+
const visibleSteps = hasDb ? ALL_STEPS : ALL_STEPS.filter((s) => s.id !== 3);
|
|
79
|
+
// Map real wizardStep → display index
|
|
80
|
+
const getDisplayIndex = (step) => {
|
|
81
|
+
if (!hasDb && step >= 4) return step - 1; // shift steps 4-7 down by 1 when db skipped
|
|
82
|
+
return step;
|
|
83
|
+
};
|
|
84
|
+
const displayCurrent = getDisplayIndex(currentStep);
|
|
85
|
+
|
|
86
|
+
return (
|
|
87
|
+
<div className="flex items-center gap-1 flex-nowrap overflow-x-auto pb-0.5">
|
|
88
|
+
{visibleSteps.map((s, idx) => {
|
|
89
|
+
const displayIdx = idx + 1;
|
|
90
|
+
const isDone = displayCurrent > displayIdx;
|
|
91
|
+
const isCurrent = displayCurrent === displayIdx;
|
|
92
|
+
|
|
93
|
+
return (
|
|
94
|
+
<div key={s.id} className="flex items-center gap-1">
|
|
95
|
+
<div
|
|
96
|
+
className={`flex items-center gap-1 text-xs px-2 py-0.5 rounded-full whitespace-nowrap ${
|
|
97
|
+
isCurrent
|
|
98
|
+
? 'bg-blue-600 text-white font-medium'
|
|
99
|
+
: isDone
|
|
100
|
+
? 'bg-green-100 text-green-700'
|
|
101
|
+
: 'bg-slate-100 text-slate-400'
|
|
102
|
+
}`}
|
|
103
|
+
>
|
|
104
|
+
{isDone ? '✓' : displayIdx} {s.label}
|
|
105
|
+
</div>
|
|
106
|
+
{idx < visibleSteps.length - 1 && (
|
|
107
|
+
<span className="text-slate-300 text-xs">›</span>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
);
|
|
111
|
+
})}
|
|
112
|
+
</div>
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
export function SponsorCallModal({ onClose, onOpenSettings, costLimitPending, onContinuePastCostLimit, onCancelFromCostLimit }) {
|
|
117
|
+
const {
|
|
118
|
+
isOpen,
|
|
119
|
+
wizardStep,
|
|
120
|
+
setWizardStep,
|
|
121
|
+
analyzing,
|
|
122
|
+
setAnalyzing,
|
|
123
|
+
strategy,
|
|
124
|
+
mission,
|
|
125
|
+
initialScope,
|
|
126
|
+
dbResult,
|
|
127
|
+
setDbResult,
|
|
128
|
+
dbChoice,
|
|
129
|
+
archOptions,
|
|
130
|
+
setArchOptions,
|
|
131
|
+
selectedArch,
|
|
132
|
+
applyPrefill,
|
|
133
|
+
requirements,
|
|
134
|
+
ceremonyStatus,
|
|
135
|
+
isPaused,
|
|
136
|
+
setCeremonyStatus,
|
|
137
|
+
setCeremonyResult,
|
|
138
|
+
setCeremonyError,
|
|
139
|
+
appendProgress,
|
|
140
|
+
startRun,
|
|
141
|
+
resetWizard,
|
|
142
|
+
closeWizard,
|
|
143
|
+
setProcessId,
|
|
144
|
+
setStrategy,
|
|
145
|
+
setMission,
|
|
146
|
+
setInitialScope,
|
|
147
|
+
setDbChoice,
|
|
148
|
+
setSelectedArch,
|
|
149
|
+
setPrefillResult,
|
|
150
|
+
setRequirements,
|
|
151
|
+
} = useCeremonyStore();
|
|
152
|
+
|
|
153
|
+
const [analyzingMessage, setAnalyzingMessage] = useState('');
|
|
154
|
+
const [showCancelConfirm, setShowCancelConfirm] = useState(false);
|
|
155
|
+
const [transitioning, setTransitioning] = useState(null); // null | 'pausing' | 'cancelling'
|
|
156
|
+
const [workflowOpen, setWorkflowOpen] = useState(false);
|
|
157
|
+
const [workflowCeremony, setWorkflowCeremony] = useState(null);
|
|
158
|
+
const [workflowModels, setWorkflowModels] = useState([]);
|
|
159
|
+
const [workflowMissionGenValidation, setWorkflowMissionGenValidation] = useState(null);
|
|
160
|
+
const [workflowAllCeremonies, setWorkflowAllCeremonies] = useState([]);
|
|
161
|
+
const [apiKeyCheck, setApiKeyCheck] = useState({ loading: true, missing: [] });
|
|
162
|
+
const [showResumePrompt, setShowResumePrompt] = useState(false);
|
|
163
|
+
const [draftData, setDraftData] = useState(null);
|
|
164
|
+
|
|
165
|
+
// Check required API keys when the modal opens
|
|
166
|
+
useEffect(() => {
|
|
167
|
+
let cancelled = false;
|
|
168
|
+
getSettings()
|
|
169
|
+
.then((s) => {
|
|
170
|
+
if (!cancelled) setApiKeyCheck({ loading: false, missing: computeMissingProviders(s) });
|
|
171
|
+
})
|
|
172
|
+
.catch(() => {
|
|
173
|
+
if (!cancelled) setApiKeyCheck({ loading: false, missing: [] }); // fail open
|
|
174
|
+
});
|
|
175
|
+
return () => { cancelled = true; };
|
|
176
|
+
}, []);
|
|
177
|
+
|
|
178
|
+
// Check for an existing draft when the wizard opens at step 1 (fresh open / post-refresh)
|
|
179
|
+
// Only show resume prompt when ceremony is idle (not during a running ceremony reopen)
|
|
180
|
+
useEffect(() => {
|
|
181
|
+
if (!isOpen || wizardStep !== 1 || ceremonyStatus !== 'idle') return;
|
|
182
|
+
let cancelled = false;
|
|
183
|
+
getSponsorCallDraft().then((draft) => {
|
|
184
|
+
if (!cancelled && draft && draft.wizardStep && draft.wizardStep > 1) {
|
|
185
|
+
setDraftData(draft);
|
|
186
|
+
setShowResumePrompt(true);
|
|
187
|
+
}
|
|
188
|
+
});
|
|
189
|
+
return () => { cancelled = true; };
|
|
190
|
+
}, [isOpen]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
191
|
+
|
|
192
|
+
const recheckKeys = () => {
|
|
193
|
+
setApiKeyCheck({ loading: true, missing: [] });
|
|
194
|
+
getSettings()
|
|
195
|
+
.then((s) => setApiKeyCheck({ loading: false, missing: computeMissingProviders(s) }))
|
|
196
|
+
.catch(() => setApiKeyCheck({ loading: false, missing: [] }));
|
|
197
|
+
};
|
|
198
|
+
|
|
199
|
+
// Snapshot current wizard state and persist to server
|
|
200
|
+
const saveDraft = (overrides = {}) => {
|
|
201
|
+
const snap = useCeremonyStore.getState();
|
|
202
|
+
saveSponsorCallDraft({
|
|
203
|
+
wizardStep: snap.wizardStep,
|
|
204
|
+
strategy: snap.strategy,
|
|
205
|
+
mission: snap.mission,
|
|
206
|
+
initialScope: snap.initialScope,
|
|
207
|
+
dbResult: snap.dbResult,
|
|
208
|
+
dbChoice: snap.dbChoice,
|
|
209
|
+
archOptions: snap.archOptions,
|
|
210
|
+
selectedArch: snap.selectedArch,
|
|
211
|
+
prefillResult: snap.prefillResult,
|
|
212
|
+
requirements: snap.requirements,
|
|
213
|
+
...overrides,
|
|
214
|
+
}).catch(() => {});
|
|
215
|
+
};
|
|
216
|
+
|
|
217
|
+
const handleResumeDraft = () => {
|
|
218
|
+
const d = draftData;
|
|
219
|
+
setShowResumePrompt(false);
|
|
220
|
+
setDraftData(null);
|
|
221
|
+
if (!d) return;
|
|
222
|
+
// Restore all wizard fields from draft
|
|
223
|
+
if (d.strategy != null) setStrategy(d.strategy);
|
|
224
|
+
if (d.mission != null) setMission(d.mission);
|
|
225
|
+
if (d.initialScope != null) setInitialScope(d.initialScope);
|
|
226
|
+
if (d.dbResult != null) setDbResult(d.dbResult);
|
|
227
|
+
if (d.dbChoice != null) setDbChoice(d.dbChoice);
|
|
228
|
+
if (d.archOptions != null) setArchOptions(d.archOptions);
|
|
229
|
+
if (d.selectedArch != null) setSelectedArch(d.selectedArch);
|
|
230
|
+
if (d.prefillResult != null) setPrefillResult(d.prefillResult);
|
|
231
|
+
if (d.requirements != null) setRequirements(d.requirements);
|
|
232
|
+
setWizardStep(d.wizardStep);
|
|
233
|
+
};
|
|
234
|
+
|
|
235
|
+
const handleStartFresh = () => {
|
|
236
|
+
setShowResumePrompt(false);
|
|
237
|
+
setDraftData(null);
|
|
238
|
+
deleteSponsorCallDraft();
|
|
239
|
+
resetWizard();
|
|
240
|
+
};
|
|
241
|
+
|
|
242
|
+
const handleWorkflowSave = async (updatedCeremony, updatedMG) => {
|
|
243
|
+
const base = workflowAllCeremonies.length > 0 ? workflowAllCeremonies : [updatedCeremony];
|
|
244
|
+
const next = base.map((c) => c.name === updatedCeremony.name ? updatedCeremony : c);
|
|
245
|
+
await saveCeremonies(next, { validation: updatedMG });
|
|
246
|
+
setWorkflowCeremony(updatedCeremony);
|
|
247
|
+
setWorkflowMissionGenValidation(updatedMG);
|
|
248
|
+
setWorkflowAllCeremonies(next);
|
|
249
|
+
};
|
|
250
|
+
|
|
251
|
+
const handleWorkflowClose = () => {
|
|
252
|
+
setWorkflowOpen(false);
|
|
253
|
+
recheckKeys();
|
|
254
|
+
};
|
|
255
|
+
|
|
256
|
+
// Clear transitioning state when WS events arrive
|
|
257
|
+
useEffect(() => {
|
|
258
|
+
if (transitioning === 'pausing' && isPaused) setTransitioning(null);
|
|
259
|
+
}, [isPaused, transitioning]);
|
|
260
|
+
|
|
261
|
+
useEffect(() => {
|
|
262
|
+
if (transitioning === 'cancelling' && ceremonyStatus === 'idle') setTransitioning(null);
|
|
263
|
+
}, [ceremonyStatus, transitioning]);
|
|
264
|
+
|
|
265
|
+
if (!isOpen) return null;
|
|
266
|
+
|
|
267
|
+
const hasDb = dbResult?.hasDatabaseNeeds === true;
|
|
268
|
+
|
|
269
|
+
const handlePause = async () => {
|
|
270
|
+
setTransitioning('pausing');
|
|
271
|
+
try { await pauseCeremony(); } catch (_) {}
|
|
272
|
+
};
|
|
273
|
+
|
|
274
|
+
const handleResume = async () => {
|
|
275
|
+
try { await resumeCeremony(); } catch (_) {}
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const handleConfirmCancel = async () => {
|
|
279
|
+
setShowCancelConfirm(false);
|
|
280
|
+
setTransitioning('cancelling');
|
|
281
|
+
try { await cancelCeremony(); } catch (_) {}
|
|
282
|
+
};
|
|
283
|
+
|
|
284
|
+
// Close handler
|
|
285
|
+
const handleClose = () => {
|
|
286
|
+
if (ceremonyStatus === 'running') return; // block close while running
|
|
287
|
+
closeWizard();
|
|
288
|
+
if (onClose) onClose();
|
|
289
|
+
};
|
|
290
|
+
|
|
291
|
+
// Step 2 → (DB analysis) → Step 3 or 4
|
|
292
|
+
const handleMissionNext = async () => {
|
|
293
|
+
setAnalyzing(true);
|
|
294
|
+
setAnalyzingMessage('Checking database needs…');
|
|
295
|
+
try {
|
|
296
|
+
const dbData = await analyzeDatabase(mission, initialScope, strategy);
|
|
297
|
+
setDbResult(dbData);
|
|
298
|
+
|
|
299
|
+
if (dbData.hasDatabaseNeeds) {
|
|
300
|
+
setWizardStep(3); // Show database step
|
|
301
|
+
saveDraft({ wizardStep: 3, dbResult: dbData });
|
|
302
|
+
} else {
|
|
303
|
+
// Skip database step — go straight to architecture
|
|
304
|
+
setAnalyzingMessage('Analysing architecture options…');
|
|
305
|
+
const archData = await analyzeArchitecture(mission, initialScope, null, strategy);
|
|
306
|
+
setArchOptions(archData);
|
|
307
|
+
setWizardStep(4);
|
|
308
|
+
saveDraft({ wizardStep: 4, dbResult: dbData, archOptions: archData });
|
|
309
|
+
}
|
|
310
|
+
} catch (err) {
|
|
311
|
+
console.error('Mission analysis error:', err);
|
|
312
|
+
alert(`Analysis failed: ${err.message}`);
|
|
313
|
+
} finally {
|
|
314
|
+
setAnalyzing(false);
|
|
315
|
+
setAnalyzingMessage('');
|
|
316
|
+
}
|
|
317
|
+
};
|
|
318
|
+
|
|
319
|
+
// Step 3 → Architecture analysis → Step 4
|
|
320
|
+
const handleDatabaseNext = async () => {
|
|
321
|
+
setAnalyzing(true);
|
|
322
|
+
setAnalyzingMessage('Analysing architecture options…');
|
|
323
|
+
try {
|
|
324
|
+
const dbContext = dbResult ? { ...dbResult, userChoice: dbChoice } : null;
|
|
325
|
+
const archData = await analyzeArchitecture(mission, initialScope, dbContext, strategy);
|
|
326
|
+
setArchOptions(archData);
|
|
327
|
+
setWizardStep(4);
|
|
328
|
+
saveDraft({ wizardStep: 4, archOptions: archData });
|
|
329
|
+
} catch (err) {
|
|
330
|
+
console.error('Architecture analysis error:', err);
|
|
331
|
+
alert(`Analysis failed: ${err.message}`);
|
|
332
|
+
} finally {
|
|
333
|
+
setAnalyzing(false);
|
|
334
|
+
setAnalyzingMessage('');
|
|
335
|
+
}
|
|
336
|
+
};
|
|
337
|
+
|
|
338
|
+
// Step 4 → Prefill → Step 5
|
|
339
|
+
const handleArchitectureNext = async () => {
|
|
340
|
+
setAnalyzing(true);
|
|
341
|
+
setAnalyzingMessage('Pre-filling requirements from your selections…');
|
|
342
|
+
try {
|
|
343
|
+
const dbContext = dbResult ? { ...dbResult, userChoice: dbChoice } : null;
|
|
344
|
+
const prefill = await prefillAnswers(mission, initialScope, selectedArch, dbContext, strategy);
|
|
345
|
+
applyPrefill(prefill, strategy, mission, initialScope);
|
|
346
|
+
setWizardStep(5);
|
|
347
|
+
// Save after applyPrefill updates requirements in the store
|
|
348
|
+
setTimeout(() => saveDraft({ wizardStep: 5, prefillResult: prefill }), 0);
|
|
349
|
+
} catch (err) {
|
|
350
|
+
console.error('Prefill error:', err);
|
|
351
|
+
alert(`Prefill failed: ${err.message}`);
|
|
352
|
+
} finally {
|
|
353
|
+
setAnalyzing(false);
|
|
354
|
+
setAnalyzingMessage('');
|
|
355
|
+
}
|
|
356
|
+
};
|
|
357
|
+
|
|
358
|
+
// Step 5 → Run ceremony → Step 6
|
|
359
|
+
const handleReviewNext = async () => {
|
|
360
|
+
deleteSponsorCallDraft(); // ceremony is starting — draft no longer needed
|
|
361
|
+
try {
|
|
362
|
+
startRun();
|
|
363
|
+
setWizardStep(6);
|
|
364
|
+
const result = await runCeremony(requirements);
|
|
365
|
+
if (result?.processId) setProcessId(result.processId);
|
|
366
|
+
} catch (err) {
|
|
367
|
+
console.error('Run ceremony error:', err);
|
|
368
|
+
setCeremonyStatus('error');
|
|
369
|
+
setCeremonyError(err.message);
|
|
370
|
+
}
|
|
371
|
+
};
|
|
372
|
+
|
|
373
|
+
const renderStep = () => {
|
|
374
|
+
switch (wizardStep) {
|
|
375
|
+
case 1:
|
|
376
|
+
return (
|
|
377
|
+
<DeploymentStep
|
|
378
|
+
onNext={() => {
|
|
379
|
+
setWizardStep(2);
|
|
380
|
+
saveDraft({ wizardStep: 2 });
|
|
381
|
+
}}
|
|
382
|
+
/>
|
|
383
|
+
);
|
|
384
|
+
case 2:
|
|
385
|
+
return <MissionStep onNext={handleMissionNext} onBack={() => setWizardStep(1)} analyzing={analyzing} onOpenSettings={onOpenSettings} />;
|
|
386
|
+
case 3:
|
|
387
|
+
return <DatabaseStep onNext={handleDatabaseNext} onBack={() => setWizardStep(2)} analyzing={analyzing} />;
|
|
388
|
+
case 4:
|
|
389
|
+
return <ArchitectureStep onNext={handleArchitectureNext} onBack={() => setWizardStep(hasDb ? 3 : 2)} analyzing={analyzing} onOpenSettings={onOpenSettings} />;
|
|
390
|
+
case 5:
|
|
391
|
+
return <ReviewAnswersStep onNext={handleReviewNext} onBack={() => setWizardStep(4)} />;
|
|
392
|
+
case 6:
|
|
393
|
+
return (
|
|
394
|
+
<RunningStep
|
|
395
|
+
transitioning={transitioning}
|
|
396
|
+
onPause={handlePause}
|
|
397
|
+
onResume={handleResume}
|
|
398
|
+
onCancel={() => setShowCancelConfirm(true)}
|
|
399
|
+
onBackground={closeWizard}
|
|
400
|
+
/>
|
|
401
|
+
);
|
|
402
|
+
case 7:
|
|
403
|
+
return <CompleteStep onClose={handleClose} />;
|
|
404
|
+
default:
|
|
405
|
+
return null;
|
|
406
|
+
}
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
return (
|
|
410
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
|
411
|
+
{/* Backdrop */}
|
|
412
|
+
<div
|
|
413
|
+
className="absolute inset-0 bg-black/40"
|
|
414
|
+
onClick={wizardStep !== 6 ? handleClose : undefined}
|
|
415
|
+
/>
|
|
416
|
+
|
|
417
|
+
{/* Modal */}
|
|
418
|
+
<div className="relative bg-white rounded-2xl shadow-2xl w-full max-w-3xl mx-4 max-h-[90vh] flex flex-col">
|
|
419
|
+
{/* Resume draft overlay */}
|
|
420
|
+
{showResumePrompt && (
|
|
421
|
+
<div className="absolute inset-0 z-10 flex items-center justify-center bg-white/90 rounded-2xl">
|
|
422
|
+
<div className="bg-white border border-slate-200 rounded-xl shadow-lg p-6 max-w-sm mx-4 text-center space-y-4">
|
|
423
|
+
<p className="text-base font-semibold text-slate-900">Resume previous session?</p>
|
|
424
|
+
<p className="text-sm text-slate-500">
|
|
425
|
+
A previous wizard session was saved
|
|
426
|
+
{draftData?.savedAt ? ` on ${new Date(draftData.savedAt).toLocaleDateString(undefined, { month: 'short', day: 'numeric', hour: '2-digit', minute: '2-digit' })}` : ''}.
|
|
427
|
+
Would you like to continue where you left off?
|
|
428
|
+
</p>
|
|
429
|
+
<div className="flex gap-3 justify-center pt-1">
|
|
430
|
+
<button
|
|
431
|
+
onClick={handleStartFresh}
|
|
432
|
+
className="px-4 py-2 text-sm rounded-lg border border-slate-200 text-slate-700 hover:bg-slate-50"
|
|
433
|
+
>
|
|
434
|
+
Start Fresh
|
|
435
|
+
</button>
|
|
436
|
+
<button
|
|
437
|
+
onClick={handleResumeDraft}
|
|
438
|
+
className="px-4 py-2 text-sm rounded-lg bg-blue-600 text-white hover:bg-blue-700"
|
|
439
|
+
>
|
|
440
|
+
Resume
|
|
441
|
+
</button>
|
|
442
|
+
</div>
|
|
443
|
+
</div>
|
|
444
|
+
</div>
|
|
445
|
+
)}
|
|
446
|
+
|
|
447
|
+
{/* Cost-limit pause overlay */}
|
|
448
|
+
{costLimitPending && (
|
|
449
|
+
<div className="absolute inset-0 z-20 flex items-center justify-center bg-white/90 rounded-2xl">
|
|
450
|
+
<div className="bg-white border border-amber-200 rounded-xl shadow-lg p-6 max-w-sm mx-4 text-center space-y-4">
|
|
451
|
+
<div className="text-3xl">⚠️</div>
|
|
452
|
+
<p className="text-base font-semibold text-slate-900">Cost Limit Reached</p>
|
|
453
|
+
<p className="text-sm text-slate-600">
|
|
454
|
+
<span className="font-mono font-medium">${costLimitPending.cost.toFixed(4)}</span> spent
|
|
455
|
+
{costLimitPending.threshold != null && (
|
|
456
|
+
<> (limit: <span className="font-mono">${Number(costLimitPending.threshold).toFixed(2)}</span>)</>
|
|
457
|
+
)}
|
|
458
|
+
</p>
|
|
459
|
+
<p className="text-sm text-slate-500">
|
|
460
|
+
The ceremony is paused. What would you like to do?
|
|
461
|
+
</p>
|
|
462
|
+
<div className="flex gap-3 justify-center pt-1">
|
|
463
|
+
<button
|
|
464
|
+
onClick={onContinuePastCostLimit}
|
|
465
|
+
className="px-4 py-2 text-sm rounded-lg bg-slate-900 text-white hover:bg-slate-700"
|
|
466
|
+
>
|
|
467
|
+
Continue Anyway
|
|
468
|
+
</button>
|
|
469
|
+
<button
|
|
470
|
+
onClick={onCancelFromCostLimit}
|
|
471
|
+
className="px-4 py-2 text-sm rounded-lg border border-red-200 text-red-600 hover:bg-red-50"
|
|
472
|
+
>
|
|
473
|
+
Cancel Ceremony
|
|
474
|
+
</button>
|
|
475
|
+
</div>
|
|
476
|
+
<p className="text-xs text-slate-400">
|
|
477
|
+
Continue disables cost checking for the rest of this run.
|
|
478
|
+
</p>
|
|
479
|
+
</div>
|
|
480
|
+
</div>
|
|
481
|
+
)}
|
|
482
|
+
|
|
483
|
+
{/* Cancel confirmation overlay */}
|
|
484
|
+
{showCancelConfirm && (
|
|
485
|
+
<div className="absolute inset-0 z-10 flex items-center justify-center bg-white/90 rounded-2xl">
|
|
486
|
+
<div className="bg-white border border-slate-200 rounded-xl shadow-lg p-6 max-w-sm mx-4 text-center space-y-4">
|
|
487
|
+
<p className="text-base font-semibold text-slate-900">Stop documentation generation?</p>
|
|
488
|
+
<p className="text-sm text-slate-500">
|
|
489
|
+
You can restart the ceremony later. Any files written so far will be kept.
|
|
490
|
+
</p>
|
|
491
|
+
<div className="flex gap-3 justify-center pt-1">
|
|
492
|
+
<button
|
|
493
|
+
onClick={() => setShowCancelConfirm(false)}
|
|
494
|
+
className="px-4 py-2 text-sm rounded-lg border border-slate-200 text-slate-700 hover:bg-slate-50"
|
|
495
|
+
>
|
|
496
|
+
Keep Running
|
|
497
|
+
</button>
|
|
498
|
+
<button
|
|
499
|
+
onClick={handleConfirmCancel}
|
|
500
|
+
className="px-4 py-2 text-sm rounded-lg bg-red-600 text-white hover:bg-red-700"
|
|
501
|
+
>
|
|
502
|
+
Cancel Run
|
|
503
|
+
</button>
|
|
504
|
+
</div>
|
|
505
|
+
</div>
|
|
506
|
+
</div>
|
|
507
|
+
)}
|
|
508
|
+
|
|
509
|
+
{/* Header */}
|
|
510
|
+
<div className="flex items-start justify-between px-6 pt-5 pb-4 border-b border-slate-200 flex-shrink-0">
|
|
511
|
+
<div className="min-w-0 flex-1">
|
|
512
|
+
<h1 className="text-base font-semibold text-slate-900">Sponsor Call Ceremony</h1>
|
|
513
|
+
<div className="mt-2">
|
|
514
|
+
<StepProgress currentStep={wizardStep} hasDb={hasDb} />
|
|
515
|
+
</div>
|
|
516
|
+
</div>
|
|
517
|
+
<div className="flex items-center gap-3 ml-4 mt-0.5 flex-shrink-0">
|
|
518
|
+
{ceremonyStatus !== 'running' && (
|
|
519
|
+
<button
|
|
520
|
+
type="button"
|
|
521
|
+
onClick={async () => {
|
|
522
|
+
try {
|
|
523
|
+
const [s, m] = await Promise.all([getSettings(), getModels()]);
|
|
524
|
+
const sc = s.ceremonies?.find((c) => c.name === 'sponsor-call') ?? {};
|
|
525
|
+
setWorkflowCeremony(sc);
|
|
526
|
+
setWorkflowModels(m);
|
|
527
|
+
setWorkflowMissionGenValidation(s.missionGenerator?.validation ?? null);
|
|
528
|
+
setWorkflowAllCeremonies(s.ceremonies || []);
|
|
529
|
+
setWorkflowOpen(true);
|
|
530
|
+
} catch {}
|
|
531
|
+
}}
|
|
532
|
+
className="flex items-center gap-1 text-xs text-slate-400 hover:text-blue-500 transition-colors whitespace-nowrap"
|
|
533
|
+
title="View ceremony workflow"
|
|
534
|
+
>
|
|
535
|
+
<Info className="w-3.5 h-3.5" />
|
|
536
|
+
How it works
|
|
537
|
+
</button>
|
|
538
|
+
)}
|
|
539
|
+
{ceremonyStatus !== 'running' && (
|
|
540
|
+
<button
|
|
541
|
+
onClick={handleClose}
|
|
542
|
+
className="text-slate-400 hover:text-slate-600 transition-colors"
|
|
543
|
+
>
|
|
544
|
+
<X className="w-5 h-5" />
|
|
545
|
+
</button>
|
|
546
|
+
)}
|
|
547
|
+
</div>
|
|
548
|
+
</div>
|
|
549
|
+
|
|
550
|
+
{/* Scrollable content */}
|
|
551
|
+
<div className="flex-1 overflow-y-auto px-6 py-5">
|
|
552
|
+
{apiKeyCheck.loading ? (
|
|
553
|
+
<div className="flex items-center justify-center py-12">
|
|
554
|
+
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600" />
|
|
555
|
+
</div>
|
|
556
|
+
) : apiKeyCheck.missing.length > 0 ? (
|
|
557
|
+
<div className="flex flex-col gap-5">
|
|
558
|
+
<div className="flex items-start gap-3 p-4 bg-amber-50 border border-amber-200 rounded-lg">
|
|
559
|
+
<AlertTriangle className="w-5 h-5 text-amber-500 flex-shrink-0 mt-0.5" />
|
|
560
|
+
<div>
|
|
561
|
+
<p className="text-sm font-semibold text-amber-900">API Keys Required</p>
|
|
562
|
+
<p className="text-xs text-amber-700 mt-1">
|
|
563
|
+
Configure the following API keys before running the Sponsor Call ceremony:
|
|
564
|
+
</p>
|
|
565
|
+
<ul className="mt-2 space-y-1.5">
|
|
566
|
+
{apiKeyCheck.missing.map((p) => (
|
|
567
|
+
<li key={p} className="flex items-center gap-2 text-xs text-amber-800">
|
|
568
|
+
<span className="w-1.5 h-1.5 rounded-full bg-amber-400 flex-shrink-0" />
|
|
569
|
+
{KEY_LABELS[p] || p}
|
|
570
|
+
</li>
|
|
571
|
+
))}
|
|
572
|
+
</ul>
|
|
573
|
+
</div>
|
|
574
|
+
</div>
|
|
575
|
+
<div className="flex items-center gap-3">
|
|
576
|
+
<button
|
|
577
|
+
onClick={() => onOpenSettings?.()}
|
|
578
|
+
className="flex items-center gap-1.5 px-4 py-2 text-sm font-medium bg-slate-900 text-white rounded-lg hover:bg-slate-700 transition-colors"
|
|
579
|
+
>
|
|
580
|
+
<SettingsIcon className="w-4 h-4" />
|
|
581
|
+
Open Settings
|
|
582
|
+
</button>
|
|
583
|
+
<button
|
|
584
|
+
onClick={recheckKeys}
|
|
585
|
+
className="px-3 py-2 text-sm text-slate-500 hover:text-slate-800 transition-colors"
|
|
586
|
+
>
|
|
587
|
+
Re-check
|
|
588
|
+
</button>
|
|
589
|
+
</div>
|
|
590
|
+
</div>
|
|
591
|
+
) : (
|
|
592
|
+
renderStep()
|
|
593
|
+
)}
|
|
594
|
+
</div>
|
|
595
|
+
|
|
596
|
+
{/* Status bar — always rendered to prevent height flicker */}
|
|
597
|
+
<div className="flex-shrink-0 border-t border-slate-100 px-6 h-8 flex items-center gap-2">
|
|
598
|
+
{analyzingMessage && (
|
|
599
|
+
<span className="w-3 h-3 border-2 border-blue-300 border-t-blue-600 rounded-full animate-spin flex-shrink-0" />
|
|
600
|
+
)}
|
|
601
|
+
<p className={`text-xs truncate ${analyzingMessage ? 'text-blue-600 font-medium' : 'text-slate-400'}`}>
|
|
602
|
+
{analyzingMessage}
|
|
603
|
+
</p>
|
|
604
|
+
</div>
|
|
605
|
+
</div>
|
|
606
|
+
|
|
607
|
+
{workflowOpen && workflowCeremony && (
|
|
608
|
+
<CeremonyWorkflowModal
|
|
609
|
+
ceremony={workflowCeremony}
|
|
610
|
+
models={workflowModels}
|
|
611
|
+
missionGenValidation={workflowMissionGenValidation}
|
|
612
|
+
readOnly={ceremonyStatus === 'running'}
|
|
613
|
+
onSave={ceremonyStatus !== 'running' ? handleWorkflowSave : undefined}
|
|
614
|
+
onClose={handleWorkflowClose}
|
|
615
|
+
/>
|
|
616
|
+
)}
|
|
617
|
+
</div>
|
|
618
|
+
);
|
|
619
|
+
}
|