@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,117 @@
|
|
|
1
|
+
import { useState, useEffect } from 'react';
|
|
2
|
+
import { X } from 'lucide-react';
|
|
3
|
+
import { getProjectDocRaw, updateProjectDoc } from '../lib/api';
|
|
4
|
+
|
|
5
|
+
const FILE_CONFIG = {
|
|
6
|
+
doc: {
|
|
7
|
+
title: 'Project Documentation',
|
|
8
|
+
filename: 'doc.md',
|
|
9
|
+
load: getProjectDocRaw,
|
|
10
|
+
save: updateProjectDoc,
|
|
11
|
+
},
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
export function ProjectFileEditorPopup({ fileType, onClose }) {
|
|
15
|
+
const config = FILE_CONFIG[fileType];
|
|
16
|
+
const [content, setContent] = useState('');
|
|
17
|
+
const [savedContent, setSavedContent] = useState('');
|
|
18
|
+
const [loading, setLoading] = useState(true);
|
|
19
|
+
const [saving, setSaving] = useState(false);
|
|
20
|
+
const [savedFlash, setSavedFlash] = useState(false);
|
|
21
|
+
|
|
22
|
+
const isDirty = content !== savedContent;
|
|
23
|
+
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
config.load().then((text) => {
|
|
26
|
+
setContent(text ?? '');
|
|
27
|
+
setSavedContent(text ?? '');
|
|
28
|
+
setLoading(false);
|
|
29
|
+
});
|
|
30
|
+
}, [fileType]);
|
|
31
|
+
|
|
32
|
+
const handleSave = async () => {
|
|
33
|
+
if (!isDirty || saving) return;
|
|
34
|
+
setSaving(true);
|
|
35
|
+
try {
|
|
36
|
+
await config.save(content);
|
|
37
|
+
setSavedContent(content);
|
|
38
|
+
setSavedFlash(true);
|
|
39
|
+
setTimeout(() => setSavedFlash(false), 2000);
|
|
40
|
+
} finally {
|
|
41
|
+
setSaving(false);
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const handleCancel = () => {
|
|
46
|
+
if (isDirty) {
|
|
47
|
+
setContent(savedContent);
|
|
48
|
+
} else {
|
|
49
|
+
onClose();
|
|
50
|
+
}
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
return (
|
|
54
|
+
<div
|
|
55
|
+
className="fixed inset-0 z-[80] flex items-center justify-center"
|
|
56
|
+
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
|
57
|
+
>
|
|
58
|
+
{/* Backdrop */}
|
|
59
|
+
<div className="absolute inset-0 bg-black/40" />
|
|
60
|
+
|
|
61
|
+
{/* Panel */}
|
|
62
|
+
<div className="relative z-10 w-full max-w-3xl h-[80vh] mx-4 bg-white rounded-xl shadow-2xl flex flex-col">
|
|
63
|
+
{/* Header */}
|
|
64
|
+
<div className="flex items-center justify-between px-5 py-4 border-b border-slate-200 flex-shrink-0">
|
|
65
|
+
<div>
|
|
66
|
+
<h2 className="text-base font-semibold text-slate-900">{config.title}</h2>
|
|
67
|
+
<p className="text-xs text-slate-500 mt-0.5 font-mono">{config.filename}</p>
|
|
68
|
+
</div>
|
|
69
|
+
<button
|
|
70
|
+
onClick={onClose}
|
|
71
|
+
className="text-slate-400 hover:text-slate-600 transition-colors"
|
|
72
|
+
title="Close"
|
|
73
|
+
>
|
|
74
|
+
<X className="w-5 h-5" />
|
|
75
|
+
</button>
|
|
76
|
+
</div>
|
|
77
|
+
|
|
78
|
+
{/* Editor */}
|
|
79
|
+
<div className="flex-1 overflow-hidden p-4">
|
|
80
|
+
{loading ? (
|
|
81
|
+
<div className="flex items-center justify-center h-full">
|
|
82
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-slate-400" />
|
|
83
|
+
</div>
|
|
84
|
+
) : (
|
|
85
|
+
<textarea
|
|
86
|
+
className="w-full h-full resize-none font-mono text-sm text-slate-800 border border-slate-200 rounded-lg p-3 focus:outline-none focus:ring-2 focus:ring-slate-300"
|
|
87
|
+
value={content}
|
|
88
|
+
onChange={(e) => setContent(e.target.value)}
|
|
89
|
+
spellCheck={false}
|
|
90
|
+
/>
|
|
91
|
+
)}
|
|
92
|
+
</div>
|
|
93
|
+
|
|
94
|
+
{/* Footer */}
|
|
95
|
+
<div className="flex items-center justify-end gap-3 px-5 py-3 border-t border-slate-200 flex-shrink-0">
|
|
96
|
+
{savedFlash && (
|
|
97
|
+
<span className="text-sm text-green-600 font-medium mr-auto">Saved ✓</span>
|
|
98
|
+
)}
|
|
99
|
+
<button
|
|
100
|
+
onClick={handleCancel}
|
|
101
|
+
disabled={!isDirty}
|
|
102
|
+
className="px-4 py-2 text-sm font-medium text-slate-700 border border-slate-200 rounded-lg hover:bg-slate-50 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
|
103
|
+
>
|
|
104
|
+
Cancel
|
|
105
|
+
</button>
|
|
106
|
+
<button
|
|
107
|
+
onClick={handleSave}
|
|
108
|
+
disabled={!isDirty || saving}
|
|
109
|
+
className="px-4 py-2 text-sm font-medium bg-slate-900 text-white rounded-lg hover:bg-slate-700 transition-colors disabled:opacity-40 disabled:cursor-not-allowed"
|
|
110
|
+
>
|
|
111
|
+
{saving ? 'Saving…' : 'Save'}
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
);
|
|
117
|
+
}
|
|
@@ -0,0 +1,416 @@
|
|
|
1
|
+
import { useEffect, useState } from 'react';
|
|
2
|
+
import { AlertTriangle, Settings as SettingsIcon } from 'lucide-react';
|
|
3
|
+
import { getModels, getSettings, generateArchitecture, refineArchitecture } from '../../lib/api';
|
|
4
|
+
|
|
5
|
+
function normalizeProvider(p = '') {
|
|
6
|
+
if (p === 'claude') return 'anthropic';
|
|
7
|
+
return p;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const KEY_LABELS = {
|
|
11
|
+
claude: 'Anthropic API Key (ANTHROPIC_API_KEY)',
|
|
12
|
+
anthropic: 'Anthropic API Key (ANTHROPIC_API_KEY)',
|
|
13
|
+
gemini: 'Google Gemini API Key (GEMINI_API_KEY)',
|
|
14
|
+
openai: 'OpenAI API Key (OPENAI_API_KEY)',
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
const COST_TIER_COLOR = {
|
|
18
|
+
'Free': 'text-green-600',
|
|
19
|
+
'$': 'text-green-600',
|
|
20
|
+
'$$': 'text-amber-500',
|
|
21
|
+
'$$$': 'text-orange-500',
|
|
22
|
+
'$$$$': 'text-red-500',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
function RefinePopup({ onSubmit, onClose }) {
|
|
26
|
+
const [refinementRequest, setRefinementRequest] = useState('');
|
|
27
|
+
|
|
28
|
+
return (
|
|
29
|
+
<div
|
|
30
|
+
className="fixed inset-0 z-[70] flex items-center justify-center bg-black/40 backdrop-blur-sm p-4"
|
|
31
|
+
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
|
32
|
+
>
|
|
33
|
+
<div className="w-full max-w-md bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden">
|
|
34
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-slate-100 flex-shrink-0">
|
|
35
|
+
<div>
|
|
36
|
+
<h3 className="text-sm font-semibold text-slate-900">Refine Architecture</h3>
|
|
37
|
+
<p className="text-xs text-slate-400 mt-0.5">Describe what you'd like to change or improve.</p>
|
|
38
|
+
</div>
|
|
39
|
+
<button
|
|
40
|
+
type="button"
|
|
41
|
+
onClick={onClose}
|
|
42
|
+
className="text-slate-400 hover:text-slate-600 transition-colors text-xl leading-none ml-4"
|
|
43
|
+
aria-label="Close"
|
|
44
|
+
>
|
|
45
|
+
×
|
|
46
|
+
</button>
|
|
47
|
+
</div>
|
|
48
|
+
<div className="px-5 py-4 flex flex-col gap-3">
|
|
49
|
+
<div>
|
|
50
|
+
<label className="block text-xs font-medium text-slate-500 mb-1">
|
|
51
|
+
What would you like to change or improve?
|
|
52
|
+
</label>
|
|
53
|
+
<textarea
|
|
54
|
+
value={refinementRequest}
|
|
55
|
+
onChange={(e) => setRefinementRequest(e.target.value)}
|
|
56
|
+
rows={4}
|
|
57
|
+
placeholder="E.g. Make it fully serverless, add edge caching, keep everything local…"
|
|
58
|
+
autoFocus
|
|
59
|
+
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"
|
|
60
|
+
/>
|
|
61
|
+
</div>
|
|
62
|
+
</div>
|
|
63
|
+
<div className="px-5 py-3 border-t border-slate-100 flex-shrink-0 flex items-center justify-end gap-2">
|
|
64
|
+
<button
|
|
65
|
+
type="button"
|
|
66
|
+
onClick={onClose}
|
|
67
|
+
className="px-4 py-1.5 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
|
|
68
|
+
>
|
|
69
|
+
Cancel
|
|
70
|
+
</button>
|
|
71
|
+
<button
|
|
72
|
+
type="button"
|
|
73
|
+
onClick={() => onSubmit(refinementRequest)}
|
|
74
|
+
disabled={!refinementRequest.trim()}
|
|
75
|
+
className="px-4 py-1.5 text-xs text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-40"
|
|
76
|
+
>
|
|
77
|
+
Refine
|
|
78
|
+
</button>
|
|
79
|
+
</div>
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* AskArchPopup
|
|
87
|
+
* Two-column popup for generating a single custom architecture via an LLM.
|
|
88
|
+
*
|
|
89
|
+
* Props:
|
|
90
|
+
* onUse(arch) — called when user accepts the generated result
|
|
91
|
+
* onClose() — called when user cancels / closes
|
|
92
|
+
* onOpenSettings — optional callback to open settings panel
|
|
93
|
+
*/
|
|
94
|
+
export function AskArchPopup({ onUse, onClose, onOpenSettings }) {
|
|
95
|
+
const [models, setModels] = useState([]);
|
|
96
|
+
const [selectedModelId, setSelectedModelId] = useState('');
|
|
97
|
+
const [description, setDescription] = useState('');
|
|
98
|
+
const [generating, setGenerating] = useState(false);
|
|
99
|
+
const [result, setResult] = useState(null);
|
|
100
|
+
const [error, setError] = useState('');
|
|
101
|
+
const [showRefinePopup, setShowRefinePopup] = useState(false);
|
|
102
|
+
const [apiKeyStatus, setApiKeyStatus] = useState(null);
|
|
103
|
+
|
|
104
|
+
useEffect(() => {
|
|
105
|
+
Promise.all([getModels(), getSettings()])
|
|
106
|
+
.then(([data, settings]) => {
|
|
107
|
+
setModels(data);
|
|
108
|
+
setApiKeyStatus(settings.apiKeys ?? {});
|
|
109
|
+
const firstReady = data.find((m) => settings.apiKeys?.[normalizeProvider(m.provider)]?.isSet);
|
|
110
|
+
setSelectedModelId(firstReady ? firstReady.modelId : (data.length > 0 ? data[0].modelId : ''));
|
|
111
|
+
})
|
|
112
|
+
.catch(() => setError('Failed to load available models.'));
|
|
113
|
+
}, []);
|
|
114
|
+
|
|
115
|
+
function recheckKeys() {
|
|
116
|
+
getSettings()
|
|
117
|
+
.then((s) => setApiKeyStatus(s.apiKeys ?? {}))
|
|
118
|
+
.catch(() => {});
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
const selectedModel = models.find((m) => m.modelId === selectedModelId);
|
|
122
|
+
|
|
123
|
+
const missingKeyProviders = (() => {
|
|
124
|
+
if (!apiKeyStatus || !selectedModel) return [];
|
|
125
|
+
const missing = new Set();
|
|
126
|
+
if (!apiKeyStatus[normalizeProvider(selectedModel.provider)]?.isSet) {
|
|
127
|
+
missing.add(selectedModel.provider);
|
|
128
|
+
}
|
|
129
|
+
return [...missing];
|
|
130
|
+
})();
|
|
131
|
+
|
|
132
|
+
async function handleGenerate() {
|
|
133
|
+
if (!description.trim() || !selectedModelId || !selectedModel) return;
|
|
134
|
+
setGenerating(true);
|
|
135
|
+
setError('');
|
|
136
|
+
setResult(null);
|
|
137
|
+
try {
|
|
138
|
+
const arch = await generateArchitecture(description.trim(), selectedModelId, selectedModel.provider);
|
|
139
|
+
setResult(arch);
|
|
140
|
+
} catch (err) {
|
|
141
|
+
setError(err.message || 'Generation failed. Please try again.');
|
|
142
|
+
} finally {
|
|
143
|
+
setGenerating(false);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function handleRefine(refinementRequest) {
|
|
148
|
+
setShowRefinePopup(false);
|
|
149
|
+
setResult(null);
|
|
150
|
+
setGenerating(true);
|
|
151
|
+
setError('');
|
|
152
|
+
try {
|
|
153
|
+
const arch = await refineArchitecture(result, refinementRequest, selectedModelId, selectedModel.provider);
|
|
154
|
+
setResult(arch);
|
|
155
|
+
} catch (err) {
|
|
156
|
+
setError(err.message || 'Refinement failed. Please try again.');
|
|
157
|
+
} finally {
|
|
158
|
+
setGenerating(false);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function handleUse() {
|
|
163
|
+
if (!result) return;
|
|
164
|
+
onUse(result);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
function handleTryAgain() {
|
|
168
|
+
setResult(null);
|
|
169
|
+
setError('');
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const providers = [...new Set(models.map((m) => m.provider))];
|
|
173
|
+
|
|
174
|
+
const canGenerate =
|
|
175
|
+
!generating &&
|
|
176
|
+
description.trim().length > 0 &&
|
|
177
|
+
!!selectedModelId &&
|
|
178
|
+
missingKeyProviders.length === 0;
|
|
179
|
+
|
|
180
|
+
function ModelSelect({ label, value, onChange }) {
|
|
181
|
+
const chosen = models.find((m) => m.modelId === value);
|
|
182
|
+
return (
|
|
183
|
+
<div>
|
|
184
|
+
<label className="block text-xs font-medium text-slate-500 mb-1">{label}</label>
|
|
185
|
+
<select
|
|
186
|
+
value={value}
|
|
187
|
+
onChange={(e) => onChange(e.target.value)}
|
|
188
|
+
disabled={generating || models.length === 0}
|
|
189
|
+
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"
|
|
190
|
+
>
|
|
191
|
+
{models.length === 0 && <option value="">Loading…</option>}
|
|
192
|
+
{providers.map((p) => (
|
|
193
|
+
<optgroup key={p} label={p.charAt(0).toUpperCase() + p.slice(1)}>
|
|
194
|
+
{models.filter((m) => m.provider === p).map((m) => (
|
|
195
|
+
<option key={m.modelId} value={m.modelId}>
|
|
196
|
+
{m.displayName}{!m.hasApiKey ? ' (no key)' : ''}
|
|
197
|
+
</option>
|
|
198
|
+
))}
|
|
199
|
+
</optgroup>
|
|
200
|
+
))}
|
|
201
|
+
</select>
|
|
202
|
+
{chosen && !chosen.hasApiKey && (
|
|
203
|
+
<p className="text-xs text-amber-600 mt-0.5">
|
|
204
|
+
⚠ No API key — add to <code>.env</code>
|
|
205
|
+
</p>
|
|
206
|
+
)}
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
return (
|
|
212
|
+
<>
|
|
213
|
+
<div
|
|
214
|
+
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
|
|
215
|
+
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
|
216
|
+
>
|
|
217
|
+
<div className="w-full max-w-5xl bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden h-[540px]">
|
|
218
|
+
|
|
219
|
+
{/* Header */}
|
|
220
|
+
<div className="flex items-center justify-between px-5 py-3 border-b border-slate-100 flex-shrink-0">
|
|
221
|
+
<div>
|
|
222
|
+
<h3 className="text-sm font-semibold text-slate-900">✨ Ask a Model</h3>
|
|
223
|
+
<p className="text-xs text-slate-400 mt-0.5">Describe the architecture you need — an LLM will generate it for you.</p>
|
|
224
|
+
</div>
|
|
225
|
+
<button
|
|
226
|
+
type="button"
|
|
227
|
+
onClick={onClose}
|
|
228
|
+
className="text-slate-400 hover:text-slate-600 transition-colors text-xl leading-none ml-4"
|
|
229
|
+
aria-label="Close"
|
|
230
|
+
>
|
|
231
|
+
×
|
|
232
|
+
</button>
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{/* Body — two columns */}
|
|
236
|
+
<div className="flex flex-1 min-h-0">
|
|
237
|
+
|
|
238
|
+
{/* LEFT column: controls */}
|
|
239
|
+
<div className="w-2/5 min-w-0 border-r border-slate-100 flex flex-col px-4 py-4 gap-3">
|
|
240
|
+
|
|
241
|
+
<ModelSelect
|
|
242
|
+
label="Generator Model"
|
|
243
|
+
value={selectedModelId}
|
|
244
|
+
onChange={setSelectedModelId}
|
|
245
|
+
/>
|
|
246
|
+
|
|
247
|
+
{missingKeyProviders.length > 0 && (
|
|
248
|
+
<div className="flex flex-col gap-2 p-3 bg-amber-50 border border-amber-200 rounded-lg">
|
|
249
|
+
<div className="flex items-start gap-2">
|
|
250
|
+
<AlertTriangle className="w-3.5 h-3.5 text-amber-500 flex-shrink-0 mt-0.5" />
|
|
251
|
+
<div>
|
|
252
|
+
<p className="text-xs font-semibold text-amber-900">API Key Missing</p>
|
|
253
|
+
<ul className="mt-1 space-y-0.5">
|
|
254
|
+
{missingKeyProviders.map((p) => (
|
|
255
|
+
<li key={p} className="flex items-center gap-1.5 text-xs text-amber-800">
|
|
256
|
+
<span className="w-1 h-1 rounded-full bg-amber-400 flex-shrink-0" />
|
|
257
|
+
{KEY_LABELS[p] || p}
|
|
258
|
+
</li>
|
|
259
|
+
))}
|
|
260
|
+
</ul>
|
|
261
|
+
</div>
|
|
262
|
+
</div>
|
|
263
|
+
<div className="flex items-center gap-2">
|
|
264
|
+
{onOpenSettings && (
|
|
265
|
+
<button
|
|
266
|
+
type="button"
|
|
267
|
+
onClick={onOpenSettings}
|
|
268
|
+
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"
|
|
269
|
+
>
|
|
270
|
+
<SettingsIcon className="w-3 h-3" />
|
|
271
|
+
Settings
|
|
272
|
+
</button>
|
|
273
|
+
)}
|
|
274
|
+
<button
|
|
275
|
+
type="button"
|
|
276
|
+
onClick={recheckKeys}
|
|
277
|
+
className="text-xs text-slate-500 hover:text-slate-800 transition-colors"
|
|
278
|
+
>
|
|
279
|
+
Re-check
|
|
280
|
+
</button>
|
|
281
|
+
</div>
|
|
282
|
+
</div>
|
|
283
|
+
)}
|
|
284
|
+
|
|
285
|
+
<div>
|
|
286
|
+
<label className="block text-xs font-medium text-slate-500 mb-1">
|
|
287
|
+
Describe the architecture you need
|
|
288
|
+
</label>
|
|
289
|
+
<textarea
|
|
290
|
+
value={description}
|
|
291
|
+
onChange={(e) => setDescription(e.target.value)}
|
|
292
|
+
rows={4}
|
|
293
|
+
placeholder="E.g. Microservices with Docker, local first, easy to scale to cloud later…"
|
|
294
|
+
disabled={generating}
|
|
295
|
+
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"
|
|
296
|
+
/>
|
|
297
|
+
</div>
|
|
298
|
+
|
|
299
|
+
{error && (
|
|
300
|
+
<p className="text-xs text-red-600 bg-red-50 rounded-md px-2 py-1.5">{error}</p>
|
|
301
|
+
)}
|
|
302
|
+
|
|
303
|
+
<div className="flex-1" />
|
|
304
|
+
|
|
305
|
+
<div className="flex flex-col gap-2">
|
|
306
|
+
{result ? (
|
|
307
|
+
<>
|
|
308
|
+
<button
|
|
309
|
+
type="button"
|
|
310
|
+
onClick={handleUse}
|
|
311
|
+
className="w-full px-3 py-2 bg-slate-900 text-white text-xs font-medium rounded-lg hover:bg-slate-700 transition-colors"
|
|
312
|
+
>
|
|
313
|
+
Use This
|
|
314
|
+
</button>
|
|
315
|
+
<button
|
|
316
|
+
type="button"
|
|
317
|
+
onClick={() => setShowRefinePopup(true)}
|
|
318
|
+
className="w-full px-3 py-2 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
|
|
319
|
+
>
|
|
320
|
+
Refine
|
|
321
|
+
</button>
|
|
322
|
+
<button
|
|
323
|
+
type="button"
|
|
324
|
+
onClick={handleTryAgain}
|
|
325
|
+
className="w-full px-3 py-2 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
|
|
326
|
+
>
|
|
327
|
+
Try Again
|
|
328
|
+
</button>
|
|
329
|
+
</>
|
|
330
|
+
) : (
|
|
331
|
+
<button
|
|
332
|
+
type="button"
|
|
333
|
+
onClick={handleGenerate}
|
|
334
|
+
disabled={!canGenerate}
|
|
335
|
+
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"
|
|
336
|
+
>
|
|
337
|
+
{generating ? (
|
|
338
|
+
<>
|
|
339
|
+
<span className="w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
|
|
340
|
+
Generating…
|
|
341
|
+
</>
|
|
342
|
+
) : 'Generate'}
|
|
343
|
+
</button>
|
|
344
|
+
)}
|
|
345
|
+
<button
|
|
346
|
+
type="button"
|
|
347
|
+
onClick={onClose}
|
|
348
|
+
className="w-full px-3 py-1.5 text-xs text-slate-400 hover:text-slate-600 transition-colors"
|
|
349
|
+
>
|
|
350
|
+
Cancel
|
|
351
|
+
</button>
|
|
352
|
+
</div>
|
|
353
|
+
</div>
|
|
354
|
+
|
|
355
|
+
{/* RIGHT column: output */}
|
|
356
|
+
<div className="flex-1 flex flex-col min-w-0">
|
|
357
|
+
|
|
358
|
+
{generating && (
|
|
359
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-3 px-8 text-center">
|
|
360
|
+
<span className="w-8 h-8 border-3 border-blue-200 border-t-blue-600 rounded-full animate-spin" style={{ borderWidth: 3 }} />
|
|
361
|
+
<p className="text-sm font-medium text-slate-700">Generating architecture…</p>
|
|
362
|
+
<p className="text-xs text-slate-400">The model is crafting a custom architecture based on your description.</p>
|
|
363
|
+
</div>
|
|
364
|
+
)}
|
|
365
|
+
|
|
366
|
+
{!generating && !result && (
|
|
367
|
+
<div className="flex-1 flex flex-col items-center justify-center gap-2 px-8 text-center text-slate-300">
|
|
368
|
+
<svg className="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
369
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
|
|
370
|
+
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" />
|
|
371
|
+
</svg>
|
|
372
|
+
<p className="text-sm font-medium text-slate-400">Custom architecture will appear here</p>
|
|
373
|
+
<p className="text-xs text-slate-300">Fill in the form and click Generate</p>
|
|
374
|
+
</div>
|
|
375
|
+
)}
|
|
376
|
+
|
|
377
|
+
{!generating && result && (
|
|
378
|
+
<div className="flex-1 flex flex-col gap-3 px-5 py-4 overflow-y-auto">
|
|
379
|
+
<div className="flex items-start justify-between gap-2">
|
|
380
|
+
<span className="font-semibold text-slate-900 text-sm">{result.name}</span>
|
|
381
|
+
{result.requiresCloudProvider
|
|
382
|
+
? <span className="text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded-full">Cloud</span>
|
|
383
|
+
: <span className="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded-full">Local</span>
|
|
384
|
+
}
|
|
385
|
+
</div>
|
|
386
|
+
<p className="text-xs text-slate-600 leading-relaxed">{result.description}</p>
|
|
387
|
+
{result.bestFor && (
|
|
388
|
+
<p className="text-xs text-slate-500 italic">Best for: {result.bestFor}</p>
|
|
389
|
+
)}
|
|
390
|
+
{result.costTier && (
|
|
391
|
+
<p className={`text-sm font-semibold ${COST_TIER_COLOR[result.costTier] ?? 'text-slate-500'}`}>
|
|
392
|
+
{result.costTier}
|
|
393
|
+
</p>
|
|
394
|
+
)}
|
|
395
|
+
{result.migrationPath && (
|
|
396
|
+
<p className="text-xs text-slate-400">
|
|
397
|
+
Migration: {result.migrationPath.estimatedMigrationEffort}
|
|
398
|
+
({result.migrationPath.migrationComplexity})
|
|
399
|
+
</p>
|
|
400
|
+
)}
|
|
401
|
+
</div>
|
|
402
|
+
)}
|
|
403
|
+
</div>
|
|
404
|
+
</div>
|
|
405
|
+
</div>
|
|
406
|
+
</div>
|
|
407
|
+
|
|
408
|
+
{showRefinePopup && result && (
|
|
409
|
+
<RefinePopup
|
|
410
|
+
onSubmit={handleRefine}
|
|
411
|
+
onClose={() => setShowRefinePopup(false)}
|
|
412
|
+
/>
|
|
413
|
+
)}
|
|
414
|
+
</>
|
|
415
|
+
);
|
|
416
|
+
}
|