@agile-vibe-coding/avc 0.1.1 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (239) hide show
  1. package/cli/agent-loader.js +21 -0
  2. package/cli/agents/agent-selector.md +152 -0
  3. package/cli/agents/architecture-recommender.md +418 -0
  4. package/cli/agents/code-implementer.md +117 -0
  5. package/cli/agents/code-validator.md +80 -0
  6. package/cli/agents/context-reviewer-epic.md +101 -0
  7. package/cli/agents/context-reviewer-story.md +92 -0
  8. package/cli/agents/context-writer-epic.md +145 -0
  9. package/cli/agents/context-writer-story.md +111 -0
  10. package/cli/agents/database-deep-dive.md +470 -0
  11. package/cli/agents/database-recommender.md +634 -0
  12. package/cli/agents/doc-distributor.md +176 -0
  13. package/cli/agents/doc-writer-epic.md +42 -0
  14. package/cli/agents/doc-writer-story.md +43 -0
  15. package/cli/agents/documentation-updater.md +203 -0
  16. package/cli/agents/duplicate-detector.md +110 -0
  17. package/cli/agents/epic-story-decomposer.md +559 -0
  18. package/cli/agents/feature-context-generator.md +91 -0
  19. package/cli/agents/gap-checker-epic.md +52 -0
  20. package/cli/agents/impact-checker-story.md +51 -0
  21. package/cli/agents/migration-guide-generator.md +305 -0
  22. package/cli/agents/mission-scope-generator.md +143 -0
  23. package/cli/agents/mission-scope-validator.md +146 -0
  24. package/cli/agents/project-context-extractor.md +122 -0
  25. package/cli/agents/project-documentation-creator.json +226 -0
  26. package/cli/agents/project-documentation-creator.md +595 -0
  27. package/cli/agents/question-prefiller.md +269 -0
  28. package/cli/agents/refiner-epic.md +39 -0
  29. package/cli/agents/refiner-story.md +42 -0
  30. package/cli/agents/scaffolding-generator.md +99 -0
  31. package/cli/agents/seed-validator.md +71 -0
  32. package/cli/agents/story-doc-enricher.md +133 -0
  33. package/cli/agents/story-scope-reviewer.md +147 -0
  34. package/cli/agents/story-splitter.md +83 -0
  35. package/cli/agents/suggestion-business-analyst.md +88 -0
  36. package/cli/agents/suggestion-deployment-architect.md +263 -0
  37. package/cli/agents/suggestion-product-manager.md +129 -0
  38. package/cli/agents/suggestion-security-specialist.md +156 -0
  39. package/cli/agents/suggestion-technical-architect.md +269 -0
  40. package/cli/agents/suggestion-ux-researcher.md +93 -0
  41. package/cli/agents/task-subtask-decomposer.md +188 -0
  42. package/cli/agents/validator-documentation.json +183 -0
  43. package/cli/agents/validator-documentation.md +455 -0
  44. package/cli/agents/validator-selector.md +211 -0
  45. package/cli/ansi-colors.js +21 -0
  46. package/cli/api-reference-tool.js +368 -0
  47. package/cli/build-docs.js +29 -8
  48. package/cli/ceremony-history.js +369 -0
  49. package/cli/checks/catalog.json +76 -0
  50. package/cli/checks/code/quality.json +26 -0
  51. package/cli/checks/code/testing.json +14 -0
  52. package/cli/checks/code/traceability.json +26 -0
  53. package/cli/checks/cross-refs/epic.json +171 -0
  54. package/cli/checks/cross-refs/story.json +149 -0
  55. package/cli/checks/epic/api.json +114 -0
  56. package/cli/checks/epic/backend.json +126 -0
  57. package/cli/checks/epic/cloud.json +126 -0
  58. package/cli/checks/epic/data.json +102 -0
  59. package/cli/checks/epic/database.json +114 -0
  60. package/cli/checks/epic/developer.json +182 -0
  61. package/cli/checks/epic/devops.json +174 -0
  62. package/cli/checks/epic/frontend.json +162 -0
  63. package/cli/checks/epic/mobile.json +102 -0
  64. package/cli/checks/epic/qa.json +90 -0
  65. package/cli/checks/epic/security.json +184 -0
  66. package/cli/checks/epic/solution-architect.json +192 -0
  67. package/cli/checks/epic/test-architect.json +90 -0
  68. package/cli/checks/epic/ui.json +102 -0
  69. package/cli/checks/epic/ux.json +90 -0
  70. package/cli/checks/fixes/epic-fix-template.md +10 -0
  71. package/cli/checks/fixes/story-fix-template.md +10 -0
  72. package/cli/checks/story/api.json +186 -0
  73. package/cli/checks/story/backend.json +102 -0
  74. package/cli/checks/story/cloud.json +102 -0
  75. package/cli/checks/story/data.json +210 -0
  76. package/cli/checks/story/database.json +102 -0
  77. package/cli/checks/story/developer.json +168 -0
  78. package/cli/checks/story/devops.json +102 -0
  79. package/cli/checks/story/frontend.json +174 -0
  80. package/cli/checks/story/mobile.json +102 -0
  81. package/cli/checks/story/qa.json +210 -0
  82. package/cli/checks/story/security.json +198 -0
  83. package/cli/checks/story/solution-architect.json +230 -0
  84. package/cli/checks/story/test-architect.json +210 -0
  85. package/cli/checks/story/ui.json +102 -0
  86. package/cli/checks/story/ux.json +102 -0
  87. package/cli/coding-order.js +401 -0
  88. package/cli/command-logger.js +49 -12
  89. package/cli/components/static-output.js +63 -0
  90. package/cli/console-output-manager.js +94 -0
  91. package/cli/dependency-checker.js +72 -0
  92. package/cli/docs-sync.js +306 -0
  93. package/cli/epic-story-validator.js +659 -0
  94. package/cli/evaluation-prompts.js +1008 -0
  95. package/cli/execution-context.js +195 -0
  96. package/cli/generate-summary-table.js +340 -0
  97. package/cli/init-model-config.js +704 -0
  98. package/cli/init.js +1737 -278
  99. package/cli/kanban-server-manager.js +227 -0
  100. package/cli/llm-claude.js +150 -1
  101. package/cli/llm-gemini.js +109 -0
  102. package/cli/llm-local.js +493 -0
  103. package/cli/llm-mock.js +233 -0
  104. package/cli/llm-openai.js +454 -0
  105. package/cli/llm-provider.js +379 -3
  106. package/cli/llm-token-limits.js +211 -0
  107. package/cli/llm-verifier.js +662 -0
  108. package/cli/llm-xiaomi.js +143 -0
  109. package/cli/message-constants.js +49 -0
  110. package/cli/message-manager.js +334 -0
  111. package/cli/message-types.js +96 -0
  112. package/cli/messaging-api.js +291 -0
  113. package/cli/micro-check-fixer.js +335 -0
  114. package/cli/micro-check-runner.js +449 -0
  115. package/cli/micro-check-scorer.js +148 -0
  116. package/cli/micro-check-validator.js +538 -0
  117. package/cli/model-pricing.js +192 -0
  118. package/cli/model-query-engine.js +468 -0
  119. package/cli/model-recommendation-analyzer.js +495 -0
  120. package/cli/model-selector.js +270 -0
  121. package/cli/output-buffer.js +107 -0
  122. package/cli/process-manager.js +73 -2
  123. package/cli/prompt-logger.js +57 -0
  124. package/cli/repl-ink.js +4625 -1094
  125. package/cli/repl-old.js +3 -4
  126. package/cli/seed-processor.js +962 -0
  127. package/cli/sprint-planning-processor.js +4162 -0
  128. package/cli/template-processor.js +2149 -105
  129. package/cli/templates/project.md +25 -8
  130. package/cli/templates/vitepress-config.mts.template +5 -4
  131. package/cli/token-tracker.js +547 -0
  132. package/cli/tools/generate-story-validators.js +317 -0
  133. package/cli/tools/generate-validators.js +669 -0
  134. package/cli/update-checker.js +19 -17
  135. package/cli/update-notifier.js +4 -4
  136. package/cli/validation-router.js +667 -0
  137. package/cli/verification-tracker.js +563 -0
  138. package/cli/worktree-runner.js +654 -0
  139. package/kanban/README.md +386 -0
  140. package/kanban/client/README.md +205 -0
  141. package/kanban/client/components.json +20 -0
  142. package/kanban/client/dist/assets/index-D_KC5EQT.css +1 -0
  143. package/kanban/client/dist/assets/index-DjY5zqW7.js +351 -0
  144. package/kanban/client/dist/index.html +16 -0
  145. package/kanban/client/dist/vite.svg +1 -0
  146. package/kanban/client/index.html +15 -0
  147. package/kanban/client/package-lock.json +9442 -0
  148. package/kanban/client/package.json +44 -0
  149. package/kanban/client/postcss.config.js +6 -0
  150. package/kanban/client/public/vite.svg +1 -0
  151. package/kanban/client/src/App.jsx +651 -0
  152. package/kanban/client/src/components/ProjectFileEditorPopup.jsx +117 -0
  153. package/kanban/client/src/components/ceremony/AskArchPopup.jsx +420 -0
  154. package/kanban/client/src/components/ceremony/AskModelPopup.jsx +629 -0
  155. package/kanban/client/src/components/ceremony/CeremonyWorkflowModal.jsx +1133 -0
  156. package/kanban/client/src/components/ceremony/EpicStorySelectionModal.jsx +254 -0
  157. package/kanban/client/src/components/ceremony/ProviderSwitcherButton.jsx +290 -0
  158. package/kanban/client/src/components/ceremony/SponsorCallModal.jsx +686 -0
  159. package/kanban/client/src/components/ceremony/SprintPlanningModal.jsx +838 -0
  160. package/kanban/client/src/components/ceremony/steps/ArchitectureStep.jsx +150 -0
  161. package/kanban/client/src/components/ceremony/steps/CompleteStep.jsx +136 -0
  162. package/kanban/client/src/components/ceremony/steps/DatabaseStep.jsx +202 -0
  163. package/kanban/client/src/components/ceremony/steps/DeploymentStep.jsx +123 -0
  164. package/kanban/client/src/components/ceremony/steps/MissionStep.jsx +106 -0
  165. package/kanban/client/src/components/ceremony/steps/ReviewAnswersStep.jsx +329 -0
  166. package/kanban/client/src/components/ceremony/steps/RunningStep.jsx +249 -0
  167. package/kanban/client/src/components/kanban/CardDetailModal.jsx +646 -0
  168. package/kanban/client/src/components/kanban/EpicSection.jsx +146 -0
  169. package/kanban/client/src/components/kanban/FilterToolbar.jsx +222 -0
  170. package/kanban/client/src/components/kanban/GroupingSelector.jsx +63 -0
  171. package/kanban/client/src/components/kanban/KanbanBoard.jsx +211 -0
  172. package/kanban/client/src/components/kanban/KanbanCard.jsx +147 -0
  173. package/kanban/client/src/components/kanban/KanbanColumn.jsx +90 -0
  174. package/kanban/client/src/components/kanban/RefineWorkItemPopup.jsx +784 -0
  175. package/kanban/client/src/components/kanban/RunButton.jsx +162 -0
  176. package/kanban/client/src/components/kanban/SeedButton.jsx +176 -0
  177. package/kanban/client/src/components/layout/LoadingScreen.jsx +82 -0
  178. package/kanban/client/src/components/process/ProcessMonitorBar.jsx +80 -0
  179. package/kanban/client/src/components/settings/AgentEditorPopup.jsx +171 -0
  180. package/kanban/client/src/components/settings/AgentsTab.jsx +381 -0
  181. package/kanban/client/src/components/settings/ApiKeysTab.jsx +142 -0
  182. package/kanban/client/src/components/settings/CeremonyModelsTab.jsx +105 -0
  183. package/kanban/client/src/components/settings/CheckEditorPopup.jsx +507 -0
  184. package/kanban/client/src/components/settings/CostThresholdsTab.jsx +95 -0
  185. package/kanban/client/src/components/settings/ModelPricingTab.jsx +269 -0
  186. package/kanban/client/src/components/settings/OpenAIAuthSection.jsx +412 -0
  187. package/kanban/client/src/components/settings/ServersTab.jsx +121 -0
  188. package/kanban/client/src/components/settings/SettingsModal.jsx +84 -0
  189. package/kanban/client/src/components/stats/CostModal.jsx +384 -0
  190. package/kanban/client/src/components/ui/badge.jsx +27 -0
  191. package/kanban/client/src/components/ui/dialog.jsx +121 -0
  192. package/kanban/client/src/components/ui/tabs.jsx +85 -0
  193. package/kanban/client/src/hooks/__tests__/useGrouping.test.js +232 -0
  194. package/kanban/client/src/hooks/useGrouping.js +177 -0
  195. package/kanban/client/src/hooks/useWebSocket.js +120 -0
  196. package/kanban/client/src/lib/__tests__/api.test.js +196 -0
  197. package/kanban/client/src/lib/__tests__/status-grouping.test.js +94 -0
  198. package/kanban/client/src/lib/api.js +515 -0
  199. package/kanban/client/src/lib/status-grouping.js +154 -0
  200. package/kanban/client/src/lib/utils.js +11 -0
  201. package/kanban/client/src/main.jsx +10 -0
  202. package/kanban/client/src/store/__tests__/kanbanStore.test.js +164 -0
  203. package/kanban/client/src/store/ceremonyStore.js +172 -0
  204. package/kanban/client/src/store/filterStore.js +201 -0
  205. package/kanban/client/src/store/kanbanStore.js +123 -0
  206. package/kanban/client/src/store/processStore.js +65 -0
  207. package/kanban/client/src/store/sprintPlanningStore.js +33 -0
  208. package/kanban/client/src/styles/globals.css +59 -0
  209. package/kanban/client/tailwind.config.js +77 -0
  210. package/kanban/client/vite.config.js +28 -0
  211. package/kanban/client/vitest.config.js +28 -0
  212. package/kanban/dev-start.sh +47 -0
  213. package/kanban/package.json +12 -0
  214. package/kanban/server/index.js +537 -0
  215. package/kanban/server/routes/ceremony.js +454 -0
  216. package/kanban/server/routes/costs.js +163 -0
  217. package/kanban/server/routes/openai-oauth.js +366 -0
  218. package/kanban/server/routes/processes.js +50 -0
  219. package/kanban/server/routes/settings.js +736 -0
  220. package/kanban/server/routes/websocket.js +281 -0
  221. package/kanban/server/routes/work-items.js +487 -0
  222. package/kanban/server/services/CeremonyService.js +1441 -0
  223. package/kanban/server/services/FileSystemScanner.js +95 -0
  224. package/kanban/server/services/FileWatcher.js +144 -0
  225. package/kanban/server/services/HierarchyBuilder.js +196 -0
  226. package/kanban/server/services/ProcessRegistry.js +122 -0
  227. package/kanban/server/services/TaskRunnerService.js +261 -0
  228. package/kanban/server/services/WorkItemReader.js +123 -0
  229. package/kanban/server/services/WorkItemRefineService.js +510 -0
  230. package/kanban/server/start.js +49 -0
  231. package/kanban/server/utils/kanban-logger.js +132 -0
  232. package/kanban/server/utils/markdown.js +91 -0
  233. package/kanban/server/utils/status-grouping.js +107 -0
  234. package/kanban/server/workers/run-task-worker.js +121 -0
  235. package/kanban/server/workers/seed-worker.js +94 -0
  236. package/kanban/server/workers/sponsor-call-worker.js +92 -0
  237. package/kanban/server/workers/sprint-planning-worker.js +212 -0
  238. package/package.json +19 -7
  239. 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,420 @@
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
+ xiaomi: 'Xiaomi MiMo API Key (XIAOMI_API_KEY)',
16
+ };
17
+
18
+ const COST_TIER_COLOR = {
19
+ 'Free': 'text-green-600',
20
+ '$': 'text-green-600',
21
+ '$$': 'text-amber-500',
22
+ '$$$': 'text-orange-500',
23
+ '$$$$': 'text-red-500',
24
+ };
25
+
26
+ function RefinePopup({ onSubmit, onClose }) {
27
+ const [refinementRequest, setRefinementRequest] = useState('');
28
+
29
+ return (
30
+ <div
31
+ className="fixed inset-0 z-[70] flex items-center justify-center bg-black/40 backdrop-blur-sm p-4"
32
+ onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
33
+ >
34
+ <div className="w-full max-w-md bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden">
35
+ <div className="flex items-center justify-between px-5 py-3 border-b border-slate-100 flex-shrink-0">
36
+ <div>
37
+ <h3 className="text-sm font-semibold text-slate-900">Refine Architecture</h3>
38
+ <p className="text-xs text-slate-400 mt-0.5">Describe what you'd like to change or improve.</p>
39
+ </div>
40
+ <button
41
+ type="button"
42
+ onClick={onClose}
43
+ className="text-slate-400 hover:text-slate-600 transition-colors text-xl leading-none ml-4"
44
+ aria-label="Close"
45
+ >
46
+ ×
47
+ </button>
48
+ </div>
49
+ <div className="px-5 py-4 flex flex-col gap-3">
50
+ <div>
51
+ <label className="block text-xs font-medium text-slate-500 mb-1">
52
+ What would you like to change or improve?
53
+ </label>
54
+ <textarea
55
+ value={refinementRequest}
56
+ onChange={(e) => setRefinementRequest(e.target.value)}
57
+ rows={4}
58
+ placeholder="E.g. Make it fully serverless, add edge caching, keep everything local…"
59
+ autoFocus
60
+ 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"
61
+ />
62
+ </div>
63
+ </div>
64
+ <div className="px-5 py-3 border-t border-slate-100 flex-shrink-0 flex items-center justify-end gap-2">
65
+ <button
66
+ type="button"
67
+ onClick={onClose}
68
+ className="px-4 py-1.5 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
69
+ >
70
+ Cancel
71
+ </button>
72
+ <button
73
+ type="button"
74
+ onClick={() => onSubmit(refinementRequest)}
75
+ disabled={!refinementRequest.trim()}
76
+ className="px-4 py-1.5 text-xs text-white bg-blue-600 rounded-lg hover:bg-blue-700 transition-colors disabled:opacity-40"
77
+ >
78
+ Refine
79
+ </button>
80
+ </div>
81
+ </div>
82
+ </div>
83
+ );
84
+ }
85
+
86
+ /**
87
+ * AskArchPopup
88
+ * Two-column popup for generating a single custom architecture via an LLM.
89
+ *
90
+ * Props:
91
+ * onUse(arch) — called when user accepts the generated result
92
+ * onClose() — called when user cancels / closes
93
+ * onOpenSettings — optional callback to open settings panel
94
+ */
95
+ export function AskArchPopup({ onUse, onClose, onOpenSettings }) {
96
+ const [models, setModels] = useState([]);
97
+ const [selectedModelId, setSelectedModelId] = useState('');
98
+ const [description, setDescription] = useState('');
99
+ const [generating, setGenerating] = useState(false);
100
+ const [result, setResult] = useState(null);
101
+ const [error, setError] = useState('');
102
+ const [showRefinePopup, setShowRefinePopup] = useState(false);
103
+ const [apiKeyStatus, setApiKeyStatus] = useState(null);
104
+
105
+ useEffect(() => {
106
+ Promise.all([getModels(), getSettings()])
107
+ .then(([data, settings]) => {
108
+ setModels(data);
109
+ setApiKeyStatus(settings.apiKeys ?? {});
110
+ // Prefer "pro" tier model for architecture generation
111
+ const ready = data.filter((m) => settings.apiKeys?.[normalizeProvider(m.provider)]?.isSet);
112
+ const isPro = (id) => /pro|opus|sonnet/i.test(id);
113
+ const best = ready.find((m) => isPro(m.modelId)) || ready[0];
114
+ setSelectedModelId(best ? best.modelId : (data.length > 0 ? data[0].modelId : ''));
115
+ })
116
+ .catch(() => setError('Failed to load available models.'));
117
+ }, []);
118
+
119
+ function recheckKeys() {
120
+ getSettings()
121
+ .then((s) => setApiKeyStatus(s.apiKeys ?? {}))
122
+ .catch(() => {});
123
+ }
124
+
125
+ const selectedModel = models.find((m) => m.modelId === selectedModelId);
126
+
127
+ const missingKeyProviders = (() => {
128
+ if (!apiKeyStatus || !selectedModel) return [];
129
+ const missing = new Set();
130
+ if (!apiKeyStatus[normalizeProvider(selectedModel.provider)]?.isSet) {
131
+ missing.add(selectedModel.provider);
132
+ }
133
+ return [...missing];
134
+ })();
135
+
136
+ async function handleGenerate() {
137
+ if (!description.trim() || !selectedModelId || !selectedModel) return;
138
+ setGenerating(true);
139
+ setError('');
140
+ setResult(null);
141
+ try {
142
+ const arch = await generateArchitecture(description.trim(), selectedModelId, selectedModel.provider);
143
+ setResult(arch);
144
+ } catch (err) {
145
+ setError(err.message || 'Generation failed. Please try again.');
146
+ } finally {
147
+ setGenerating(false);
148
+ }
149
+ }
150
+
151
+ async function handleRefine(refinementRequest) {
152
+ setShowRefinePopup(false);
153
+ setResult(null);
154
+ setGenerating(true);
155
+ setError('');
156
+ try {
157
+ const arch = await refineArchitecture(result, refinementRequest, selectedModelId, selectedModel.provider);
158
+ setResult(arch);
159
+ } catch (err) {
160
+ setError(err.message || 'Refinement failed. Please try again.');
161
+ } finally {
162
+ setGenerating(false);
163
+ }
164
+ }
165
+
166
+ function handleUse() {
167
+ if (!result) return;
168
+ onUse(result);
169
+ }
170
+
171
+ function handleTryAgain() {
172
+ setResult(null);
173
+ setError('');
174
+ }
175
+
176
+ const providers = [...new Set(models.map((m) => m.provider))];
177
+
178
+ const canGenerate =
179
+ !generating &&
180
+ description.trim().length > 0 &&
181
+ !!selectedModelId &&
182
+ missingKeyProviders.length === 0;
183
+
184
+ function ModelSelect({ label, value, onChange }) {
185
+ const chosen = models.find((m) => m.modelId === value);
186
+ return (
187
+ <div>
188
+ <label className="block text-xs font-medium text-slate-500 mb-1">{label}</label>
189
+ <select
190
+ value={value}
191
+ onChange={(e) => onChange(e.target.value)}
192
+ disabled={generating || models.length === 0}
193
+ 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"
194
+ >
195
+ {models.length === 0 && <option value="">Loading…</option>}
196
+ {providers.map((p) => (
197
+ <optgroup key={p} label={p.charAt(0).toUpperCase() + p.slice(1)}>
198
+ {models.filter((m) => m.provider === p).map((m) => (
199
+ <option key={m.modelId} value={m.modelId}>
200
+ {m.displayName}{!m.hasApiKey ? ' (no key)' : ''}
201
+ </option>
202
+ ))}
203
+ </optgroup>
204
+ ))}
205
+ </select>
206
+ {chosen && !chosen.hasApiKey && (
207
+ <p className="text-xs text-amber-600 mt-0.5">
208
+ ⚠ No API key — add to <code>.env</code>
209
+ </p>
210
+ )}
211
+ </div>
212
+ );
213
+ }
214
+
215
+ return (
216
+ <>
217
+ <div
218
+ className="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
219
+ onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
220
+ >
221
+ <div className="w-full max-w-5xl bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden h-[540px]">
222
+
223
+ {/* Header */}
224
+ <div className="flex items-center justify-between px-5 py-3 border-b border-slate-100 flex-shrink-0">
225
+ <div>
226
+ <h3 className="text-sm font-semibold text-slate-900">✨ Ask a Model</h3>
227
+ <p className="text-xs text-slate-400 mt-0.5">Describe the architecture you need — an LLM will generate it for you.</p>
228
+ </div>
229
+ <button
230
+ type="button"
231
+ onClick={onClose}
232
+ className="text-slate-400 hover:text-slate-600 transition-colors text-xl leading-none ml-4"
233
+ aria-label="Close"
234
+ >
235
+ ×
236
+ </button>
237
+ </div>
238
+
239
+ {/* Body — two columns */}
240
+ <div className="flex flex-1 min-h-0">
241
+
242
+ {/* LEFT column: controls */}
243
+ <div className="w-2/5 min-w-0 border-r border-slate-100 flex flex-col px-4 py-4 gap-3">
244
+
245
+ <ModelSelect
246
+ label="Generator Model"
247
+ value={selectedModelId}
248
+ onChange={setSelectedModelId}
249
+ />
250
+
251
+ {missingKeyProviders.length > 0 && (
252
+ <div className="flex flex-col gap-2 p-3 bg-amber-50 border border-amber-200 rounded-lg">
253
+ <div className="flex items-start gap-2">
254
+ <AlertTriangle className="w-3.5 h-3.5 text-amber-500 flex-shrink-0 mt-0.5" />
255
+ <div>
256
+ <p className="text-xs font-semibold text-amber-900">API Key Missing</p>
257
+ <ul className="mt-1 space-y-0.5">
258
+ {missingKeyProviders.map((p) => (
259
+ <li key={p} className="flex items-center gap-1.5 text-xs text-amber-800">
260
+ <span className="w-1 h-1 rounded-full bg-amber-400 flex-shrink-0" />
261
+ {KEY_LABELS[p] || p}
262
+ </li>
263
+ ))}
264
+ </ul>
265
+ </div>
266
+ </div>
267
+ <div className="flex items-center gap-2">
268
+ {onOpenSettings && (
269
+ <button
270
+ type="button"
271
+ onClick={() => onOpenSettings()}
272
+ 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"
273
+ >
274
+ <SettingsIcon className="w-3 h-3" />
275
+ Settings
276
+ </button>
277
+ )}
278
+ <button
279
+ type="button"
280
+ onClick={recheckKeys}
281
+ className="text-xs text-slate-500 hover:text-slate-800 transition-colors"
282
+ >
283
+ Re-check
284
+ </button>
285
+ </div>
286
+ </div>
287
+ )}
288
+
289
+ <div>
290
+ <label className="block text-xs font-medium text-slate-500 mb-1">
291
+ Describe the architecture you need
292
+ </label>
293
+ <textarea
294
+ value={description}
295
+ onChange={(e) => setDescription(e.target.value)}
296
+ rows={4}
297
+ placeholder="E.g. Microservices with Docker, local first, easy to scale to cloud later…"
298
+ disabled={generating}
299
+ 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"
300
+ />
301
+ </div>
302
+
303
+ {error && (
304
+ <p className="text-xs text-red-600 bg-red-50 rounded-md px-2 py-1.5">{error}</p>
305
+ )}
306
+
307
+ <div className="flex-1" />
308
+
309
+ <div className="flex flex-col gap-2">
310
+ {result ? (
311
+ <>
312
+ <button
313
+ type="button"
314
+ onClick={handleUse}
315
+ className="w-full px-3 py-2 bg-slate-900 text-white text-xs font-medium rounded-lg hover:bg-slate-700 transition-colors"
316
+ >
317
+ Use This
318
+ </button>
319
+ <button
320
+ type="button"
321
+ onClick={() => setShowRefinePopup(true)}
322
+ className="w-full px-3 py-2 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
323
+ >
324
+ Refine
325
+ </button>
326
+ <button
327
+ type="button"
328
+ onClick={handleTryAgain}
329
+ className="w-full px-3 py-2 text-xs text-slate-600 border border-slate-300 rounded-lg hover:bg-slate-50 transition-colors"
330
+ >
331
+ Try Again
332
+ </button>
333
+ </>
334
+ ) : (
335
+ <button
336
+ type="button"
337
+ onClick={handleGenerate}
338
+ disabled={!canGenerate}
339
+ 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"
340
+ >
341
+ {generating ? (
342
+ <>
343
+ <span className="w-3.5 h-3.5 border-2 border-white/30 border-t-white rounded-full animate-spin" />
344
+ Generating…
345
+ </>
346
+ ) : 'Generate'}
347
+ </button>
348
+ )}
349
+ <button
350
+ type="button"
351
+ onClick={onClose}
352
+ className="w-full px-3 py-1.5 text-xs text-slate-400 hover:text-slate-600 transition-colors"
353
+ >
354
+ Cancel
355
+ </button>
356
+ </div>
357
+ </div>
358
+
359
+ {/* RIGHT column: output */}
360
+ <div className="flex-1 flex flex-col min-w-0">
361
+
362
+ {generating && (
363
+ <div className="flex-1 flex flex-col items-center justify-center gap-3 px-8 text-center">
364
+ <span className="w-8 h-8 border-3 border-blue-200 border-t-blue-600 rounded-full animate-spin" style={{ borderWidth: 3 }} />
365
+ <p className="text-sm font-medium text-slate-700">Generating architecture…</p>
366
+ <p className="text-xs text-slate-400">The model is crafting a custom architecture based on your description.</p>
367
+ </div>
368
+ )}
369
+
370
+ {!generating && !result && (
371
+ <div className="flex-1 flex flex-col items-center justify-center gap-2 px-8 text-center text-slate-300">
372
+ <svg className="w-10 h-10" fill="none" stroke="currentColor" viewBox="0 0 24 24">
373
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={1.5}
374
+ 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" />
375
+ </svg>
376
+ <p className="text-sm font-medium text-slate-400">Custom architecture will appear here</p>
377
+ <p className="text-xs text-slate-300">Fill in the form and click Generate</p>
378
+ </div>
379
+ )}
380
+
381
+ {!generating && result && (
382
+ <div className="flex-1 flex flex-col gap-3 px-5 py-4 overflow-y-auto">
383
+ <div className="flex items-start justify-between gap-2">
384
+ <span className="font-semibold text-slate-900 text-sm">{result.name}</span>
385
+ {result.requiresCloudProvider
386
+ ? <span className="text-xs bg-purple-100 text-purple-700 px-2 py-0.5 rounded-full">Cloud</span>
387
+ : <span className="text-xs bg-green-100 text-green-700 px-2 py-0.5 rounded-full">Local</span>
388
+ }
389
+ </div>
390
+ <p className="text-xs text-slate-600 leading-relaxed">{result.description}</p>
391
+ {result.bestFor && (
392
+ <p className="text-xs text-slate-500 italic">Best for: {result.bestFor}</p>
393
+ )}
394
+ {result.costTier && (
395
+ <p className={`text-sm font-semibold ${COST_TIER_COLOR[result.costTier] ?? 'text-slate-500'}`}>
396
+ {result.costTier}
397
+ </p>
398
+ )}
399
+ {result.migrationPath && (
400
+ <p className="text-xs text-slate-400">
401
+ Migration: {result.migrationPath.estimatedMigrationEffort}
402
+ ({result.migrationPath.migrationComplexity})
403
+ </p>
404
+ )}
405
+ </div>
406
+ )}
407
+ </div>
408
+ </div>
409
+ </div>
410
+ </div>
411
+
412
+ {showRefinePopup && result && (
413
+ <RefinePopup
414
+ onSubmit={handleRefine}
415
+ onClose={() => setShowRefinePopup(false)}
416
+ />
417
+ )}
418
+ </>
419
+ );
420
+ }