@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,204 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { ExternalLink } from 'lucide-react';
|
|
3
|
+
import { saveModelPricing } from '../../lib/api';
|
|
4
|
+
|
|
5
|
+
const PROVIDER_COLORS = {
|
|
6
|
+
claude: 'bg-orange-50 text-orange-700 border-orange-200',
|
|
7
|
+
gemini: 'bg-blue-50 text-blue-700 border-blue-200',
|
|
8
|
+
openai: 'bg-green-50 text-green-700 border-green-200',
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
const UNIT_OPTIONS = [
|
|
12
|
+
{ value: 'million', label: 'per 1M tokens' },
|
|
13
|
+
{ value: 'thousand', label: 'per 1K tokens' },
|
|
14
|
+
];
|
|
15
|
+
|
|
16
|
+
function initState(models) {
|
|
17
|
+
const state = {};
|
|
18
|
+
for (const [modelId, info] of Object.entries(models)) {
|
|
19
|
+
state[modelId] = {
|
|
20
|
+
input: String(info.pricing?.input ?? ''),
|
|
21
|
+
output: String(info.pricing?.output ?? ''),
|
|
22
|
+
unit: info.pricing?.unit ?? 'million',
|
|
23
|
+
source: info.pricing?.source ?? '',
|
|
24
|
+
lastUpdated: info.pricing?.lastUpdated ?? '',
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
return state;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function formatDate(iso) {
|
|
31
|
+
if (!iso) return null;
|
|
32
|
+
const d = new Date(iso + 'T00:00:00');
|
|
33
|
+
return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short', day: 'numeric' });
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function ModelPricingTab({ settings, onSaved }) {
|
|
37
|
+
const models = settings.models || {};
|
|
38
|
+
const [pricing, setPricing] = useState(() => initState(models));
|
|
39
|
+
const [status, setStatus] = useState(null); // null | 'saving' | 'saved' | 'error'
|
|
40
|
+
|
|
41
|
+
const modelEntries = Object.entries(models);
|
|
42
|
+
|
|
43
|
+
const update = (modelId, field, value) => {
|
|
44
|
+
setPricing((prev) => ({
|
|
45
|
+
...prev,
|
|
46
|
+
[modelId]: { ...prev[modelId], [field]: value },
|
|
47
|
+
}));
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const handleSave = async () => {
|
|
51
|
+
setStatus('saving');
|
|
52
|
+
try {
|
|
53
|
+
// Build payload: { modelId: { pricing: { input, output, unit } } }
|
|
54
|
+
const payload = {};
|
|
55
|
+
for (const [modelId, p] of Object.entries(pricing)) {
|
|
56
|
+
payload[modelId] = {
|
|
57
|
+
pricing: {
|
|
58
|
+
input: parseFloat(p.input) || 0,
|
|
59
|
+
output: parseFloat(p.output) || 0,
|
|
60
|
+
unit: p.unit,
|
|
61
|
+
source: p.source,
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
await saveModelPricing(payload);
|
|
66
|
+
setStatus('saved');
|
|
67
|
+
onSaved();
|
|
68
|
+
setTimeout(() => setStatus(null), 2000);
|
|
69
|
+
} catch {
|
|
70
|
+
setStatus('error');
|
|
71
|
+
setTimeout(() => setStatus(null), 2000);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
if (modelEntries.length === 0) {
|
|
76
|
+
return (
|
|
77
|
+
<div className="px-5 py-8 text-center">
|
|
78
|
+
<p className="text-sm text-slate-500">
|
|
79
|
+
No models configured yet. Run your first ceremony to populate model settings.
|
|
80
|
+
</p>
|
|
81
|
+
</div>
|
|
82
|
+
);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return (
|
|
86
|
+
<div className="px-5 py-4 flex flex-col gap-4">
|
|
87
|
+
<p className="text-xs text-slate-500">
|
|
88
|
+
Set the cost per token for each model. These rates are used by the cost tracker
|
|
89
|
+
to estimate LLM spend. Prices are in <strong>USD</strong>.
|
|
90
|
+
</p>
|
|
91
|
+
|
|
92
|
+
<div className="flex flex-col gap-2">
|
|
93
|
+
{modelEntries.map(([modelId, info]) => {
|
|
94
|
+
const p = pricing[modelId] ?? { input: '', output: '', unit: 'million' };
|
|
95
|
+
const providerColor = PROVIDER_COLORS[info.provider] || 'bg-slate-50 text-slate-600 border-slate-200';
|
|
96
|
+
|
|
97
|
+
return (
|
|
98
|
+
<div key={modelId} className="border border-slate-200 rounded-lg p-4">
|
|
99
|
+
{/* Model header */}
|
|
100
|
+
<div className="flex items-center gap-2 mb-3">
|
|
101
|
+
<span className="text-sm font-semibold text-slate-800">
|
|
102
|
+
{info.displayName || modelId}
|
|
103
|
+
</span>
|
|
104
|
+
<span className={`text-xs font-medium border rounded-full px-2 py-0.5 ${providerColor}`}>
|
|
105
|
+
{info.provider}
|
|
106
|
+
</span>
|
|
107
|
+
<span className="text-xs text-slate-400 font-mono ml-auto">{modelId}</span>
|
|
108
|
+
{p.lastUpdated && (
|
|
109
|
+
<span className="text-xs text-slate-400 whitespace-nowrap">
|
|
110
|
+
verified {formatDate(p.lastUpdated)}
|
|
111
|
+
</span>
|
|
112
|
+
)}
|
|
113
|
+
</div>
|
|
114
|
+
|
|
115
|
+
{/* Pricing rows */}
|
|
116
|
+
<div className="grid grid-cols-[80px_1fr_1fr] gap-x-3 gap-y-2 items-center text-xs">
|
|
117
|
+
{/* Column headers */}
|
|
118
|
+
<div />
|
|
119
|
+
<div className="text-slate-400 font-medium">Price (USD $)</div>
|
|
120
|
+
<div className="text-slate-400 font-medium">Unit</div>
|
|
121
|
+
|
|
122
|
+
{/* Input row */}
|
|
123
|
+
<label className="text-slate-600 font-medium">Input</label>
|
|
124
|
+
<input
|
|
125
|
+
type="number"
|
|
126
|
+
min="0"
|
|
127
|
+
step="0.01"
|
|
128
|
+
value={p.input}
|
|
129
|
+
onChange={(e) => update(modelId, 'input', e.target.value)}
|
|
130
|
+
placeholder="0.00"
|
|
131
|
+
className="rounded-md border border-slate-300 px-2 py-1.5 text-slate-900 focus:outline-none focus:ring-2 focus:ring-blue-500 w-full max-w-[120px]"
|
|
132
|
+
/>
|
|
133
|
+
<select
|
|
134
|
+
value={p.unit}
|
|
135
|
+
onChange={(e) => update(modelId, 'unit', e.target.value)}
|
|
136
|
+
className="rounded-md border border-slate-300 px-2 py-1.5 text-slate-900 focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white w-full max-w-[160px]"
|
|
137
|
+
>
|
|
138
|
+
{UNIT_OPTIONS.map((opt) => (
|
|
139
|
+
<option key={opt.value} value={opt.value}>{opt.label}</option>
|
|
140
|
+
))}
|
|
141
|
+
</select>
|
|
142
|
+
|
|
143
|
+
{/* Output row */}
|
|
144
|
+
<label className="text-slate-600 font-medium">Output</label>
|
|
145
|
+
<input
|
|
146
|
+
type="number"
|
|
147
|
+
min="0"
|
|
148
|
+
step="0.01"
|
|
149
|
+
value={p.output}
|
|
150
|
+
onChange={(e) => update(modelId, 'output', e.target.value)}
|
|
151
|
+
placeholder="0.00"
|
|
152
|
+
className="rounded-md border border-slate-300 px-2 py-1.5 text-slate-900 focus:outline-none focus:ring-2 focus:ring-blue-500 w-full max-w-[120px]"
|
|
153
|
+
/>
|
|
154
|
+
{/* Unit selector is shared — show a static label for output to keep layout consistent */}
|
|
155
|
+
<span className="text-slate-400">
|
|
156
|
+
{UNIT_OPTIONS.find((o) => o.value === p.unit)?.label}
|
|
157
|
+
</span>
|
|
158
|
+
|
|
159
|
+
{/* Source URL row — aligned with price inputs */}
|
|
160
|
+
<label className="text-slate-600 font-medium">Source</label>
|
|
161
|
+
<div className="col-span-2 flex items-center gap-1.5">
|
|
162
|
+
<input
|
|
163
|
+
type="url"
|
|
164
|
+
value={p.source}
|
|
165
|
+
readOnly
|
|
166
|
+
placeholder="https://provider.com/pricing"
|
|
167
|
+
className="flex-1 rounded-md border border-slate-200 bg-slate-50 px-2 py-1.5 text-xs text-slate-500 cursor-default focus:outline-none min-w-0"
|
|
168
|
+
/>
|
|
169
|
+
{p.source && (
|
|
170
|
+
<a
|
|
171
|
+
href={p.source}
|
|
172
|
+
target="_blank"
|
|
173
|
+
rel="noopener noreferrer"
|
|
174
|
+
className="text-blue-500 hover:text-blue-700 shrink-0"
|
|
175
|
+
title="Open pricing page"
|
|
176
|
+
>
|
|
177
|
+
<ExternalLink className="w-3.5 h-3.5" />
|
|
178
|
+
</a>
|
|
179
|
+
)}
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
</div>
|
|
183
|
+
);
|
|
184
|
+
})}
|
|
185
|
+
</div>
|
|
186
|
+
|
|
187
|
+
<div className="flex justify-end pt-1">
|
|
188
|
+
<button
|
|
189
|
+
type="button"
|
|
190
|
+
onClick={handleSave}
|
|
191
|
+
disabled={status === 'saving'}
|
|
192
|
+
className="px-4 py-2 text-sm font-medium bg-slate-900 text-white rounded-md hover:bg-slate-700 transition-colors disabled:opacity-40"
|
|
193
|
+
>
|
|
194
|
+
{status === 'saving' ? (
|
|
195
|
+
<span className="inline-flex items-center gap-2">
|
|
196
|
+
<span className="w-3.5 h-3.5 border border-white/40 border-t-white rounded-full animate-spin" />
|
|
197
|
+
Saving…
|
|
198
|
+
</span>
|
|
199
|
+
) : status === 'saved' ? '✓ Saved' : status === 'error' ? '✗ Error' : 'Save Pricing'}
|
|
200
|
+
</button>
|
|
201
|
+
</div>
|
|
202
|
+
</div>
|
|
203
|
+
);
|
|
204
|
+
}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { saveGeneralSettings } from '../../lib/api';
|
|
3
|
+
|
|
4
|
+
export function ServersTab({ settings, onSaved }) {
|
|
5
|
+
const [kanbanPort, setKanbanPort] = useState(String(settings.kanbanPort || 4174));
|
|
6
|
+
const [docsPort, setDocsPort] = useState(String(settings.docsPort || 4173));
|
|
7
|
+
const [boardTitle, setBoardTitle] = useState(settings.boardTitle || 'AVC Kanban Board');
|
|
8
|
+
const [portsStatus, setPortsStatus] = useState(null); // null | 'saving' | 'saved' | 'error'
|
|
9
|
+
const [titleStatus, setTitleStatus] = useState(null);
|
|
10
|
+
|
|
11
|
+
const handleSavePorts = async () => {
|
|
12
|
+
setPortsStatus('saving');
|
|
13
|
+
try {
|
|
14
|
+
await saveGeneralSettings({
|
|
15
|
+
kanbanPort: Number(kanbanPort),
|
|
16
|
+
docsPort: Number(docsPort),
|
|
17
|
+
});
|
|
18
|
+
setPortsStatus('saved');
|
|
19
|
+
onSaved();
|
|
20
|
+
setTimeout(() => setPortsStatus(null), 2000);
|
|
21
|
+
} catch {
|
|
22
|
+
setPortsStatus('error');
|
|
23
|
+
setTimeout(() => setPortsStatus(null), 2000);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const handleSaveTitle = async () => {
|
|
28
|
+
const trimmed = boardTitle.trim();
|
|
29
|
+
if (!trimmed) return;
|
|
30
|
+
setTitleStatus('saving');
|
|
31
|
+
try {
|
|
32
|
+
await saveGeneralSettings({ boardTitle: trimmed });
|
|
33
|
+
setTitleStatus('saved');
|
|
34
|
+
onSaved();
|
|
35
|
+
setTimeout(() => setTitleStatus(null), 2000);
|
|
36
|
+
} catch {
|
|
37
|
+
setTitleStatus('error');
|
|
38
|
+
setTimeout(() => setTitleStatus(null), 2000);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<div className="px-5 py-4 flex flex-col gap-6">
|
|
44
|
+
{/* General section */}
|
|
45
|
+
<div>
|
|
46
|
+
<h3 className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-3">General</h3>
|
|
47
|
+
<div className="flex items-center gap-3">
|
|
48
|
+
<label className="text-sm text-slate-700 w-28 flex-shrink-0">Board title</label>
|
|
49
|
+
<input
|
|
50
|
+
type="text"
|
|
51
|
+
value={boardTitle}
|
|
52
|
+
onChange={(e) => setBoardTitle(e.target.value)}
|
|
53
|
+
onKeyDown={(e) => { if (e.key === 'Enter') handleSaveTitle(); }}
|
|
54
|
+
className="flex-1 max-w-xs rounded-md border border-slate-300 px-2 py-1.5 text-sm text-slate-900 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
55
|
+
/>
|
|
56
|
+
<button
|
|
57
|
+
type="button"
|
|
58
|
+
onClick={handleSaveTitle}
|
|
59
|
+
disabled={!boardTitle.trim() || titleStatus === 'saving'}
|
|
60
|
+
className="px-3 py-1.5 text-xs font-medium bg-slate-900 text-white rounded-md hover:bg-slate-700 transition-colors disabled:opacity-40"
|
|
61
|
+
>
|
|
62
|
+
{titleStatus === 'saving' ? (
|
|
63
|
+
<span className="inline-flex items-center gap-1">
|
|
64
|
+
<span className="w-3 h-3 border border-white/40 border-t-white rounded-full animate-spin" />
|
|
65
|
+
Saving
|
|
66
|
+
</span>
|
|
67
|
+
) : titleStatus === 'saved' ? '✓ Saved' : titleStatus === 'error' ? '✗ Error' : 'Save'}
|
|
68
|
+
</button>
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
|
|
72
|
+
{/* Ports section */}
|
|
73
|
+
<div>
|
|
74
|
+
<h3 className="text-xs font-semibold text-slate-500 uppercase tracking-wide mb-3">Server Ports</h3>
|
|
75
|
+
<div className="flex flex-col gap-3">
|
|
76
|
+
<div className="flex items-center gap-3">
|
|
77
|
+
<label className="text-sm text-slate-700 w-28 flex-shrink-0">Kanban board</label>
|
|
78
|
+
<input
|
|
79
|
+
type="number"
|
|
80
|
+
min="1024"
|
|
81
|
+
max="65535"
|
|
82
|
+
value={kanbanPort}
|
|
83
|
+
onChange={(e) => setKanbanPort(e.target.value)}
|
|
84
|
+
className="w-24 rounded-md border border-slate-300 px-2 py-1.5 text-sm text-slate-900 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
<div className="flex items-center gap-3">
|
|
88
|
+
<label className="text-sm text-slate-700 w-28 flex-shrink-0">Documentation</label>
|
|
89
|
+
<input
|
|
90
|
+
type="number"
|
|
91
|
+
min="1024"
|
|
92
|
+
max="65535"
|
|
93
|
+
value={docsPort}
|
|
94
|
+
onChange={(e) => setDocsPort(e.target.value)}
|
|
95
|
+
className="w-24 rounded-md border border-slate-300 px-2 py-1.5 text-sm text-slate-900 focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
<div className="flex items-center gap-3 pt-1">
|
|
99
|
+
<div className="w-28 flex-shrink-0" />
|
|
100
|
+
<button
|
|
101
|
+
type="button"
|
|
102
|
+
onClick={handleSavePorts}
|
|
103
|
+
disabled={portsStatus === 'saving'}
|
|
104
|
+
className="px-3 py-1.5 text-xs font-medium bg-slate-900 text-white rounded-md hover:bg-slate-700 transition-colors disabled:opacity-40"
|
|
105
|
+
>
|
|
106
|
+
{portsStatus === 'saving' ? (
|
|
107
|
+
<span className="inline-flex items-center gap-1">
|
|
108
|
+
<span className="w-3 h-3 border border-white/40 border-t-white rounded-full animate-spin" />
|
|
109
|
+
Saving
|
|
110
|
+
</span>
|
|
111
|
+
) : portsStatus === 'saved' ? '✓ Saved' : portsStatus === 'error' ? '✗ Error' : 'Save'}
|
|
112
|
+
</button>
|
|
113
|
+
</div>
|
|
114
|
+
</div>
|
|
115
|
+
<p className="text-xs text-slate-400 mt-3">
|
|
116
|
+
Changing ports requires restarting the servers (run <code className="font-mono bg-slate-100 px-1 rounded">/kanban</code> and <code className="font-mono bg-slate-100 px-1 rounded">/documentation</code>).
|
|
117
|
+
</p>
|
|
118
|
+
</div>
|
|
119
|
+
</div>
|
|
120
|
+
);
|
|
121
|
+
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { X } from 'lucide-react';
|
|
3
|
+
import { ApiKeysTab } from './ApiKeysTab';
|
|
4
|
+
import { CeremonyModelsTab } from './CeremonyModelsTab';
|
|
5
|
+
import { ServersTab } from './ServersTab';
|
|
6
|
+
import { ModelPricingTab } from './ModelPricingTab';
|
|
7
|
+
import { AgentsTab } from './AgentsTab';
|
|
8
|
+
import { CostThresholdsTab } from './CostThresholdsTab';
|
|
9
|
+
|
|
10
|
+
const TABS = [
|
|
11
|
+
{ id: 'api-keys', label: 'API Keys' },
|
|
12
|
+
{ id: 'ceremonies', label: 'Ceremony Models' },
|
|
13
|
+
{ id: 'pricing', label: 'Model Pricing' },
|
|
14
|
+
{ id: 'cost-thresholds', label: 'Cost Limits' },
|
|
15
|
+
{ id: 'servers', label: 'Servers & Ports' },
|
|
16
|
+
{ id: 'agents', label: 'Agents' },
|
|
17
|
+
];
|
|
18
|
+
|
|
19
|
+
export function SettingsModal({ settings, models, onClose, onSaved }) {
|
|
20
|
+
const [activeTab, setActiveTab] = useState('api-keys');
|
|
21
|
+
|
|
22
|
+
return (
|
|
23
|
+
<div
|
|
24
|
+
className="fixed inset-0 z-[60] flex items-center justify-center bg-black/50 backdrop-blur-sm p-4"
|
|
25
|
+
onClick={(e) => { if (e.target === e.currentTarget) onClose(); }}
|
|
26
|
+
>
|
|
27
|
+
<div
|
|
28
|
+
className="w-full max-w-2xl bg-white rounded-2xl shadow-2xl flex flex-col overflow-hidden"
|
|
29
|
+
style={{ height: '85vh' }}
|
|
30
|
+
>
|
|
31
|
+
{/* Header */}
|
|
32
|
+
<div className="flex items-center justify-between px-5 py-4 border-b border-slate-100 flex-shrink-0">
|
|
33
|
+
<h2 className="text-base font-semibold text-slate-900">⚙ Project Settings</h2>
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
onClick={onClose}
|
|
37
|
+
className="text-slate-400 hover:text-slate-600 transition-colors ml-4"
|
|
38
|
+
aria-label="Close"
|
|
39
|
+
>
|
|
40
|
+
<X className="w-5 h-5" />
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
|
|
44
|
+
{/* Tab bar */}
|
|
45
|
+
<div className="flex border-b border-slate-100 flex-shrink-0 px-5">
|
|
46
|
+
{TABS.map((tab) => (
|
|
47
|
+
<button
|
|
48
|
+
key={tab.id}
|
|
49
|
+
type="button"
|
|
50
|
+
onClick={() => setActiveTab(tab.id)}
|
|
51
|
+
className={`px-3 py-3 text-sm font-medium transition-colors border-b-2 -mb-px ${
|
|
52
|
+
activeTab === tab.id
|
|
53
|
+
? 'border-slate-900 text-slate-900'
|
|
54
|
+
: 'border-transparent text-slate-500 hover:text-slate-700'
|
|
55
|
+
}`}
|
|
56
|
+
>
|
|
57
|
+
{tab.label}
|
|
58
|
+
</button>
|
|
59
|
+
))}
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{/* Tab content — scrollable */}
|
|
63
|
+
<div className="flex-1 overflow-y-auto">
|
|
64
|
+
{activeTab === 'api-keys' && (
|
|
65
|
+
<ApiKeysTab settings={settings} onSaved={onSaved} />
|
|
66
|
+
)}
|
|
67
|
+
{activeTab === 'ceremonies' && (
|
|
68
|
+
<CeremonyModelsTab settings={settings} models={models} onSaved={onSaved} />
|
|
69
|
+
)}
|
|
70
|
+
{activeTab === 'pricing' && (
|
|
71
|
+
<ModelPricingTab settings={settings} onSaved={onSaved} />
|
|
72
|
+
)}
|
|
73
|
+
{activeTab === 'cost-thresholds' && (
|
|
74
|
+
<CostThresholdsTab settings={settings} onSaved={onSaved} />
|
|
75
|
+
)}
|
|
76
|
+
{activeTab === 'servers' && (
|
|
77
|
+
<ServersTab settings={settings} onSaved={onSaved} />
|
|
78
|
+
)}
|
|
79
|
+
{activeTab === 'agents' && <AgentsTab />}
|
|
80
|
+
</div>
|
|
81
|
+
</div>
|
|
82
|
+
</div>
|
|
83
|
+
);
|
|
84
|
+
}
|