@agile-vibe-coding/avc 0.1.1 → 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.
Files changed (289) hide show
  1. package/cli/agent-loader.js +21 -0
  2. package/cli/agents/agent-selector.md +129 -0
  3. package/cli/agents/architecture-recommender.md +418 -0
  4. package/cli/agents/database-deep-dive.md +470 -0
  5. package/cli/agents/database-recommender.md +634 -0
  6. package/cli/agents/doc-distributor.md +176 -0
  7. package/cli/agents/documentation-updater.md +203 -0
  8. package/cli/agents/epic-story-decomposer.md +280 -0
  9. package/cli/agents/feature-context-generator.md +91 -0
  10. package/cli/agents/gap-checker-epic.md +52 -0
  11. package/cli/agents/impact-checker-story.md +51 -0
  12. package/cli/agents/migration-guide-generator.md +305 -0
  13. package/cli/agents/mission-scope-generator.md +79 -0
  14. package/cli/agents/mission-scope-validator.md +112 -0
  15. package/cli/agents/project-context-extractor.md +107 -0
  16. package/cli/agents/project-documentation-creator.json +226 -0
  17. package/cli/agents/project-documentation-creator.md +595 -0
  18. package/cli/agents/question-prefiller.md +269 -0
  19. package/cli/agents/refiner-epic.md +39 -0
  20. package/cli/agents/refiner-story.md +42 -0
  21. package/cli/agents/solver-epic-api.json +15 -0
  22. package/cli/agents/solver-epic-api.md +39 -0
  23. package/cli/agents/solver-epic-backend.json +15 -0
  24. package/cli/agents/solver-epic-backend.md +39 -0
  25. package/cli/agents/solver-epic-cloud.json +15 -0
  26. package/cli/agents/solver-epic-cloud.md +39 -0
  27. package/cli/agents/solver-epic-data.json +15 -0
  28. package/cli/agents/solver-epic-data.md +39 -0
  29. package/cli/agents/solver-epic-database.json +15 -0
  30. package/cli/agents/solver-epic-database.md +39 -0
  31. package/cli/agents/solver-epic-developer.json +15 -0
  32. package/cli/agents/solver-epic-developer.md +39 -0
  33. package/cli/agents/solver-epic-devops.json +15 -0
  34. package/cli/agents/solver-epic-devops.md +39 -0
  35. package/cli/agents/solver-epic-frontend.json +15 -0
  36. package/cli/agents/solver-epic-frontend.md +39 -0
  37. package/cli/agents/solver-epic-mobile.json +15 -0
  38. package/cli/agents/solver-epic-mobile.md +39 -0
  39. package/cli/agents/solver-epic-qa.json +15 -0
  40. package/cli/agents/solver-epic-qa.md +39 -0
  41. package/cli/agents/solver-epic-security.json +15 -0
  42. package/cli/agents/solver-epic-security.md +39 -0
  43. package/cli/agents/solver-epic-solution-architect.json +15 -0
  44. package/cli/agents/solver-epic-solution-architect.md +39 -0
  45. package/cli/agents/solver-epic-test-architect.json +15 -0
  46. package/cli/agents/solver-epic-test-architect.md +39 -0
  47. package/cli/agents/solver-epic-ui.json +15 -0
  48. package/cli/agents/solver-epic-ui.md +39 -0
  49. package/cli/agents/solver-epic-ux.json +15 -0
  50. package/cli/agents/solver-epic-ux.md +39 -0
  51. package/cli/agents/solver-story-api.json +15 -0
  52. package/cli/agents/solver-story-api.md +39 -0
  53. package/cli/agents/solver-story-backend.json +15 -0
  54. package/cli/agents/solver-story-backend.md +39 -0
  55. package/cli/agents/solver-story-cloud.json +15 -0
  56. package/cli/agents/solver-story-cloud.md +39 -0
  57. package/cli/agents/solver-story-data.json +15 -0
  58. package/cli/agents/solver-story-data.md +39 -0
  59. package/cli/agents/solver-story-database.json +15 -0
  60. package/cli/agents/solver-story-database.md +39 -0
  61. package/cli/agents/solver-story-developer.json +15 -0
  62. package/cli/agents/solver-story-developer.md +39 -0
  63. package/cli/agents/solver-story-devops.json +15 -0
  64. package/cli/agents/solver-story-devops.md +39 -0
  65. package/cli/agents/solver-story-frontend.json +15 -0
  66. package/cli/agents/solver-story-frontend.md +39 -0
  67. package/cli/agents/solver-story-mobile.json +15 -0
  68. package/cli/agents/solver-story-mobile.md +39 -0
  69. package/cli/agents/solver-story-qa.json +15 -0
  70. package/cli/agents/solver-story-qa.md +39 -0
  71. package/cli/agents/solver-story-security.json +15 -0
  72. package/cli/agents/solver-story-security.md +39 -0
  73. package/cli/agents/solver-story-solution-architect.json +15 -0
  74. package/cli/agents/solver-story-solution-architect.md +39 -0
  75. package/cli/agents/solver-story-test-architect.json +15 -0
  76. package/cli/agents/solver-story-test-architect.md +39 -0
  77. package/cli/agents/solver-story-ui.json +15 -0
  78. package/cli/agents/solver-story-ui.md +39 -0
  79. package/cli/agents/solver-story-ux.json +15 -0
  80. package/cli/agents/solver-story-ux.md +39 -0
  81. package/cli/agents/story-doc-enricher.md +133 -0
  82. package/cli/agents/suggestion-business-analyst.md +88 -0
  83. package/cli/agents/suggestion-deployment-architect.md +263 -0
  84. package/cli/agents/suggestion-product-manager.md +129 -0
  85. package/cli/agents/suggestion-security-specialist.md +156 -0
  86. package/cli/agents/suggestion-technical-architect.md +269 -0
  87. package/cli/agents/suggestion-ux-researcher.md +93 -0
  88. package/cli/agents/task-subtask-decomposer.md +188 -0
  89. package/cli/agents/validator-documentation.json +152 -0
  90. package/cli/agents/validator-documentation.md +453 -0
  91. package/cli/agents/validator-epic-api.json +93 -0
  92. package/cli/agents/validator-epic-api.md +137 -0
  93. package/cli/agents/validator-epic-backend.json +93 -0
  94. package/cli/agents/validator-epic-backend.md +130 -0
  95. package/cli/agents/validator-epic-cloud.json +93 -0
  96. package/cli/agents/validator-epic-cloud.md +137 -0
  97. package/cli/agents/validator-epic-data.json +93 -0
  98. package/cli/agents/validator-epic-data.md +130 -0
  99. package/cli/agents/validator-epic-database.json +93 -0
  100. package/cli/agents/validator-epic-database.md +137 -0
  101. package/cli/agents/validator-epic-developer.json +74 -0
  102. package/cli/agents/validator-epic-developer.md +153 -0
  103. package/cli/agents/validator-epic-devops.json +74 -0
  104. package/cli/agents/validator-epic-devops.md +153 -0
  105. package/cli/agents/validator-epic-frontend.json +74 -0
  106. package/cli/agents/validator-epic-frontend.md +153 -0
  107. package/cli/agents/validator-epic-mobile.json +93 -0
  108. package/cli/agents/validator-epic-mobile.md +130 -0
  109. package/cli/agents/validator-epic-qa.json +93 -0
  110. package/cli/agents/validator-epic-qa.md +130 -0
  111. package/cli/agents/validator-epic-security.json +74 -0
  112. package/cli/agents/validator-epic-security.md +154 -0
  113. package/cli/agents/validator-epic-solution-architect.json +74 -0
  114. package/cli/agents/validator-epic-solution-architect.md +156 -0
  115. package/cli/agents/validator-epic-test-architect.json +93 -0
  116. package/cli/agents/validator-epic-test-architect.md +130 -0
  117. package/cli/agents/validator-epic-ui.json +93 -0
  118. package/cli/agents/validator-epic-ui.md +130 -0
  119. package/cli/agents/validator-epic-ux.json +93 -0
  120. package/cli/agents/validator-epic-ux.md +130 -0
  121. package/cli/agents/validator-selector.md +211 -0
  122. package/cli/agents/validator-story-api.json +104 -0
  123. package/cli/agents/validator-story-api.md +152 -0
  124. package/cli/agents/validator-story-backend.json +104 -0
  125. package/cli/agents/validator-story-backend.md +152 -0
  126. package/cli/agents/validator-story-cloud.json +104 -0
  127. package/cli/agents/validator-story-cloud.md +152 -0
  128. package/cli/agents/validator-story-data.json +104 -0
  129. package/cli/agents/validator-story-data.md +152 -0
  130. package/cli/agents/validator-story-database.json +104 -0
  131. package/cli/agents/validator-story-database.md +152 -0
  132. package/cli/agents/validator-story-developer.json +104 -0
  133. package/cli/agents/validator-story-developer.md +152 -0
  134. package/cli/agents/validator-story-devops.json +104 -0
  135. package/cli/agents/validator-story-devops.md +152 -0
  136. package/cli/agents/validator-story-frontend.json +104 -0
  137. package/cli/agents/validator-story-frontend.md +152 -0
  138. package/cli/agents/validator-story-mobile.json +104 -0
  139. package/cli/agents/validator-story-mobile.md +152 -0
  140. package/cli/agents/validator-story-qa.json +104 -0
  141. package/cli/agents/validator-story-qa.md +152 -0
  142. package/cli/agents/validator-story-security.json +104 -0
  143. package/cli/agents/validator-story-security.md +152 -0
  144. package/cli/agents/validator-story-solution-architect.json +104 -0
  145. package/cli/agents/validator-story-solution-architect.md +152 -0
  146. package/cli/agents/validator-story-test-architect.json +104 -0
  147. package/cli/agents/validator-story-test-architect.md +152 -0
  148. package/cli/agents/validator-story-ui.json +104 -0
  149. package/cli/agents/validator-story-ui.md +152 -0
  150. package/cli/agents/validator-story-ux.json +104 -0
  151. package/cli/agents/validator-story-ux.md +152 -0
  152. package/cli/ansi-colors.js +21 -0
  153. package/cli/build-docs.js +29 -8
  154. package/cli/ceremony-history.js +369 -0
  155. package/cli/command-logger.js +49 -12
  156. package/cli/components/static-output.js +63 -0
  157. package/cli/console-output-manager.js +94 -0
  158. package/cli/docs-sync.js +306 -0
  159. package/cli/epic-story-validator.js +1174 -0
  160. package/cli/evaluation-prompts.js +1008 -0
  161. package/cli/execution-context.js +195 -0
  162. package/cli/generate-summary-table.js +340 -0
  163. package/cli/index.js +0 -0
  164. package/cli/init-model-config.js +697 -0
  165. package/cli/init.js +1311 -274
  166. package/cli/kanban-server-manager.js +228 -0
  167. package/cli/llm-claude.js +83 -1
  168. package/cli/llm-gemini.js +85 -0
  169. package/cli/llm-mock.js +233 -0
  170. package/cli/llm-openai.js +233 -0
  171. package/cli/llm-provider.js +240 -3
  172. package/cli/llm-token-limits.js +102 -0
  173. package/cli/llm-verifier.js +454 -0
  174. package/cli/message-constants.js +58 -0
  175. package/cli/message-manager.js +334 -0
  176. package/cli/message-types.js +96 -0
  177. package/cli/messaging-api.js +297 -0
  178. package/cli/model-pricing.js +169 -0
  179. package/cli/model-query-engine.js +468 -0
  180. package/cli/model-recommendation-analyzer.js +495 -0
  181. package/cli/model-selector.js +269 -0
  182. package/cli/output-buffer.js +107 -0
  183. package/cli/process-manager.js +73 -2
  184. package/cli/repl-ink.js +4988 -1217
  185. package/cli/repl-old.js +4 -4
  186. package/cli/seed-processor.js +792 -0
  187. package/cli/sprint-planning-processor.js +1813 -0
  188. package/cli/template-processor.js +2102 -105
  189. package/cli/templates/project.md +25 -8
  190. package/cli/templates/vitepress-config.mts.template +5 -4
  191. package/cli/token-tracker.js +520 -0
  192. package/cli/tools/generate-story-validators.js +317 -0
  193. package/cli/tools/generate-validators.js +669 -0
  194. package/cli/update-checker.js +19 -17
  195. package/cli/update-notifier.js +4 -4
  196. package/cli/validation-router.js +605 -0
  197. package/cli/verification-tracker.js +563 -0
  198. package/kanban/README.md +386 -0
  199. package/kanban/client/README.md +205 -0
  200. package/kanban/client/components.json +20 -0
  201. package/kanban/client/dist/assets/index-CiD8PS2e.js +306 -0
  202. package/kanban/client/dist/assets/index-nLh0m82Q.css +1 -0
  203. package/kanban/client/dist/index.html +16 -0
  204. package/kanban/client/dist/vite.svg +1 -0
  205. package/kanban/client/index.html +15 -0
  206. package/kanban/client/package-lock.json +9442 -0
  207. package/kanban/client/package.json +44 -0
  208. package/kanban/client/postcss.config.js +6 -0
  209. package/kanban/client/public/vite.svg +1 -0
  210. package/kanban/client/src/App.jsx +622 -0
  211. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  212. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +416 -0
  213. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +616 -0
  214. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +946 -0
  215. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  216. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +619 -0
  217. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +704 -0
  218. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  219. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +154 -0
  220. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  221. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  222. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  223. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +125 -0
  224. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +228 -0
  225. package/kanban/client/src/components/kanban/CardDetailModal.jsx +559 -0
  226. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  227. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  228. package/kanban/client/src/components/kanban/GroupingSelector.jsx +57 -0
  229. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  230. package/kanban/client/src/components/kanban/KanbanCard.jsx +138 -0
  231. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  232. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +789 -0
  233. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  234. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  235. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  236. package/kanban/client/src/components/settings/AgentsTab.jsx +353 -0
  237. package/kanban/client/src/components/settings/ApiKeysTab.jsx +113 -0
  238. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +98 -0
  239. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +94 -0
  240. package/kanban/client/src/components/settings/ModelPricingTab.jsx +204 -0
  241. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  242. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  243. package/kanban/client/src/components/stats/CostModal.jsx +353 -0
  244. package/kanban/client/src/components/ui/badge.jsx +27 -0
  245. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  246. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  247. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  248. package/kanban/client/src/hooks/useGrouping.js +118 -0
  249. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  250. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  251. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  252. package/kanban/client/src/lib/api.js +401 -0
  253. package/kanban/client/src/lib/status-grouping.js +144 -0
  254. package/kanban/client/src/lib/utils.js +11 -0
  255. package/kanban/client/src/main.jsx +10 -0
  256. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  257. package/kanban/client/src/store/ceremonyStore.js +172 -0
  258. package/kanban/client/src/store/filterStore.js +201 -0
  259. package/kanban/client/src/store/kanbanStore.js +115 -0
  260. package/kanban/client/src/store/processStore.js +65 -0
  261. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  262. package/kanban/client/src/styles/globals.css +59 -0
  263. package/kanban/client/tailwind.config.js +77 -0
  264. package/kanban/client/vite.config.js +28 -0
  265. package/kanban/client/vitest.config.js +28 -0
  266. package/kanban/dev-start.sh +47 -0
  267. package/kanban/package.json +12 -0
  268. package/kanban/server/index.js +516 -0
  269. package/kanban/server/routes/ceremony.js +305 -0
  270. package/kanban/server/routes/costs.js +157 -0
  271. package/kanban/server/routes/processes.js +50 -0
  272. package/kanban/server/routes/settings.js +303 -0
  273. package/kanban/server/routes/websocket.js +276 -0
  274. package/kanban/server/routes/work-items.js +347 -0
  275. package/kanban/server/services/CeremonyService.js +1190 -0
  276. package/kanban/server/services/FileSystemScanner.js +95 -0
  277. package/kanban/server/services/FileWatcher.js +144 -0
  278. package/kanban/server/services/HierarchyBuilder.js +196 -0
  279. package/kanban/server/services/ProcessRegistry.js +122 -0
  280. package/kanban/server/services/WorkItemReader.js +123 -0
  281. package/kanban/server/services/WorkItemRefineService.js +510 -0
  282. package/kanban/server/start.js +49 -0
  283. package/kanban/server/utils/kanban-logger.js +132 -0
  284. package/kanban/server/utils/markdown.js +91 -0
  285. package/kanban/server/utils/status-grouping.js +107 -0
  286. package/kanban/server/workers/sponsor-call-worker.js +84 -0
  287. package/kanban/server/workers/sprint-planning-worker.js +130 -0
  288. package/package.json +18 -5
  289. package/cli/agents/documentation.md +0 -302
@@ -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
+ }