@agile-vibe-coding/avc 0.2.3 → 0.3.2
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 +475 -3
- 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
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
import { useState, useRef, useEffect } from 'react';
|
|
2
|
+
import { ChevronDown, Check } from 'lucide-react';
|
|
3
|
+
import { saveCeremonies, getLocalModels } from '../../lib/api';
|
|
4
|
+
|
|
5
|
+
// Map ceremony provider name → apiKeys property name
|
|
6
|
+
const PROVIDER_TO_KEY = { claude: 'anthropic', gemini: 'gemini', openai: 'openai', xiaomi: 'xiaomi', local: 'local' };
|
|
7
|
+
// Display labels (extendable as new providers are added)
|
|
8
|
+
const PROVIDER_LABELS = { claude: 'Claude', gemini: 'Gemini', openai: 'OpenAI', xiaomi: 'Xiaomi MiMo', local: 'Local LLM', custom: 'Custom' };
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Inspect all stage and validation provider fields to determine whether a
|
|
12
|
+
* ceremony is using a single provider or a mix.
|
|
13
|
+
* Returns the provider name when uniform, or 'custom' when mixed.
|
|
14
|
+
*/
|
|
15
|
+
function detectProvider(ceremony) {
|
|
16
|
+
const providers = new Set();
|
|
17
|
+
for (const stage of Object.values(ceremony?.stages || {})) {
|
|
18
|
+
if (stage?.provider) providers.add(stage.provider);
|
|
19
|
+
}
|
|
20
|
+
// Sponsor-call validation sub-models
|
|
21
|
+
if (ceremony?.validation?.provider) providers.add(ceremony.validation.provider);
|
|
22
|
+
if (ceremony?.validation?.documentation?.provider) providers.add(ceremony.validation.documentation.provider);
|
|
23
|
+
if (ceremony?.validation?.refinement?.provider) providers.add(ceremony.validation.refinement.provider);
|
|
24
|
+
|
|
25
|
+
if (providers.size === 0) return ceremony?.provider || null;
|
|
26
|
+
if (providers.size === 1) return [...providers][0];
|
|
27
|
+
return 'custom';
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Apply a provider preset to a ceremony object (immutable).
|
|
32
|
+
* Merges preset provider/model/stages while preserving non-model keys
|
|
33
|
+
* (useContextualSelection, maxIterations, etc.) from the existing config.
|
|
34
|
+
*/
|
|
35
|
+
function applyProviderPreset(ceremony, providerKey) {
|
|
36
|
+
const preset = ceremony.providerPresets?.[providerKey];
|
|
37
|
+
if (!preset) return ceremony;
|
|
38
|
+
|
|
39
|
+
const updated = { ...ceremony, provider: preset.provider, defaultModel: preset.defaultModel };
|
|
40
|
+
|
|
41
|
+
// Merge stages: start from current stages to preserve extra keys, overlay preset values
|
|
42
|
+
const newStages = {};
|
|
43
|
+
const allStageNames = new Set([
|
|
44
|
+
...Object.keys(updated.stages || {}),
|
|
45
|
+
...Object.keys(preset.stages || {}),
|
|
46
|
+
]);
|
|
47
|
+
for (const stageName of allStageNames) {
|
|
48
|
+
const existing = updated.stages?.[stageName] ?? {};
|
|
49
|
+
const presetStage = preset.stages?.[stageName];
|
|
50
|
+
if (presetStage) {
|
|
51
|
+
newStages[stageName] = { ...existing, provider: presetStage.provider, model: presetStage.model };
|
|
52
|
+
} else {
|
|
53
|
+
newStages[stageName] = existing;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
updated.stages = newStages;
|
|
57
|
+
|
|
58
|
+
// Handle validation (sponsor-call specific)
|
|
59
|
+
if (preset.validation && updated.validation) {
|
|
60
|
+
updated.validation = {
|
|
61
|
+
...updated.validation,
|
|
62
|
+
provider: preset.validation.provider,
|
|
63
|
+
model: preset.validation.model,
|
|
64
|
+
};
|
|
65
|
+
if (preset.validation.refinement && updated.validation.refinement) {
|
|
66
|
+
updated.validation.refinement = {
|
|
67
|
+
...updated.validation.refinement,
|
|
68
|
+
provider: preset.validation.refinement.provider,
|
|
69
|
+
model: preset.validation.refinement.model,
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
if (preset.validation.documentation && updated.validation.documentation) {
|
|
73
|
+
updated.validation.documentation = {
|
|
74
|
+
...updated.validation.documentation,
|
|
75
|
+
provider: preset.validation.documentation.provider,
|
|
76
|
+
model: preset.validation.documentation.model,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return updated;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Apply a local model to all stages of a ceremony (immutable).
|
|
86
|
+
* Sets every stage + validation area to { provider: 'local', model: modelId }.
|
|
87
|
+
*/
|
|
88
|
+
function applyLocalModel(ceremony, modelId) {
|
|
89
|
+
const updated = { ...ceremony, provider: 'local', defaultModel: modelId };
|
|
90
|
+
|
|
91
|
+
// Set all stages to local
|
|
92
|
+
const newStages = {};
|
|
93
|
+
for (const [stageName, existing] of Object.entries(updated.stages || {})) {
|
|
94
|
+
newStages[stageName] = { ...existing, provider: 'local', model: modelId };
|
|
95
|
+
}
|
|
96
|
+
updated.stages = newStages;
|
|
97
|
+
|
|
98
|
+
// Set validation (sponsor-call specific)
|
|
99
|
+
if (updated.validation) {
|
|
100
|
+
updated.validation = { ...updated.validation, provider: 'local', model: modelId };
|
|
101
|
+
if (updated.validation.documentation) {
|
|
102
|
+
updated.validation.documentation = { ...updated.validation.documentation, provider: 'local', model: modelId };
|
|
103
|
+
}
|
|
104
|
+
if (updated.validation.refinement) {
|
|
105
|
+
updated.validation.refinement = { ...updated.validation.refinement, provider: 'local', model: modelId };
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return updated;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
export function ProviderSwitcherButton({ ceremonyName, ceremonies, apiKeys, onApplied }) {
|
|
113
|
+
const [isOpen, setIsOpen] = useState(false);
|
|
114
|
+
const [saving, setSaving] = useState(false);
|
|
115
|
+
const [localServers, setLocalServers] = useState(null); // null = not yet probed
|
|
116
|
+
const [localExpanded, setLocalExpanded] = useState(false);
|
|
117
|
+
const dropdownRef = useRef(null);
|
|
118
|
+
|
|
119
|
+
const ceremony = ceremonies?.find((c) => c.name === ceremonyName);
|
|
120
|
+
const currentProvider = detectProvider(ceremony);
|
|
121
|
+
const presets = ceremony?.providerPresets;
|
|
122
|
+
|
|
123
|
+
// Close dropdown on outside click
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (!isOpen) return;
|
|
126
|
+
const handler = (e) => {
|
|
127
|
+
if (dropdownRef.current && !dropdownRef.current.contains(e.target)) {
|
|
128
|
+
setIsOpen(false);
|
|
129
|
+
setLocalExpanded(false);
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
document.addEventListener('mousedown', handler);
|
|
133
|
+
return () => document.removeEventListener('mousedown', handler);
|
|
134
|
+
}, [isOpen]);
|
|
135
|
+
|
|
136
|
+
// Probe local servers when dropdown opens
|
|
137
|
+
useEffect(() => {
|
|
138
|
+
if (!isOpen || localServers !== null) return;
|
|
139
|
+
getLocalModels()
|
|
140
|
+
.then((data) => setLocalServers(data.servers || []))
|
|
141
|
+
.catch(() => setLocalServers([]));
|
|
142
|
+
}, [isOpen, localServers]);
|
|
143
|
+
|
|
144
|
+
if (!presets || Object.keys(presets).length === 0) return null;
|
|
145
|
+
|
|
146
|
+
const providerKeys = Object.keys(presets);
|
|
147
|
+
const currentLabel = PROVIDER_LABELS[currentProvider] || currentProvider || '—';
|
|
148
|
+
|
|
149
|
+
const handleSelect = async (providerKey) => {
|
|
150
|
+
if (!ceremony || providerKey === currentProvider) { setIsOpen(false); return; }
|
|
151
|
+
setIsOpen(false);
|
|
152
|
+
setLocalExpanded(false);
|
|
153
|
+
setSaving(true);
|
|
154
|
+
try {
|
|
155
|
+
const updated = applyProviderPreset(ceremony, providerKey);
|
|
156
|
+
const updatedCeremonies = ceremonies.map((c) => c.name === ceremonyName ? updated : c);
|
|
157
|
+
await saveCeremonies(updatedCeremonies, null);
|
|
158
|
+
onApplied(updatedCeremonies);
|
|
159
|
+
} finally {
|
|
160
|
+
setSaving(false);
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
|
|
164
|
+
const handleSelectLocalModel = async (modelId) => {
|
|
165
|
+
if (!ceremony) return;
|
|
166
|
+
setIsOpen(false);
|
|
167
|
+
setLocalExpanded(false);
|
|
168
|
+
setSaving(true);
|
|
169
|
+
try {
|
|
170
|
+
const updated = applyLocalModel(ceremony, modelId);
|
|
171
|
+
const updatedCeremonies = ceremonies.map((c) => c.name === ceremonyName ? updated : c);
|
|
172
|
+
await saveCeremonies(updatedCeremonies, null);
|
|
173
|
+
onApplied(updatedCeremonies);
|
|
174
|
+
} finally {
|
|
175
|
+
setSaving(false);
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
|
|
179
|
+
const allLocalModels = (localServers || []).flatMap((srv) =>
|
|
180
|
+
srv.models.map((m) => ({ id: m.id, server: srv.label }))
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
return (
|
|
184
|
+
<div className="relative" ref={dropdownRef}>
|
|
185
|
+
<button
|
|
186
|
+
type="button"
|
|
187
|
+
disabled={saving}
|
|
188
|
+
onClick={() => { setIsOpen((o) => !o); setLocalExpanded(false); }}
|
|
189
|
+
className="flex items-center gap-1 text-xs px-2 py-1 rounded-md bg-slate-100 hover:bg-slate-200 text-slate-600 hover:text-slate-800 transition-colors disabled:opacity-60"
|
|
190
|
+
title="Switch AI provider preset"
|
|
191
|
+
>
|
|
192
|
+
{saving ? (
|
|
193
|
+
<span className="w-3 h-3 border border-slate-400 border-t-slate-700 rounded-full animate-spin" />
|
|
194
|
+
) : (
|
|
195
|
+
<span className="font-medium">⚡ {currentLabel}</span>
|
|
196
|
+
)}
|
|
197
|
+
{!saving && <ChevronDown className="w-3 h-3" />}
|
|
198
|
+
</button>
|
|
199
|
+
{isOpen && (
|
|
200
|
+
<div className="absolute right-0 mt-1 w-56 bg-white border border-slate-200 rounded-lg shadow-lg z-50 py-1">
|
|
201
|
+
{/* Cloud provider presets (local handled separately below) */}
|
|
202
|
+
{providerKeys.filter((k) => k !== 'local').map((key) => {
|
|
203
|
+
const label = PROVIDER_LABELS[key] || key;
|
|
204
|
+
const apiKeyProp = PROVIDER_TO_KEY[key];
|
|
205
|
+
const hasKey = apiKeys?.[apiKeyProp]?.isSet ?? false;
|
|
206
|
+
const isCurrent = key === currentProvider;
|
|
207
|
+
return (
|
|
208
|
+
<button
|
|
209
|
+
key={key}
|
|
210
|
+
type="button"
|
|
211
|
+
onClick={() => handleSelect(key)}
|
|
212
|
+
className="w-full flex items-center justify-between px-3 py-2 text-sm text-left hover:bg-slate-50 transition-colors"
|
|
213
|
+
>
|
|
214
|
+
<span className={isCurrent ? 'font-medium text-slate-900' : 'text-slate-700'}>
|
|
215
|
+
{label}
|
|
216
|
+
</span>
|
|
217
|
+
<div className="flex items-center gap-1.5">
|
|
218
|
+
{isCurrent && <Check className="w-3.5 h-3.5 text-blue-500" />}
|
|
219
|
+
<span
|
|
220
|
+
className={`text-xs px-1.5 py-0.5 rounded-full ${
|
|
221
|
+
hasKey ? 'bg-green-100 text-green-700' : 'bg-slate-100 text-slate-400'
|
|
222
|
+
}`}
|
|
223
|
+
>
|
|
224
|
+
{hasKey ? 'key set' : 'no key'}
|
|
225
|
+
</span>
|
|
226
|
+
</div>
|
|
227
|
+
</button>
|
|
228
|
+
);
|
|
229
|
+
})}
|
|
230
|
+
|
|
231
|
+
{/* Divider */}
|
|
232
|
+
<div className="border-t border-slate-100 my-1" />
|
|
233
|
+
|
|
234
|
+
{/* Local LLM option */}
|
|
235
|
+
<button
|
|
236
|
+
type="button"
|
|
237
|
+
onClick={() => {
|
|
238
|
+
if (allLocalModels.length === 1) {
|
|
239
|
+
handleSelectLocalModel(allLocalModels[0].id);
|
|
240
|
+
} else {
|
|
241
|
+
setLocalExpanded((v) => !v);
|
|
242
|
+
}
|
|
243
|
+
}}
|
|
244
|
+
className="w-full flex items-center justify-between px-3 py-2 text-sm text-left hover:bg-slate-50 transition-colors"
|
|
245
|
+
>
|
|
246
|
+
<span className={currentProvider === 'local' ? 'font-medium text-slate-900' : 'text-slate-700'}>
|
|
247
|
+
🖥 Local LLM
|
|
248
|
+
</span>
|
|
249
|
+
<div className="flex items-center gap-1.5">
|
|
250
|
+
{currentProvider === 'local' && <Check className="w-3.5 h-3.5 text-blue-500" />}
|
|
251
|
+
{localServers === null ? (
|
|
252
|
+
<span className="w-3 h-3 border border-slate-300 border-t-slate-500 rounded-full animate-spin" />
|
|
253
|
+
) : allLocalModels.length > 0 ? (
|
|
254
|
+
<span className="text-xs px-1.5 py-0.5 rounded-full bg-green-100 text-green-700">
|
|
255
|
+
{allLocalModels.length} model{allLocalModels.length !== 1 ? 's' : ''}
|
|
256
|
+
</span>
|
|
257
|
+
) : (
|
|
258
|
+
<span className="text-xs px-1.5 py-0.5 rounded-full bg-slate-100 text-slate-400">
|
|
259
|
+
offline
|
|
260
|
+
</span>
|
|
261
|
+
)}
|
|
262
|
+
</div>
|
|
263
|
+
</button>
|
|
264
|
+
|
|
265
|
+
{/* Expanded local model list */}
|
|
266
|
+
{localExpanded && allLocalModels.length > 0 && (
|
|
267
|
+
<div className="border-t border-slate-100 bg-slate-50 py-1 max-h-48 overflow-y-auto">
|
|
268
|
+
{allLocalModels.map((lm) => (
|
|
269
|
+
<button
|
|
270
|
+
key={`${lm.server}-${lm.id}`}
|
|
271
|
+
type="button"
|
|
272
|
+
onClick={() => handleSelectLocalModel(lm.id)}
|
|
273
|
+
className="w-full px-4 py-1.5 text-xs text-left hover:bg-slate-100 transition-colors flex items-center justify-between gap-2"
|
|
274
|
+
>
|
|
275
|
+
<span className="text-slate-700 truncate">{lm.id}</span>
|
|
276
|
+
<span className="text-[10px] text-slate-400 flex-shrink-0">{lm.server}</span>
|
|
277
|
+
</button>
|
|
278
|
+
))}
|
|
279
|
+
</div>
|
|
280
|
+
)}
|
|
281
|
+
{localExpanded && allLocalModels.length === 0 && (
|
|
282
|
+
<div className="px-4 py-2 text-xs text-slate-400">
|
|
283
|
+
No local server detected. Start LM Studio, Ollama, or similar.
|
|
284
|
+
</div>
|
|
285
|
+
)}
|
|
286
|
+
</div>
|
|
287
|
+
)}
|
|
288
|
+
</div>
|
|
289
|
+
);
|
|
290
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { useState, useEffect } from 'react';
|
|
2
|
-
import { X,
|
|
1
|
+
import { useState, useEffect, useCallback } from 'react';
|
|
2
|
+
import { X, AlertTriangle, Settings as SettingsIcon } from 'lucide-react';
|
|
3
3
|
import { useCeremonyStore } from '../../store/ceremonyStore';
|
|
4
4
|
import {
|
|
5
5
|
analyzeDatabase,
|
|
@@ -30,6 +30,7 @@ const KEY_LABELS = {
|
|
|
30
30
|
anthropic: 'Anthropic API Key (ANTHROPIC_API_KEY)',
|
|
31
31
|
gemini: 'Google Gemini API Key (GEMINI_API_KEY)',
|
|
32
32
|
openai: 'OpenAI API Key (OPENAI_API_KEY)',
|
|
33
|
+
xiaomi: 'Xiaomi MiMo API Key (XIAOMI_API_KEY)',
|
|
33
34
|
};
|
|
34
35
|
|
|
35
36
|
function normalizeProvider(provider = '') {
|
|
@@ -60,7 +61,7 @@ function computeMissingProviders(settings) {
|
|
|
60
61
|
}
|
|
61
62
|
|
|
62
63
|
const apiKeys = settings.apiKeys ?? {};
|
|
63
|
-
return [...needed].filter((p) => !apiKeys[p]?.isSet);
|
|
64
|
+
return [...needed].filter((p) => p !== 'local' && !apiKeys[p]?.isSet);
|
|
64
65
|
}
|
|
65
66
|
|
|
66
67
|
// Step definitions for the progress header (shown steps vary based on hasDb)
|
|
@@ -151,6 +152,8 @@ export function SponsorCallModal({ onClose, onOpenSettings, costLimitPending, on
|
|
|
151
152
|
} = useCeremonyStore();
|
|
152
153
|
|
|
153
154
|
const [analyzingMessage, setAnalyzingMessage] = useState('');
|
|
155
|
+
const [analysisError, setAnalysisError] = useState(null); // { message, retry? }
|
|
156
|
+
const setError = (message, retryFn = null) => setAnalysisError(message ? { message, retry: retryFn } : null);
|
|
154
157
|
const [showCancelConfirm, setShowCancelConfirm] = useState(false);
|
|
155
158
|
const [transitioning, setTransitioning] = useState(null); // null | 'pausing' | 'cancelling'
|
|
156
159
|
const [workflowOpen, setWorkflowOpen] = useState(false);
|
|
@@ -158,16 +161,21 @@ export function SponsorCallModal({ onClose, onOpenSettings, costLimitPending, on
|
|
|
158
161
|
const [workflowModels, setWorkflowModels] = useState([]);
|
|
159
162
|
const [workflowMissionGenValidation, setWorkflowMissionGenValidation] = useState(null);
|
|
160
163
|
const [workflowAllCeremonies, setWorkflowAllCeremonies] = useState([]);
|
|
164
|
+
const [workflowApiKeys, setWorkflowApiKeys] = useState({});
|
|
161
165
|
const [apiKeyCheck, setApiKeyCheck] = useState({ loading: true, missing: [] });
|
|
162
166
|
const [showResumePrompt, setShowResumePrompt] = useState(false);
|
|
163
167
|
const [draftData, setDraftData] = useState(null);
|
|
168
|
+
const [settings, setSettings] = useState({ ceremonies: [], apiKeys: {} });
|
|
164
169
|
|
|
165
170
|
// Check required API keys when the modal opens
|
|
166
171
|
useEffect(() => {
|
|
167
172
|
let cancelled = false;
|
|
168
173
|
getSettings()
|
|
169
174
|
.then((s) => {
|
|
170
|
-
if (!cancelled)
|
|
175
|
+
if (!cancelled) {
|
|
176
|
+
setApiKeyCheck({ loading: false, missing: computeMissingProviders(s) });
|
|
177
|
+
setSettings(s);
|
|
178
|
+
}
|
|
171
179
|
})
|
|
172
180
|
.catch(() => {
|
|
173
181
|
if (!cancelled) setApiKeyCheck({ loading: false, missing: [] }); // fail open
|
|
@@ -189,12 +197,18 @@ export function SponsorCallModal({ onClose, onOpenSettings, costLimitPending, on
|
|
|
189
197
|
return () => { cancelled = true; };
|
|
190
198
|
}, [isOpen]); // eslint-disable-line react-hooks/exhaustive-deps
|
|
191
199
|
|
|
192
|
-
const recheckKeys = () => {
|
|
200
|
+
const recheckKeys = useCallback(() => {
|
|
193
201
|
setApiKeyCheck({ loading: true, missing: [] });
|
|
194
202
|
getSettings()
|
|
195
203
|
.then((s) => setApiKeyCheck({ loading: false, missing: computeMissingProviders(s) }))
|
|
196
204
|
.catch(() => setApiKeyCheck({ loading: false, missing: [] }));
|
|
197
|
-
};
|
|
205
|
+
}, []);
|
|
206
|
+
|
|
207
|
+
// Auto-recheck whenever settings are saved anywhere in the app (API keys or ceremony models)
|
|
208
|
+
useEffect(() => {
|
|
209
|
+
document.addEventListener('avc:settings-saved', recheckKeys);
|
|
210
|
+
return () => document.removeEventListener('avc:settings-saved', recheckKeys);
|
|
211
|
+
}, [recheckKeys]);
|
|
198
212
|
|
|
199
213
|
// Snapshot current wizard state and persist to server
|
|
200
214
|
const saveDraft = (overrides = {}) => {
|
|
@@ -290,6 +304,7 @@ export function SponsorCallModal({ onClose, onOpenSettings, costLimitPending, on
|
|
|
290
304
|
|
|
291
305
|
// Step 2 → (DB analysis) → Step 3 or 4
|
|
292
306
|
const handleMissionNext = async () => {
|
|
307
|
+
setError(null);
|
|
293
308
|
setAnalyzing(true);
|
|
294
309
|
setAnalyzingMessage('Checking database needs…');
|
|
295
310
|
try {
|
|
@@ -309,7 +324,7 @@ export function SponsorCallModal({ onClose, onOpenSettings, costLimitPending, on
|
|
|
309
324
|
}
|
|
310
325
|
} catch (err) {
|
|
311
326
|
console.error('Mission analysis error:', err);
|
|
312
|
-
|
|
327
|
+
setError(err.message, handleMissionNext);
|
|
313
328
|
} finally {
|
|
314
329
|
setAnalyzing(false);
|
|
315
330
|
setAnalyzingMessage('');
|
|
@@ -318,6 +333,7 @@ export function SponsorCallModal({ onClose, onOpenSettings, costLimitPending, on
|
|
|
318
333
|
|
|
319
334
|
// Step 3 → Architecture analysis → Step 4
|
|
320
335
|
const handleDatabaseNext = async () => {
|
|
336
|
+
setError(null);
|
|
321
337
|
setAnalyzing(true);
|
|
322
338
|
setAnalyzingMessage('Analysing architecture options…');
|
|
323
339
|
try {
|
|
@@ -328,7 +344,7 @@ export function SponsorCallModal({ onClose, onOpenSettings, costLimitPending, on
|
|
|
328
344
|
saveDraft({ wizardStep: 4, archOptions: archData });
|
|
329
345
|
} catch (err) {
|
|
330
346
|
console.error('Architecture analysis error:', err);
|
|
331
|
-
|
|
347
|
+
setError(err.message, handleDatabaseNext);
|
|
332
348
|
} finally {
|
|
333
349
|
setAnalyzing(false);
|
|
334
350
|
setAnalyzingMessage('');
|
|
@@ -337,6 +353,7 @@ export function SponsorCallModal({ onClose, onOpenSettings, costLimitPending, on
|
|
|
337
353
|
|
|
338
354
|
// Step 4 → Prefill → Step 5
|
|
339
355
|
const handleArchitectureNext = async () => {
|
|
356
|
+
setError(null);
|
|
340
357
|
setAnalyzing(true);
|
|
341
358
|
setAnalyzingMessage('Pre-filling requirements from your selections…');
|
|
342
359
|
try {
|
|
@@ -348,7 +365,7 @@ export function SponsorCallModal({ onClose, onOpenSettings, costLimitPending, on
|
|
|
348
365
|
setTimeout(() => saveDraft({ wizardStep: 5, prefillResult: prefill }), 0);
|
|
349
366
|
} catch (err) {
|
|
350
367
|
console.error('Prefill error:', err);
|
|
351
|
-
|
|
368
|
+
setError(err.message, handleArchitectureNext);
|
|
352
369
|
} finally {
|
|
353
370
|
setAnalyzing(false);
|
|
354
371
|
setAnalyzingMessage('');
|
|
@@ -526,14 +543,15 @@ export function SponsorCallModal({ onClose, onOpenSettings, costLimitPending, on
|
|
|
526
543
|
setWorkflowModels(m);
|
|
527
544
|
setWorkflowMissionGenValidation(s.missionGenerator?.validation ?? null);
|
|
528
545
|
setWorkflowAllCeremonies(s.ceremonies || []);
|
|
546
|
+
setWorkflowApiKeys(s.apiKeys || {});
|
|
529
547
|
setWorkflowOpen(true);
|
|
530
548
|
} catch {}
|
|
531
549
|
}}
|
|
532
|
-
className="flex items-center gap-1 text-xs text-slate-
|
|
533
|
-
title="
|
|
550
|
+
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"
|
|
551
|
+
title="Configure ceremony models"
|
|
534
552
|
>
|
|
535
|
-
<
|
|
536
|
-
|
|
553
|
+
<SettingsIcon className="w-3.5 h-3.5" />
|
|
554
|
+
Select Model(s)
|
|
537
555
|
</button>
|
|
538
556
|
)}
|
|
539
557
|
{ceremonyStatus !== 'running' && (
|
|
@@ -593,6 +611,48 @@ export function SponsorCallModal({ onClose, onOpenSettings, costLimitPending, on
|
|
|
593
611
|
)}
|
|
594
612
|
</div>
|
|
595
613
|
|
|
614
|
+
{/* Error banner — shown above status bar when an analysis step fails */}
|
|
615
|
+
{analysisError && (
|
|
616
|
+
<div className="flex-shrink-0 border-t border-red-200 bg-red-50 px-6 py-3">
|
|
617
|
+
<div className="flex items-start gap-2">
|
|
618
|
+
<span className="text-red-500 flex-shrink-0 mt-0.5 text-sm">✕</span>
|
|
619
|
+
<div className="flex-1 min-w-0">
|
|
620
|
+
{analysisError.message?.includes('n_keep') || analysisError.message?.includes('n_ctx') || analysisError.message?.includes('context length') || analysisError.message?.includes('context_length') ? (
|
|
621
|
+
<>
|
|
622
|
+
<p className="text-xs font-medium text-red-700">Context length exceeded</p>
|
|
623
|
+
<p className="text-xs text-red-600 mt-0.5">
|
|
624
|
+
The prompt is too large for this model's context window. Increase the context length in your local server (e.g. LM Studio → Model Settings → Context Length), then click Retry.
|
|
625
|
+
</p>
|
|
626
|
+
</>
|
|
627
|
+
) : (
|
|
628
|
+
<>
|
|
629
|
+
<p className="text-xs font-medium text-red-700">Analysis failed</p>
|
|
630
|
+
<p className="text-xs text-red-600 mt-0.5 break-words">{analysisError.message}</p>
|
|
631
|
+
</>
|
|
632
|
+
)}
|
|
633
|
+
<div className="flex items-center gap-3 mt-2">
|
|
634
|
+
{analysisError.retry && (
|
|
635
|
+
<button
|
|
636
|
+
type="button"
|
|
637
|
+
onClick={() => { const fn = analysisError.retry; setError(null); fn(); }}
|
|
638
|
+
className="text-xs font-medium text-white bg-red-600 hover:bg-red-700 px-3 py-1 rounded transition-colors"
|
|
639
|
+
>
|
|
640
|
+
Retry
|
|
641
|
+
</button>
|
|
642
|
+
)}
|
|
643
|
+
<button
|
|
644
|
+
type="button"
|
|
645
|
+
onClick={() => setError(null)}
|
|
646
|
+
className="text-xs font-medium text-red-700 hover:text-red-900 underline underline-offset-2"
|
|
647
|
+
>
|
|
648
|
+
Dismiss
|
|
649
|
+
</button>
|
|
650
|
+
</div>
|
|
651
|
+
</div>
|
|
652
|
+
</div>
|
|
653
|
+
</div>
|
|
654
|
+
)}
|
|
655
|
+
|
|
596
656
|
{/* Status bar — always rendered to prevent height flicker */}
|
|
597
657
|
<div className="flex-shrink-0 border-t border-slate-100 px-6 h-8 flex items-center gap-2">
|
|
598
658
|
{analyzingMessage && (
|
|
@@ -607,11 +667,18 @@ export function SponsorCallModal({ onClose, onOpenSettings, costLimitPending, on
|
|
|
607
667
|
{workflowOpen && workflowCeremony && (
|
|
608
668
|
<CeremonyWorkflowModal
|
|
609
669
|
ceremony={workflowCeremony}
|
|
670
|
+
allCeremonies={workflowAllCeremonies}
|
|
671
|
+
apiKeys={workflowApiKeys}
|
|
610
672
|
models={workflowModels}
|
|
611
673
|
missionGenValidation={workflowMissionGenValidation}
|
|
612
674
|
readOnly={ceremonyStatus === 'running'}
|
|
613
675
|
onSave={ceremonyStatus !== 'running' ? handleWorkflowSave : undefined}
|
|
614
676
|
onClose={handleWorkflowClose}
|
|
677
|
+
onCeremoniesUpdated={(updated) => {
|
|
678
|
+
setWorkflowAllCeremonies(updated);
|
|
679
|
+
const sc = updated.find((c) => c.name === 'sponsor-call');
|
|
680
|
+
if (sc) setWorkflowCeremony(sc);
|
|
681
|
+
}}
|
|
615
682
|
/>
|
|
616
683
|
)}
|
|
617
684
|
</div>
|