crewswarm 0.9.2 → 0.9.4
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 +22 -9
- package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js +1 -0
- package/apps/dashboard/dist/assets/chat-core-uXb_C0GM.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js +1 -0
- package/apps/dashboard/dist/assets/cli-process-CNZ_UBCt.js.br +0 -0
- package/apps/dashboard/dist/assets/index-BeVllEj_.js +2 -0
- package/apps/dashboard/dist/assets/index-BeVllEj_.js.br +0 -0
- package/apps/dashboard/dist/assets/{index-CF0aJRtC.css → index-D-sRshvg.css} +1 -1
- package/apps/dashboard/dist/assets/index-D-sRshvg.css.br +0 -0
- package/apps/dashboard/dist/assets/tab-benchmarks-tab-BHjKCPm3.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js +1 -0
- package/apps/dashboard/dist/assets/tab-models-tab-dNRgsTOO.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-pm-loop-tab-Bfd449B4.js → tab-pm-loop-tab-DiAPTJXu.js} +1 -1
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-DiAPTJXu.js.br +0 -0
- package/apps/dashboard/dist/assets/{tab-projects-tab-DhNWnlzt.js → tab-projects-tab-SFH4E--a.js} +1 -1
- package/apps/dashboard/dist/assets/tab-projects-tab-SFH4E--a.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js +1 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-CuvH_Fj_.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js +1 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-DR7PJ7NB.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js +1 -0
- package/apps/dashboard/dist/assets/tab-testing-tab-CezZOZcJ.js.br +0 -0
- package/apps/dashboard/dist/index.html +135 -15
- package/apps/dashboard/dist/index.html.br +0 -0
- package/apps/dashboard/dist/index.html.gz +0 -0
- package/apps/vibe/README.md +2 -2
- package/apps/vibe/package.json +1 -1
- package/apps/vibe/server.mjs +101 -56
- package/crew-lead.mjs +34 -4
- package/lib/bridges/cli-executor.mjs +1 -1
- package/lib/bridges/gateway-ws.mjs +4 -0
- package/lib/browser/passthrough-stderr.js +1 -0
- package/lib/chat/project-messages.mjs +3 -5
- package/lib/cli-process-tracker.mjs +3 -2
- package/lib/contacts/identity-linker.mjs +1 -0
- package/lib/crew-judge/judge.mjs +19 -18
- package/lib/crew-lead/agent-manager.mjs +1 -1
- package/lib/crew-lead/background.mjs +14 -1
- package/lib/crew-lead/chat-handler.mjs +38 -1
- package/lib/crew-lead/http-server.mjs +106 -57
- package/lib/crew-lead/llm-caller.mjs +24 -8
- package/lib/crew-lead/prompts.mjs +14 -1
- package/lib/crew-lead/tools.mjs +3 -2
- package/lib/crew-lead/wave-dispatcher.mjs +19 -5
- package/lib/crew-lead/ws-router.mjs +219 -27
- package/lib/engines/crew-cli.mjs +1 -1
- package/lib/engines/engine-registry.mjs +14 -3
- package/lib/engines/rt-envelope.mjs +1 -0
- package/lib/engines/runners.mjs +28 -4
- package/lib/gemini-cli-passthrough-noise.mjs +1 -1
- package/lib/integrations/code-search.mjs +4 -3
- package/lib/memory/shared-adapter.mjs +23 -10
- package/lib/pipeline/manager.mjs +2 -1
- package/lib/runtime/config.mjs +1 -1
- package/lib/runtime/paths.mjs +12 -8
- package/lib/runtime/spending.mjs +2 -1
- package/package.json +42 -14
- package/scripts/capture-build-flow.mjs +118 -0
- package/scripts/coverage-report.mjs +209 -0
- package/scripts/coverage-summary.mjs +47 -0
- package/scripts/dashboard-validation.mjs +76 -0
- package/scripts/dashboard.mjs +1667 -551
- package/scripts/generate-openapi.mjs +683 -277
- package/scripts/live-bridge-matrix.mjs +79 -0
- package/scripts/live-cli-matrix.mjs +166 -0
- package/scripts/live-crewchat-check.mjs +42 -0
- package/scripts/live-engine-matrix.mjs +50 -0
- package/scripts/live-provider-failover-matrix.mjs +107 -0
- package/scripts/live-provider-matrix.mjs +228 -0
- package/scripts/restart-all-from-repo.sh +4 -4
- package/scripts/restart-service.sh +12 -9
- package/scripts/smoke-dispatch.mjs +4 -1
- package/scripts/test-blast-radius.mjs +204 -0
- package/scripts/test-report-summary.mjs +88 -0
- package/scripts/test-reporter.mjs +651 -0
- package/scripts/test-rerun.mjs +136 -0
- package/scripts/tmux-bridge +130 -0
- package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js +0 -1
- package/apps/dashboard/dist/assets/chat-core-Cx4sTxDd.js.br +0 -0
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js +0 -1
- package/apps/dashboard/dist/assets/cli-process-COMRNPqr.js.br +0 -0
- package/apps/dashboard/dist/assets/index-CF0aJRtC.css.br +0 -0
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js +0 -2
- package/apps/dashboard/dist/assets/index-DnClJ1ee.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js +0 -1
- package/apps/dashboard/dist/assets/tab-models-tab-BLEjmd19.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-pm-loop-tab-Bfd449B4.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-projects-tab-DhNWnlzt.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js +0 -1
- package/apps/dashboard/dist/assets/tab-settings-tab-Bn4nXtDe.js.br +0 -0
- package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js +0 -1
- package/apps/dashboard/dist/assets/tab-skills-tab-BpY0uZHW.js.br +0 -0
- package/apps/dashboard/index.html +0 -6529
- package/apps/dashboard/package.json +0 -15
- package/apps/dashboard/src/app.js +0 -2828
- package/apps/dashboard/src/app.js.br +0 -0
- package/apps/dashboard/src/app.js.gz +0 -0
- package/apps/dashboard/src/chat/chat-actions.js +0 -1847
- package/apps/dashboard/src/chat/chat-actions.js.br +0 -0
- package/apps/dashboard/src/chat/unified-messages.js +0 -327
- package/apps/dashboard/src/chat/unified-messages.js.br +0 -0
- package/apps/dashboard/src/cli-process.js +0 -208
- package/apps/dashboard/src/cli-process.js.br +0 -0
- package/apps/dashboard/src/cli-process.js.gz +0 -0
- package/apps/dashboard/src/components/active-tasks-panel.js +0 -175
- package/apps/dashboard/src/components/active-tasks-panel.js.br +0 -0
- package/apps/dashboard/src/core/api.js +0 -18
- package/apps/dashboard/src/core/api.js.br +0 -0
- package/apps/dashboard/src/core/dom.js +0 -228
- package/apps/dashboard/src/core/dom.js.br +0 -0
- package/apps/dashboard/src/core/state.js +0 -91
- package/apps/dashboard/src/core/state.js.br +0 -0
- package/apps/dashboard/src/core/task-manager.js +0 -134
- package/apps/dashboard/src/core/task-manager.js.br +0 -0
- package/apps/dashboard/src/orchestration-status.js +0 -127
- package/apps/dashboard/src/orchestration-status.js.br +0 -0
- package/apps/dashboard/src/setup-wizard.js +0 -562
- package/apps/dashboard/src/setup-wizard.js.br +0 -0
- package/apps/dashboard/src/styles.css +0 -2085
- package/apps/dashboard/src/styles.css.br +0 -0
- package/apps/dashboard/src/styles.css.gz +0 -0
- package/apps/dashboard/src/tabs/agents-tab.js +0 -2237
- package/apps/dashboard/src/tabs/agents-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/benchmarks-tab.js +0 -229
- package/apps/dashboard/src/tabs/benchmarks-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/comms-tab.js +0 -955
- package/apps/dashboard/src/tabs/comms-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/contacts-tab.js +0 -654
- package/apps/dashboard/src/tabs/contacts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/engines-tab.js +0 -175
- package/apps/dashboard/src/tabs/engines-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/memory-tab.js +0 -182
- package/apps/dashboard/src/tabs/memory-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/models-tab.js +0 -450
- package/apps/dashboard/src/tabs/models-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/pm-loop-tab.js +0 -185
- package/apps/dashboard/src/tabs/pm-loop-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js +0 -663
- package/apps/dashboard/src/tabs/projects-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/projects-tab.js.gz +0 -0
- package/apps/dashboard/src/tabs/prompts-tab.js +0 -160
- package/apps/dashboard/src/tabs/prompts-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/services-tab.js +0 -202
- package/apps/dashboard/src/tabs/services-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/settings-tab.js +0 -861
- package/apps/dashboard/src/tabs/settings-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/skills-tab.js +0 -284
- package/apps/dashboard/src/tabs/skills-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/spending-tab.js +0 -173
- package/apps/dashboard/src/tabs/spending-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-chat-tab.js +0 -660
- package/apps/dashboard/src/tabs/swarm-chat-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/swarm-tab.js +0 -538
- package/apps/dashboard/src/tabs/swarm-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/usage-tab.js +0 -390
- package/apps/dashboard/src/tabs/usage-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/waves-tab.js +0 -238
- package/apps/dashboard/src/tabs/waves-tab.js.br +0 -0
- package/apps/dashboard/src/tabs/workflows-tab.js +0 -747
- package/apps/dashboard/src/tabs/workflows-tab.js.br +0 -0
- package/apps/vibe/.crew/agent-memory/pipeline.json +0 -304
- package/apps/vibe/.crew/cost.json +0 -17
- package/apps/vibe/.crew/json-parse-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-metrics.jsonl +0 -27
- package/apps/vibe/.crew/pipeline-runs/pipeline-0f90c392-2425-4ae5-850c-bd9d17b1d690.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-1c269dd9-a63f-4fba-af81-5cf08048ef06.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-288a7765-da24-4a22-89bc-1f3cc9b0562c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-2c78fd22-a657-4bd1-bc49-0679fb384409.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3da23550-22ed-4904-9a0a-8e79c1f3024c.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-3e6fe08d-3264-404a-8df3-aab7efef10e7.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-42eec610-57fe-4e09-9e7e-b315038495c2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4438eb4c-ae13-42b1-90e2-b043d8983be8.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4740a9f5-86e7-44b6-a394-de433e291727.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-49e1da6a-957e-48fd-9220-415019e4f8e2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-4c9251db-be68-427b-a3fc-a264f2b5778d.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6413fa33-a802-4b57-a8c0-a9056ad67842.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-65e29a57-664d-4196-8109-017e364f182e.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6aa04bc5-9593-4b1f-b58d-3bf2978cb602.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-6e1cba53-9b70-457e-99e0-59199149dd21.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-749f41cc-4dac-4204-be64-873a6080a0d2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-74d68121-e181-4864-bd9a-c3211341dfaf.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-8509bc24-142d-4e07-b44a-a50bf99d1103.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-960339c6-07ca-43ce-9900-f6e1702b39b9.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9bef2dd2-6122-42e5-b3d9-19f4d80f9e40.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9c6480a9-7031-4146-b241-825b9a2d1de1.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-9fd42426-8492-4157-9d5f-e1537c060489.jsonl +0 -2
- package/apps/vibe/.crew/pipeline-runs/pipeline-ad6d40a3-2f5e-46a9-a345-47caaccc51aa.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-bc606133-8d5b-4535-8d85-f1a29cdaa981.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1418f4e-b773-4ca1-84a3-216acf36e2f2.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c1a13ccd-634a-4d01-a4a7-1177b8a752ff.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-c7d27b42-249e-4bd4-8f26-6aa998110b8a.jsonl +0 -5
- package/apps/vibe/.crew/pipeline-runs/pipeline-cca2e9b9-4a34-4d25-a311-5c793fa7e91e.jsonl +0 -5
- package/apps/vibe/.crew/sandbox.json +0 -7
- package/apps/vibe/.crew/session.json +0 -330
- package/apps/vibe/.crew/training-data.jsonl +0 -0
- package/apps/vibe/.github/workflows/studio-quality.yml +0 -37
- package/apps/vibe/.studio-data/project-messages/chuck-norris.jsonl +0 -18
- package/apps/vibe/.studio-data/project-messages/general.jsonl +0 -81
- package/apps/vibe/.studio-data/project-messages/studio-local.jsonl +0 -18
- package/apps/vibe/ARCHITECTURE.md +0 -3393
- package/apps/vibe/QUICK-REFERENCE.md +0 -211
- package/apps/vibe/ROADMAP.md +0 -41
- package/apps/vibe/STUDIO-SETUP-COMPLETE.md +0 -35
- package/apps/vibe/VISUAL-GUIDE.md +0 -378
- package/apps/vibe/capture-demo.mjs +0 -160
- package/apps/vibe/capture-full-demo.mjs +0 -255
- package/apps/vibe/capture-quickstart.mjs +0 -256
- package/apps/vibe/capture-vibe-assets.mjs +0 -71
- package/apps/vibe/capture-vibe-video.mjs +0 -260
- package/apps/vibe/check-buttons.js +0 -41
- package/apps/vibe/diagnose.html +0 -106
- package/apps/vibe/fix-buttons.js +0 -103
- package/apps/vibe/index.html +0 -3404
- package/apps/vibe/package-lock.json +0 -920
- package/apps/vibe/scripts/studio-pty-host.py +0 -117
- package/apps/vibe/src/main.js +0 -2940
- package/apps/vibe/src/register-all-languages.js +0 -98
- package/apps/vibe/start-studio.sh +0 -11
- package/apps/vibe/test/accessibility-tests.js +0 -77
- package/apps/vibe/test/browser-performance-audit.mjs +0 -205
- package/apps/vibe/test/performance-tests.js +0 -120
- package/apps/vibe/test/security-tests.js +0 -213
- package/apps/vibe/tests/e2e.local.mjs +0 -54
- package/apps/vibe/tests/server.smoke.mjs +0 -106
- package/apps/vibe/update_website.mjs +0 -74
- package/apps/vibe/vite.config.js +0 -19
- package/lib/crew-lead/chat-handler.mjs.bak +0 -1274
- package/lib/engines/rt-envelope.mjs.backup-current +0 -870
|
@@ -1,450 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Models / Providers / Search-tools tab — extracted from app.js
|
|
3
|
-
* Deps: getJSON, postJSON (core/api), showNotification (core/dom)
|
|
4
|
-
* Inject: initModelsTab({ hideAllViews, setNavActive, loadAgents: loadAgents_cfg })
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { getJSON, postJSON } from '../core/api.js';
|
|
8
|
-
import { showNotification, showLoading, showError } from '../core/dom.js';
|
|
9
|
-
|
|
10
|
-
let _hideAllViews = () => {};
|
|
11
|
-
let _setNavActive = () => {};
|
|
12
|
-
let _loadAgents = () => {};
|
|
13
|
-
|
|
14
|
-
export function initModelsTab({ hideAllViews, setNavActive, loadAgents } = {}) {
|
|
15
|
-
_hideAllViews = hideAllViews || _hideAllViews;
|
|
16
|
-
_setNavActive = setNavActive || _setNavActive;
|
|
17
|
-
_loadAgents = loadAgents || _loadAgents;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
// ── Constants ──────────────────────────────────────────────────────────────────
|
|
21
|
-
|
|
22
|
-
const BUILTIN_PROVIDERS = [
|
|
23
|
-
{ id:'groq', label:'Groq', icon:'⚡', url:'https://console.groq.com/keys', hint:'Fast inference — great for crew-coder, crew-fixer' },
|
|
24
|
-
{ id:'fireworks', label:'Fireworks AI', icon:'🎆', url:'https://fireworks.ai/', hint:'OpenAI-compatible inference platform — fast serverless models, custom deployments, and easy model discovery' },
|
|
25
|
-
{ id:'anthropic', label:'Anthropic', icon:'🟣', url:'https://console.anthropic.com/', hint:'Claude models — best for complex reasoning tasks' },
|
|
26
|
-
{ id:'openai', label:'OpenAI (API)', icon:'🟢', url:'https://platform.openai.com/api-keys', hint:'GPT-4o and o-series — pay per use with API key' },
|
|
27
|
-
{ id:'cerebras', label:'Cerebras', icon:'🧠', url:'https://cloud.cerebras.ai/', hint:'Ultra-fast inference on Cerebras hardware — llama-3.3-70b at 2,000 tok/s' },
|
|
28
|
-
{ id:'nvidia', label:'NVIDIA NIM', icon:'🎮', url:'https://build.nvidia.com/explore/discover', hint:'NVIDIA NIM microservices — Llama, Mistral, Phi and more' },
|
|
29
|
-
{ id:'openrouter', label:'OpenRouter', icon:'🔀', url:'https://openrouter.ai/keys', hint:'One API key for 400+ models — Claude, GPT-4, Gemini, Hunter Alpha, Llama and more' },
|
|
30
|
-
{ id:'perplexity', label:'Perplexity', icon:'🔍', url:'https://www.perplexity.ai/settings/api', hint:'Sonar Pro — ideal for crew-pm research tasks' },
|
|
31
|
-
{ id:'mistral', label:'Mistral', icon:'🌀', url:'https://console.mistral.ai/', hint:'Open-weight models, efficient mid-tier tasks' },
|
|
32
|
-
{ id:'deepseek', label:'DeepSeek', icon:'🌊', url:'https://platform.deepseek.com/', hint:'Low cost, strong coding performance' },
|
|
33
|
-
{ id:'together', label:'Together AI', icon:'🤝', url:'https://api.together.ai/', hint:'OpenAI-compatible access to strong open models like Qwen, DeepSeek, Llama, and more' },
|
|
34
|
-
{ id:'xai', label:'xAI (Grok)', icon:'𝕏', url:'https://console.x.ai/', hint:'Grok models with real-time X/Twitter access, vision (grok-vision-beta), 128K context — ideal for research, social media analysis' },
|
|
35
|
-
{ id:'huggingface', label:'Hugging Face', icon:'🤗', url:'https://huggingface.co/settings/tokens', hint:'Open-source model hub — access thousands of models via Inference API' },
|
|
36
|
-
{ id:'venice', label:'Venice AI', icon:'🏖️', url:'https://venice.ai/settings/api', hint:'Privacy-focused inference — no logging, no training on your data' },
|
|
37
|
-
{ id:'moonshot', label:'Moonshot / Kimi', icon:'🌙', url:'https://platform.moonshot.cn/console/api-keys', hint:'128K+ context windows — strong on long codebases, Chinese + English' },
|
|
38
|
-
{ id:'minimax', label:'MiniMax', icon:'✨', url:'https://www.minimaxi.com/', hint:'Chinese LLM provider — competitive pricing, multilingual' },
|
|
39
|
-
{ id:'volcengine', label:'Volcengine', icon:'🌋', url:'https://console.volcengine.com/ark', hint:'ByteDance Doubao models — fast inference' },
|
|
40
|
-
{ id:'qianfan', label:'Baidu Qianfan', icon:'🔵', url:'https://console.bce.baidu.com/qianfan/', hint:'Baidu ERNIE models — strong on Chinese language and reasoning' },
|
|
41
|
-
{ id:'ollama', label:'Ollama', icon:'🏠', url:'https://ollama.com/download', hint:'Local models — no API key needed, runs offline' },
|
|
42
|
-
{ id:'vllm', label:'vLLM', icon:'⚡', url:'https://docs.vllm.ai/', hint:'Self-hosted inference server — any open model, OpenAI-compatible' },
|
|
43
|
-
{ id:'sglang', label:'SGLang', icon:'⚡', url:'https://github.com/sgl-project/sglang', hint:'Self-hosted inference server — fast structured generation' },
|
|
44
|
-
{ id:'openai-local', label:'OpenAI (local)', icon:'🟢', url:'https://github.com/RayBytes/ChatMock', hint:'ChatMock — use ChatGPT Plus/Pro subscription. Run ChatMock server first (e.g. port 8000). Key ignored.' },
|
|
45
|
-
];
|
|
46
|
-
|
|
47
|
-
const SEARCH_TOOLS = [
|
|
48
|
-
{ id:'parallel', label:'Parallel', icon:'🔬', url:'https://platform.parallel.ai/signup', hint:'Deep research & web synthesis — used by crew-pm for project planning', envKey:'PARALLEL_API_KEY' },
|
|
49
|
-
{ id:'brave', label:'Brave Search', icon:'🦁', url:'https://api.search.brave.com/', hint:'Fast web search (~700ms) — best for quick agent lookups', envKey:'BRAVE_API_KEY' },
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
const PROVIDER_ICONS = {
|
|
53
|
-
opencode:'🚀', groq:'⚡', fireworks:'🎆', nvidia:'🎮', ollama:'🏠', 'openai-local':'🟢', xai:'𝕏',
|
|
54
|
-
google:'🔵', deepseek:'🌊', openai:'🟢', perplexity:'🔍', cerebras:'🧠', mistral:'🌀',
|
|
55
|
-
together:'🤝', cohere:'🔶', anthropic:'🟣', openrouter:'🔀', huggingface:'🤗',
|
|
56
|
-
venice:'🏖️', moonshot:'🌙', minimax:'✨', volcengine:'🌋', qianfan:'🔵', vllm:'⚡', sglang:'⚡',
|
|
57
|
-
};
|
|
58
|
-
|
|
59
|
-
// ── Tab entry point ────────────────────────────────────────────────────────────
|
|
60
|
-
|
|
61
|
-
export function showModels() {
|
|
62
|
-
_hideAllViews();
|
|
63
|
-
document.getElementById('modelsView').classList.add('active');
|
|
64
|
-
_setNavActive('navModels');
|
|
65
|
-
loadRTToken_local();
|
|
66
|
-
loadBuiltinProviders();
|
|
67
|
-
loadSearchTools();
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function showProviders() { showModels(); }
|
|
71
|
-
|
|
72
|
-
// ── RT Token (mirrored here for models view; source of truth in settings-tab) ─
|
|
73
|
-
|
|
74
|
-
async function loadRTToken_local() {
|
|
75
|
-
try {
|
|
76
|
-
const d = await getJSON('/api/settings/rt-token');
|
|
77
|
-
const badge = document.getElementById('rtTokenBadge');
|
|
78
|
-
const inp = document.getElementById('rtTokenInput');
|
|
79
|
-
if (!badge) return;
|
|
80
|
-
if (d.token) {
|
|
81
|
-
badge.textContent = 'set ✓';
|
|
82
|
-
badge.style.background = 'rgba(52,211,153,0.15)';
|
|
83
|
-
badge.style.color = 'var(--green)';
|
|
84
|
-
badge.style.borderColor = 'rgba(52,211,153,0.3)';
|
|
85
|
-
if (inp) inp.placeholder = '••••••••••••••••••••••• (saved)';
|
|
86
|
-
} else {
|
|
87
|
-
badge.textContent = 'not set';
|
|
88
|
-
badge.style.background = 'rgba(251,191,36,0.15)';
|
|
89
|
-
badge.style.color = 'var(--yellow)';
|
|
90
|
-
badge.style.borderColor = 'rgba(251,191,36,0.3)';
|
|
91
|
-
}
|
|
92
|
-
} catch {}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
// ── Search tools ───────────────────────────────────────────────────────────────
|
|
96
|
-
|
|
97
|
-
export async function loadSearchTools() {
|
|
98
|
-
const list = document.getElementById('searchToolsList');
|
|
99
|
-
if (!list) return;
|
|
100
|
-
let saved = {};
|
|
101
|
-
try { saved = (await getJSON('/api/search-tools')).keys || {}; } catch {}
|
|
102
|
-
list.innerHTML = SEARCH_TOOLS.map(p => {
|
|
103
|
-
const hasKey = !!saved[p.id];
|
|
104
|
-
const badge = hasKey
|
|
105
|
-
? `<span style="font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600;background:rgba(52,211,153,0.15);color:var(--green);border:1px solid rgba(52,211,153,0.3);">set ✓</span>`
|
|
106
|
-
: `<span style="font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600;background:rgba(107,114,128,0.12);color:var(--text-2);border:1px solid var(--border);">no key</span>`;
|
|
107
|
-
return `<div class="card" style="margin-bottom:8px;">
|
|
108
|
-
<div style="display:flex;align-items:center;gap:10px;cursor:pointer;" data-toggle-child=".st-body">
|
|
109
|
-
<span style="font-size:18px;width:24px;text-align:center;">${p.icon}</span>
|
|
110
|
-
<div style="flex:1;">
|
|
111
|
-
<div style="font-weight:600;font-size:13px;">${p.label}</div>
|
|
112
|
-
<div style="font-size:11px;color:var(--text-2);">${p.hint}</div>
|
|
113
|
-
</div>
|
|
114
|
-
${badge}
|
|
115
|
-
<span style="color:var(--text-2);font-size:12px;">▾</span>
|
|
116
|
-
</div>
|
|
117
|
-
<div class="st-body" style="display:none;margin-top:12px;padding-top:12px;border-top:1px solid var(--border);">
|
|
118
|
-
<div style="display:flex;gap:8px;">
|
|
119
|
-
<input id="st_${p.id}" type="password" autocomplete="new-password" placeholder="${hasKey ? '••••••••••••••• (saved — paste to update)' : 'Paste API key'}" style="flex:1;" />
|
|
120
|
-
<button data-action="saveSearchTool" data-arg="${p.id}" class="btn-purple">Save</button>
|
|
121
|
-
<button data-action="testSearchTool" data-arg="${p.id}" class="btn-ghost">Test</button>
|
|
122
|
-
<a href="${p.url}" target="_blank" class="btn-ghost" style="text-decoration:none;font-size:12px;">Keys ↗</a>
|
|
123
|
-
</div>
|
|
124
|
-
<div style="font-size:11px;color:var(--text-2);margin-top:6px;">Saved as <code style="background:rgba(255,255,255,0.06);padding:1px 5px;border-radius:4px;">${p.envKey}</code> in environment</div>
|
|
125
|
-
<div id="st_status_${p.id}" style="font-size:12px;margin-top:8px;color:var(--text-2);"></div>
|
|
126
|
-
</div>
|
|
127
|
-
</div>`;
|
|
128
|
-
}).join('');
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
export async function saveSearchTool(toolId) {
|
|
132
|
-
const inp = document.getElementById('st_' + toolId);
|
|
133
|
-
const key = inp?.value?.trim();
|
|
134
|
-
if (!key) { showNotification('Paste an API key first', 'error'); return; }
|
|
135
|
-
try {
|
|
136
|
-
await postJSON('/api/search-tools/save', { toolId, key });
|
|
137
|
-
showNotification('Key saved', 'success');
|
|
138
|
-
loadSearchTools();
|
|
139
|
-
} catch(e) { showNotification('Save failed: ' + e.message, 'error'); }
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export async function testSearchTool(toolId) {
|
|
143
|
-
const statusEl = document.getElementById('st_status_' + toolId);
|
|
144
|
-
statusEl.textContent = 'Testing…';
|
|
145
|
-
try {
|
|
146
|
-
const r = await postJSON('/api/search-tools/test', { toolId });
|
|
147
|
-
statusEl.style.color = r.ok ? 'var(--green)' : 'var(--red)';
|
|
148
|
-
statusEl.textContent = r.ok ? '✓ ' + (r.message || 'Connected') : '✗ ' + (r.error || 'Failed');
|
|
149
|
-
} catch(e) { statusEl.style.color='var(--red)'; statusEl.textContent = '✗ ' + e.message; }
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
// ── Built-in providers ─────────────────────────────────────────────────────────
|
|
153
|
-
|
|
154
|
-
export async function loadBuiltinProviders() {
|
|
155
|
-
const list = document.getElementById('builtinProvidersList');
|
|
156
|
-
if (!list) return;
|
|
157
|
-
let saved = {};
|
|
158
|
-
try { saved = (await getJSON('/api/providers/builtin')).keys || {}; } catch {}
|
|
159
|
-
const builtinIds = new Set(BUILTIN_PROVIDERS.map(p => p.id));
|
|
160
|
-
|
|
161
|
-
let html = BUILTIN_PROVIDERS.map(p => {
|
|
162
|
-
const hasKey = !!saved[p.id];
|
|
163
|
-
const isOllama = p.id === 'ollama';
|
|
164
|
-
const isOpenAiLocal = p.id === 'openai-local';
|
|
165
|
-
const badge = hasKey || isOllama || isOpenAiLocal
|
|
166
|
-
? `<span style="font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600;background:rgba(52,211,153,0.15);color:var(--green);border:1px solid rgba(52,211,153,0.3);">${(isOllama || isOpenAiLocal) && !hasKey ? 'local' : 'set ✓'}</span>`
|
|
167
|
-
: `<span style="font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600;background:rgba(107,114,128,0.12);color:var(--text-2);border:1px solid var(--border);">no key</span>`;
|
|
168
|
-
return `<div class="card" style="margin-bottom:8px;">
|
|
169
|
-
<div style="display:flex;align-items:center;gap:10px;cursor:pointer;" data-toggle-child=".bp-body">
|
|
170
|
-
<span style="font-size:18px;width:24px;text-align:center;">${p.icon}</span>
|
|
171
|
-
<div style="flex:1;">
|
|
172
|
-
<div style="font-weight:600;font-size:13px;">${p.label}</div>
|
|
173
|
-
<div style="font-size:11px;color:var(--text-2);">${p.hint}</div>
|
|
174
|
-
</div>
|
|
175
|
-
${badge}
|
|
176
|
-
<span style="color:var(--text-2);font-size:12px;">▾</span>
|
|
177
|
-
</div>
|
|
178
|
-
<div class="bp-body" style="display:none;margin-top:12px;padding-top:12px;border-top:1px solid var(--border);">
|
|
179
|
-
${isOllama ? `<div style="font-size:12px;color:var(--text-2);margin-bottom:8px;">Ollama runs locally — no API key required. Make sure Ollama is running on port 11434.</div>` : ''}
|
|
180
|
-
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
|
181
|
-
${isOllama ? '' : `<input id="bp_${p.id}" type="password" autocomplete="new-password" placeholder="${hasKey ? '••••••••••••••• (saved — paste to update)' : 'Paste API key'}" style="flex:1;min-width:180px;" />`}
|
|
182
|
-
${isOllama
|
|
183
|
-
? `<button data-action="testBuiltinProvider" data-arg="${p.id}" class="btn-ghost">Test Connection</button>
|
|
184
|
-
<button data-action="fetchBuiltinModels" data-arg="${p.id}" data-self="1" class="btn-ghost" style="background:#0f766e20;color:var(--green);border-color:#0f766e40;">↻ Models</button>`
|
|
185
|
-
: `<button data-action="saveBuiltinKey" data-arg="${p.id}" class="btn-purple">Save</button>
|
|
186
|
-
<button data-action="testBuiltinProvider" data-arg="${p.id}" class="btn-ghost">Test</button>
|
|
187
|
-
<button data-action="fetchBuiltinModels" data-arg="${p.id}" data-self="1" class="btn-ghost" style="background:#0f766e20;color:var(--green);border-color:#0f766e40;">↻ Models</button>
|
|
188
|
-
<a href="${p.url}" target="_blank" class="btn-ghost" style="text-decoration:none;font-size:12px;">Keys ↗</a>`}
|
|
189
|
-
</div>
|
|
190
|
-
<div id="bp_status_${p.id}" style="font-size:12px;margin-top:8px;color:var(--text-2);"></div>
|
|
191
|
-
<div id="bp_models_${p.id}" style="margin-top:8px;display:none;">
|
|
192
|
-
<span style="font-size:11px;color:var(--text-2);display:block;margin-bottom:4px;">Models (<span id="bp_mcount_${p.id}">0</span>):</span>
|
|
193
|
-
<span id="bp_mtags_${p.id}"></span>
|
|
194
|
-
</div>
|
|
195
|
-
</div>
|
|
196
|
-
</div>`;
|
|
197
|
-
}).join('');
|
|
198
|
-
|
|
199
|
-
try {
|
|
200
|
-
const data = await getJSON('/api/providers');
|
|
201
|
-
const customs = (data.providers || []).filter(p => !builtinIds.has(p.id) && p.id !== 'greptile');
|
|
202
|
-
if (customs.length) {
|
|
203
|
-
html += `<div style="font-size:11px;font-weight:600;color:var(--text-2);text-transform:uppercase;letter-spacing:0.08em;margin:14px 0 8px;padding:0 2px;">Custom Providers</div>`;
|
|
204
|
-
html += customs.map(p => {
|
|
205
|
-
const icon = PROVIDER_ICONS[p.id] || '🔌';
|
|
206
|
-
const hasKey = p.hasKey;
|
|
207
|
-
const badge = hasKey
|
|
208
|
-
? `<span style="font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600;background:rgba(52,211,153,0.15);color:var(--green);border:1px solid rgba(52,211,153,0.3);">key set ✓</span>`
|
|
209
|
-
: `<span style="font-size:11px;padding:2px 8px;border-radius:999px;font-weight:600;background:rgba(107,114,128,0.12);color:var(--text-2);border:1px solid var(--border);">no key</span>`;
|
|
210
|
-
const modelCount = p.models?.length || 0;
|
|
211
|
-
return `<div class="card" style="margin-bottom:8px;">
|
|
212
|
-
<div style="display:flex;align-items:center;gap:10px;cursor:pointer;" data-toggle-child=".cp-body">
|
|
213
|
-
<span style="font-size:18px;width:24px;text-align:center;">${icon}</span>
|
|
214
|
-
<div style="flex:1;">
|
|
215
|
-
<div style="font-weight:600;font-size:13px;">${p.id}</div>
|
|
216
|
-
<div style="font-size:11px;color:var(--text-2);">${p.baseUrl}${modelCount ? ' · ' + modelCount + ' models' : ''}</div>
|
|
217
|
-
</div>
|
|
218
|
-
${badge}
|
|
219
|
-
<span style="color:var(--text-2);font-size:12px;">▾</span>
|
|
220
|
-
</div>
|
|
221
|
-
<div class="cp-body" style="display:none;margin-top:12px;padding-top:12px;border-top:1px solid var(--border);">
|
|
222
|
-
<div style="display:flex;gap:8px;flex-wrap:wrap;">
|
|
223
|
-
<input id="key_${p.id}" type="password" autocomplete="new-password" placeholder="${hasKey ? '••••••••••••••• (saved — paste to update)' : 'Paste API key'}" style="flex:1;min-width:180px;" />
|
|
224
|
-
<button data-action="saveKey" data-arg="${p.id}" class="btn-purple">Save</button>
|
|
225
|
-
<button data-action="testKey" data-arg="${p.id}" class="btn-ghost">Test</button>
|
|
226
|
-
<button data-action="fetchModels" data-arg="${p.id}" data-self="1" class="btn-ghost" style="background:#0f766e20;color:var(--green);border-color:#0f766e40;">↻ Models</button>
|
|
227
|
-
</div>
|
|
228
|
-
<div style="font-size:11px;color:var(--text-2);margin-top:6px;">Base URL: <code style="font-size:10px;">${p.baseUrl}</code></div>
|
|
229
|
-
<div id="test_${p.id}" style="font-size:12px;margin-top:8px;color:var(--text-2);"></div>
|
|
230
|
-
<div id="mwrap_${p.id}" style="margin-top:8px;${modelCount ? '' : 'display:none;'}">
|
|
231
|
-
<span style="font-size:11px;color:var(--text-2);">Models (<span id="mcount_${p.id}">${modelCount}</span>):</span>
|
|
232
|
-
<span id="mtags_${p.id}">${(p.models||[]).map(m => '<span class="model-tag">' + (m.id||m) + '</span>').join('')}</span>
|
|
233
|
-
</div>
|
|
234
|
-
</div>
|
|
235
|
-
</div>`;
|
|
236
|
-
}).join('');
|
|
237
|
-
}
|
|
238
|
-
} catch {}
|
|
239
|
-
|
|
240
|
-
list.innerHTML = html;
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
export async function saveBuiltinKey(providerId) {
|
|
244
|
-
const inp = document.getElementById('bp_' + providerId);
|
|
245
|
-
const key = inp?.value?.trim();
|
|
246
|
-
if (!key && providerId !== 'openai-local') { showNotification('Paste an API key first', 'error'); return; }
|
|
247
|
-
await postJSON('/api/providers/builtin/save', { providerId, apiKey: key || '' });
|
|
248
|
-
if (inp) inp.value = '';
|
|
249
|
-
showNotification('Key saved — fetching models…');
|
|
250
|
-
await loadBuiltinProviders();
|
|
251
|
-
try {
|
|
252
|
-
const r = await postJSON('/api/providers/fetch-models', { providerId });
|
|
253
|
-
if (r.ok) {
|
|
254
|
-
const tags = document.getElementById('bp_mtags_' + providerId);
|
|
255
|
-
const count = document.getElementById('bp_mcount_' + providerId);
|
|
256
|
-
const wrap = document.getElementById('bp_models_' + providerId);
|
|
257
|
-
const status = document.getElementById('bp_status_' + providerId);
|
|
258
|
-
if (tags) tags.innerHTML = r.models.map(m => '<span class="model-tag">' + m + '</span>').join('');
|
|
259
|
-
if (count) count.textContent = r.models.length;
|
|
260
|
-
if (wrap) wrap.style.display = 'block';
|
|
261
|
-
if (status) { status.style.color = 'var(--green)'; status.textContent = '✓ ' + r.models.length + ' models'; }
|
|
262
|
-
showNotification('Key saved for ' + providerId + ' — ' + r.models.length + ' models ready');
|
|
263
|
-
_loadAgents();
|
|
264
|
-
} else {
|
|
265
|
-
showNotification('Key saved — could not fetch models: ' + (r.error || 'unknown'), 'warning');
|
|
266
|
-
}
|
|
267
|
-
} catch(e) {
|
|
268
|
-
showNotification('Key saved — model fetch failed: ' + e.message, 'warning');
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
export async function testBuiltinProvider(providerId) {
|
|
273
|
-
const statusEl = document.getElementById('bp_status_' + providerId);
|
|
274
|
-
statusEl.textContent = 'Testing…';
|
|
275
|
-
try {
|
|
276
|
-
const r = await postJSON('/api/providers/builtin/test', { providerId });
|
|
277
|
-
statusEl.style.color = r.ok ? 'var(--green)' : 'var(--red)';
|
|
278
|
-
statusEl.textContent = r.ok ? '✓ Connected — ' + (r.model || 'OK') : '✗ ' + (r.error || 'Failed');
|
|
279
|
-
} catch(e) { statusEl.style.color='var(--red)'; statusEl.textContent = '✗ ' + e.message; }
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
export async function fetchBuiltinModels(providerId, btn) {
|
|
283
|
-
const statusEl = document.getElementById('bp_status_' + providerId);
|
|
284
|
-
const orig = btn.textContent;
|
|
285
|
-
btn.textContent = 'Fetching…';
|
|
286
|
-
btn.disabled = true;
|
|
287
|
-
statusEl.textContent = '';
|
|
288
|
-
try {
|
|
289
|
-
const r = await postJSON('/api/providers/fetch-models', { providerId });
|
|
290
|
-
if (r.ok) {
|
|
291
|
-
const tags = document.getElementById('bp_mtags_' + providerId);
|
|
292
|
-
const count = document.getElementById('bp_mcount_' + providerId);
|
|
293
|
-
const wrap = document.getElementById('bp_models_' + providerId);
|
|
294
|
-
if (tags) tags.innerHTML = r.models.map(m => '<span class="model-tag">' + m + '</span>').join('');
|
|
295
|
-
if (count) count.textContent = r.models.length;
|
|
296
|
-
if (wrap) wrap.style.display = 'block';
|
|
297
|
-
statusEl.style.color = 'var(--green)';
|
|
298
|
-
statusEl.textContent = '✓ ' + r.models.length + ' models fetched' + (r.note ? ' — ' + r.note : '');
|
|
299
|
-
_loadAgents();
|
|
300
|
-
} else {
|
|
301
|
-
statusEl.style.color = 'var(--red)';
|
|
302
|
-
statusEl.textContent = '✗ ' + (r.error || 'Failed');
|
|
303
|
-
}
|
|
304
|
-
} catch(e) { statusEl.style.color='var(--red)'; statusEl.textContent = '✗ ' + e.message; }
|
|
305
|
-
finally { btn.textContent = orig; btn.disabled = false; }
|
|
306
|
-
}
|
|
307
|
-
|
|
308
|
-
// ── Legacy custom provider list (secondary view) ───────────────────────────────
|
|
309
|
-
|
|
310
|
-
export async function loadProviders() {
|
|
311
|
-
const list = document.getElementById('providersList');
|
|
312
|
-
if (!list) return;
|
|
313
|
-
showLoading(list, 'Loading providers...');
|
|
314
|
-
try {
|
|
315
|
-
const data = await getJSON('/api/providers');
|
|
316
|
-
const providers = data.providers || [];
|
|
317
|
-
if (!providers.length) { showEmpty(list, 'No providers found. Check ~/.crewswarm/crewswarm.json'); return; }
|
|
318
|
-
list.innerHTML = '';
|
|
319
|
-
providers.forEach(p => {
|
|
320
|
-
const icon = PROVIDER_ICONS[p.id] || '🔌';
|
|
321
|
-
const hasKey = p.hasKey;
|
|
322
|
-
const badgeColor = hasKey ? '#10b981' : 'var(--red-hi)';
|
|
323
|
-
const badgeText = hasKey ? '✓ key set' : '✗ no key';
|
|
324
|
-
const card = document.createElement('div');
|
|
325
|
-
card.className = 'provider-card';
|
|
326
|
-
card.innerHTML = `
|
|
327
|
-
<div class="provider-header" data-toggle-sibling="open">
|
|
328
|
-
<span style="font-size:20px;">${icon}</span>
|
|
329
|
-
<div style="flex:1;">
|
|
330
|
-
<strong style="font-size:15px;">${p.id}</strong>
|
|
331
|
-
<span class="meta" style="margin-left:10px;">${p.baseUrl}</span>
|
|
332
|
-
</div>
|
|
333
|
-
<span class="provider-badge" style="background:${badgeColor}20; color:${badgeColor}; border:1px solid ${badgeColor}40;">${badgeText}</span>
|
|
334
|
-
<span class="meta" style="margin-left:12px;">${p.models.length} model${p.models.length !== 1 ? 's' : ''}</span>
|
|
335
|
-
<span style="color:#64748b; margin-left:8px;">▼</span>
|
|
336
|
-
</div>
|
|
337
|
-
<div class="provider-body">
|
|
338
|
-
<div class="key-row">
|
|
339
|
-
<input class="key-input" type="password" autocomplete="new-password" id="key_${p.id}" value="${p.maskedKey || ''}" placeholder="Paste API key…" />
|
|
340
|
-
<button data-action="toggleKeyVis" data-arg="key_${p.id}" data-self="1" style="background:#334155; padding:6px 10px; font-size:12px;">👁</button>
|
|
341
|
-
<button data-action="saveKey" data-arg="${p.id}" style="background:#6366f1; padding:6px 14px; font-size:12px;">Save</button>
|
|
342
|
-
<button data-action="testKey" data-arg="${p.id}" style="background:#334155; padding:6px 10px; font-size:12px;">Test</button>
|
|
343
|
-
<button data-action="fetchModels" data-arg="${p.id}" data-self="1" style="background:#0f766e; padding:6px 10px; font-size:12px;">↻ Fetch models</button>
|
|
344
|
-
<span id="test_${p.id}"></span>
|
|
345
|
-
</div>
|
|
346
|
-
<div style="margin-bottom:8px;"><span class="meta">Base URL: </span><code style="font-size:11px; color:#94a3b8;">${p.baseUrl}</code></div>
|
|
347
|
-
<div><span class="meta" style="display:block; margin-bottom:6px;">Models (<span id="mcount_${p.id}">${p.models.length}</span>):</span><span id="mtags_${p.id}">${p.models.map(m => '<span class="model-tag">' + m.id + '</span>').join('')}</span></div>
|
|
348
|
-
${p.models.length === 0 ? `<div class="meta" style="margin-top:8px; color:var(--amber);" id="mnone_${p.id}">No models yet — click ↻ Fetch models</div>` : ''}
|
|
349
|
-
</div>
|
|
350
|
-
`;
|
|
351
|
-
list.appendChild(card);
|
|
352
|
-
});
|
|
353
|
-
} catch(e) { showError(list, 'Error: ' + e.message); }
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
export function toggleKeyVis(inputId, btn) {
|
|
357
|
-
const inp = document.getElementById(inputId);
|
|
358
|
-
inp.type = inp.type === 'password' ? 'text' : 'password';
|
|
359
|
-
btn.textContent = inp.type === 'password' ? '👁' : '🙈';
|
|
360
|
-
}
|
|
361
|
-
|
|
362
|
-
export async function saveKey(providerId) {
|
|
363
|
-
const inp = document.getElementById('key_' + providerId);
|
|
364
|
-
const key = inp.value.trim();
|
|
365
|
-
if (!key) { showNotification('Key is empty', true); return; }
|
|
366
|
-
try {
|
|
367
|
-
await postJSON('/api/providers/save', { providerId, apiKey: key });
|
|
368
|
-
showNotification('Saved key for ' + providerId);
|
|
369
|
-
loadProviders();
|
|
370
|
-
_loadAgents();
|
|
371
|
-
} catch(e) { showNotification('Failed: ' + e.message, true); }
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
export async function testKey(providerId) {
|
|
375
|
-
const statusEl = document.getElementById('test_' + providerId);
|
|
376
|
-
statusEl.textContent = 'testing…';
|
|
377
|
-
statusEl.className = 'meta';
|
|
378
|
-
try {
|
|
379
|
-
const r = await postJSON('/api/providers/test', { providerId });
|
|
380
|
-
statusEl.textContent = r.ok ? '✓ ' + (r.model || 'ok') : '✗ ' + r.error;
|
|
381
|
-
statusEl.className = r.ok ? 'test-ok' : 'test-err';
|
|
382
|
-
} catch(e) { statusEl.textContent = '✗ ' + e.message; statusEl.className = 'test-err'; }
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
export async function fetchModels(providerId, btn) {
|
|
386
|
-
const statusEl = document.getElementById('test_' + providerId);
|
|
387
|
-
const origText = btn.textContent;
|
|
388
|
-
btn.textContent = 'Fetching…';
|
|
389
|
-
btn.disabled = true;
|
|
390
|
-
if (statusEl) statusEl.textContent = '';
|
|
391
|
-
try {
|
|
392
|
-
const r = await postJSON('/api/providers/fetch-models', { providerId });
|
|
393
|
-
if (r.ok) {
|
|
394
|
-
const tags = document.getElementById('mtags_' + providerId);
|
|
395
|
-
const count = document.getElementById('mcount_' + providerId);
|
|
396
|
-
const none = document.getElementById('mnone_' + providerId);
|
|
397
|
-
const wrap = document.getElementById('mwrap_' + providerId);
|
|
398
|
-
if (tags) tags.innerHTML = r.models.map(m => '<span class="model-tag">' + m + '</span>').join('');
|
|
399
|
-
if (count) count.textContent = r.models.length;
|
|
400
|
-
if (none) none.style.display = 'none';
|
|
401
|
-
if (wrap) wrap.style.display = 'block';
|
|
402
|
-
if (statusEl) { statusEl.textContent = '✓ ' + r.models.length + ' models'; statusEl.className = 'test-ok'; }
|
|
403
|
-
_loadAgents();
|
|
404
|
-
} else {
|
|
405
|
-
if (statusEl) { statusEl.textContent = '✗ ' + r.error; statusEl.className = 'test-err'; }
|
|
406
|
-
}
|
|
407
|
-
} catch(e) {
|
|
408
|
-
if (statusEl) { statusEl.textContent = '✗ ' + e.message; statusEl.className = 'test-err'; }
|
|
409
|
-
}
|
|
410
|
-
finally { btn.textContent = origText; btn.disabled = false; }
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
// ── Add provider form wiring (called once on DOMContentLoaded) ─────────────────
|
|
414
|
-
|
|
415
|
-
export function initAddProviderForm() {
|
|
416
|
-
const addBtn = document.getElementById('addProviderBtn');
|
|
417
|
-
if (addBtn) {
|
|
418
|
-
addBtn.onclick = () => {
|
|
419
|
-
const form = document.getElementById('addProviderForm');
|
|
420
|
-
form.style.display = 'block';
|
|
421
|
-
setTimeout(() => form.scrollIntoView({ behavior: 'smooth', block: 'start' }), 50);
|
|
422
|
-
const firstInput = form.querySelector('input');
|
|
423
|
-
if (firstInput) setTimeout(() => firstInput.focus(), 150);
|
|
424
|
-
};
|
|
425
|
-
}
|
|
426
|
-
const cancelBtn = document.getElementById('apCancelBtn');
|
|
427
|
-
if (cancelBtn) {
|
|
428
|
-
cancelBtn.onclick = () => {
|
|
429
|
-
document.getElementById('addProviderForm').style.display = 'none';
|
|
430
|
-
};
|
|
431
|
-
}
|
|
432
|
-
const saveBtn = document.getElementById('apSaveBtn');
|
|
433
|
-
if (saveBtn) {
|
|
434
|
-
saveBtn.onclick = async () => {
|
|
435
|
-
const id = document.getElementById('apId').value.trim();
|
|
436
|
-
const baseUrl = document.getElementById('apBaseUrl').value.trim();
|
|
437
|
-
const apiKey = document.getElementById('apKey').value.trim();
|
|
438
|
-
const api = document.getElementById('apApi').value;
|
|
439
|
-
if (!id || !baseUrl) { showNotification('ID and Base URL are required', true); return; }
|
|
440
|
-
try {
|
|
441
|
-
await postJSON('/api/providers/add', { id, baseUrl, apiKey, api });
|
|
442
|
-
showNotification('Provider added: ' + id);
|
|
443
|
-
document.getElementById('addProviderForm').style.display = 'none';
|
|
444
|
-
loadBuiltinProviders();
|
|
445
|
-
} catch(e) { showNotification('Failed: ' + e.message, true); }
|
|
446
|
-
};
|
|
447
|
-
}
|
|
448
|
-
const refreshBtn = document.getElementById('refreshProvidersBtn');
|
|
449
|
-
if (refreshBtn) refreshBtn.onclick = loadBuiltinProviders;
|
|
450
|
-
}
|
|
Binary file
|
|
@@ -1,185 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* PM Loop controls tab — extracted from app.js
|
|
3
|
-
* Deps: showNotification (core/dom), loadBuildProjectPicker, getBuildProjectById (projects-tab)
|
|
4
|
-
*/
|
|
5
|
-
import { showNotification } from '../core/dom.js';
|
|
6
|
-
import { loadBuildProjectPicker, getBuildProjectById } from './projects-tab.js';
|
|
7
|
-
|
|
8
|
-
// ── PM Loop controls ──────────────────────────────────────────────────────
|
|
9
|
-
let pmPoller = null;
|
|
10
|
-
|
|
11
|
-
function getSelectedProjectId() {
|
|
12
|
-
const sel = document.getElementById('buildProjectPicker');
|
|
13
|
-
return sel ? sel.value : '';
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function checkPmStatus() {
|
|
17
|
-
try {
|
|
18
|
-
const projectId = getSelectedProjectId();
|
|
19
|
-
const qs = projectId ? '?projectId=' + encodeURIComponent(projectId) : '';
|
|
20
|
-
const s = await fetch('/api/pm-loop/status' + qs).then(r => r.json());
|
|
21
|
-
const badge = document.getElementById('pmLoopBadge');
|
|
22
|
-
const startBtn = document.getElementById('pmStartBtn');
|
|
23
|
-
const dryBtn = document.getElementById('pmDryRunBtn');
|
|
24
|
-
const logBox = document.getElementById('pmLiveLog');
|
|
25
|
-
if (s.running) {
|
|
26
|
-
badge.textContent = 'running (pid ' + s.pid + ')';
|
|
27
|
-
badge.classList.add('running');
|
|
28
|
-
startBtn.disabled = true;
|
|
29
|
-
dryBtn.disabled = true;
|
|
30
|
-
logBox.style.display = 'block';
|
|
31
|
-
if (!pmPoller) startPmLogPoller();
|
|
32
|
-
} else {
|
|
33
|
-
if (badge.textContent.startsWith('running')) {
|
|
34
|
-
badge.textContent = 'idle';
|
|
35
|
-
badge.classList.remove('running');
|
|
36
|
-
startBtn.disabled = false;
|
|
37
|
-
dryBtn.disabled = false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
} catch(_) {}
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
function startPmLogPoller() {
|
|
44
|
-
if (pmPoller) return;
|
|
45
|
-
pmPoller = setInterval(async () => {
|
|
46
|
-
try {
|
|
47
|
-
const lg = await fetch('/api/pm-loop/log').then(r2 => r2.json());
|
|
48
|
-
const logBox = document.getElementById('pmLiveLog');
|
|
49
|
-
const badge = document.getElementById('pmLoopBadge');
|
|
50
|
-
const startBtn = document.getElementById('pmStartBtn');
|
|
51
|
-
const dryBtn = document.getElementById('pmDryRunBtn');
|
|
52
|
-
if (lg.lines && lg.lines.length) {
|
|
53
|
-
logBox.textContent = lg.lines.map(l => {
|
|
54
|
-
if (l.event === 'finish') return `🏁 Done ✓${l.done} ✗${l.failed} ⏳${l.pending}`;
|
|
55
|
-
if (l.event === 'stopped_by_file') return '⛔ Stopped by user';
|
|
56
|
-
if (l.event === 'all_done') return `🏁 All ${l.total} items complete!`;
|
|
57
|
-
const icon = l.status === 'done' ? '✅' : l.status === 'failed' ? '❌' : l.event ? '·' : '·';
|
|
58
|
-
const txt = l.item ? `${l.item.substring(0, 60)}` : (l.event || '');
|
|
59
|
-
return `${icon} ${txt}`;
|
|
60
|
-
}).join('\n');
|
|
61
|
-
logBox.scrollTop = logBox.scrollHeight;
|
|
62
|
-
const last = lg.lines[lg.lines.length - 1];
|
|
63
|
-
if (last && (last.event === 'finish' || last.event === 'all_done' || last.event === 'stopped_by_file')) {
|
|
64
|
-
clearInterval(pmPoller); pmPoller = null;
|
|
65
|
-
badge.textContent = last.event === 'all_done' ? '✓ complete' : 'idle';
|
|
66
|
-
badge.classList.remove('running');
|
|
67
|
-
startBtn.disabled = false; dryBtn.disabled = false;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
} catch(_){}
|
|
71
|
-
}, 5000);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
export async function startPmLoop(dryRun = false) {
|
|
75
|
-
const projectId = getSelectedProjectId();
|
|
76
|
-
const badge = document.getElementById('pmLoopBadge');
|
|
77
|
-
const status = document.getElementById('pmStatus');
|
|
78
|
-
const logBox = document.getElementById('pmLiveLog');
|
|
79
|
-
const startBtn = document.getElementById('pmStartBtn');
|
|
80
|
-
const dryBtn = document.getElementById('pmDryRunBtn');
|
|
81
|
-
const proj = getBuildProjectById(projectId);
|
|
82
|
-
if (!projectId) {
|
|
83
|
-
showNotification('Select a project first from the Project picker above', true);
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
try {
|
|
87
|
-
badge.textContent = dryRun ? 'dry run...' : 'starting...';
|
|
88
|
-
badge.classList.add('running');
|
|
89
|
-
startBtn.disabled = true;
|
|
90
|
-
dryBtn.disabled = true;
|
|
91
|
-
logBox.style.display = 'block';
|
|
92
|
-
logBox.textContent = '⚙ Starting PM Loop for ' + (proj ? proj.name : projectId) + (dryRun ? ' (dry run)' : '') + '...\n';
|
|
93
|
-
const resp = await fetch('/api/pm-loop/start', {
|
|
94
|
-
method: 'POST',
|
|
95
|
-
headers: {'content-type':'application/json'},
|
|
96
|
-
body: JSON.stringify({
|
|
97
|
-
dryRun, projectId,
|
|
98
|
-
pmOptions: {
|
|
99
|
-
useQA: document.getElementById('pmOptQA')?.checked ?? true,
|
|
100
|
-
useSecurity: document.getElementById('pmOptSecurity')?.checked ?? true,
|
|
101
|
-
useSpecialists: document.getElementById('pmOptSpecialists')?.checked ?? true,
|
|
102
|
-
selfExtend: document.getElementById('pmOptSelfExtend')?.checked ?? true,
|
|
103
|
-
maxItems: parseInt(document.getElementById('pmOptMaxItems')?.value || '200'),
|
|
104
|
-
taskTimeoutMin: parseInt(document.getElementById('pmOptTimeout')?.value || '10'),
|
|
105
|
-
extendEveryN: parseInt(document.getElementById('pmOptExtendN')?.value || '5'),
|
|
106
|
-
pauseSec: parseInt(document.getElementById('pmOptPause')?.value || '5'),
|
|
107
|
-
maxRetries: parseInt(document.getElementById('pmOptMaxRetries')?.value || '2'),
|
|
108
|
-
coderAgent: document.getElementById('pmOptCoder')?.value.trim() || 'crew-coder',
|
|
109
|
-
}
|
|
110
|
-
})
|
|
111
|
-
});
|
|
112
|
-
const r = await resp.json();
|
|
113
|
-
if (resp.status === 409 || r.alreadyRunning) {
|
|
114
|
-
logBox.textContent = '⚠ Already running (pid ' + r.pid + '). Watch the log below.\n';
|
|
115
|
-
badge.textContent = 'running (pid ' + r.pid + ')';
|
|
116
|
-
showNotification('PM Loop already running for this project (pid ' + r.pid + ')', true);
|
|
117
|
-
startPmLogPoller();
|
|
118
|
-
return;
|
|
119
|
-
}
|
|
120
|
-
logBox.textContent += '✅ Spawned (pid ' + r.pid + '). PM is reading roadmap...\n';
|
|
121
|
-
badge.textContent = 'running (pid ' + r.pid + ')';
|
|
122
|
-
showNotification('PM Loop started' + (dryRun ? ' (dry run)' : '') + ' for ' + (proj ? proj.name : projectId));
|
|
123
|
-
startPmLogPoller();
|
|
124
|
-
} catch (e) {
|
|
125
|
-
showNotification('PM Loop failed: ' + e.message, true);
|
|
126
|
-
badge.textContent = 'idle';
|
|
127
|
-
badge.classList.remove('running');
|
|
128
|
-
startBtn.disabled = false;
|
|
129
|
-
dryBtn.disabled = false;
|
|
130
|
-
}
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
export async function stopPmLoop() {
|
|
134
|
-
const projectId = getSelectedProjectId();
|
|
135
|
-
try {
|
|
136
|
-
await fetch('/api/pm-loop/stop', { method: 'POST', headers: {'content-type':'application/json'}, body: JSON.stringify({ projectId }) });
|
|
137
|
-
showNotification('Stop signal sent — PM will finish current task then halt.');
|
|
138
|
-
document.getElementById('pmLoopBadge').textContent = 'stopping...';
|
|
139
|
-
} catch (e) { showNotification('Stop failed: ' + e.message, true); }
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
export async function toggleRoadmap() {
|
|
143
|
-
const panel = document.getElementById('pmRoadmapPanel');
|
|
144
|
-
if (panel.style.display !== 'none') { panel.style.display = 'none'; return; }
|
|
145
|
-
try {
|
|
146
|
-
// Ensure projects are loaded first
|
|
147
|
-
await loadBuildProjectPicker();
|
|
148
|
-
|
|
149
|
-
const projectId = getSelectedProjectId();
|
|
150
|
-
const proj = getBuildProjectById(projectId);
|
|
151
|
-
console.log('[toggleRoadmap] projectId:', projectId, 'proj:', proj);
|
|
152
|
-
|
|
153
|
-
// If we have a project selected, fetch its roadmap file directly via file API
|
|
154
|
-
let content = '';
|
|
155
|
-
if (proj && proj.roadmapFile) {
|
|
156
|
-
console.log('[toggleRoadmap] Fetching project roadmap:', proj.roadmapFile);
|
|
157
|
-
const r = await fetch('/api/file-content?path=' + encodeURIComponent(proj.roadmapFile)).then(r2 => r2.json());
|
|
158
|
-
content = r.content || '(empty)';
|
|
159
|
-
} else {
|
|
160
|
-
console.log('[toggleRoadmap] Using default roadmap (no project selected or no roadmapFile)');
|
|
161
|
-
const r = await fetch('/api/pm-loop/roadmap').then(r2 => r2.json());
|
|
162
|
-
content = r.content || '(empty)';
|
|
163
|
-
}
|
|
164
|
-
panel.textContent = content;
|
|
165
|
-
panel.style.display = 'block';
|
|
166
|
-
} catch (e) {
|
|
167
|
-
console.error('[toggleRoadmap] Error:', e);
|
|
168
|
-
panel.textContent = 'Could not load roadmap: ' + e.message;
|
|
169
|
-
panel.style.display = 'block';
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
export function initPmLoopTab() {
|
|
174
|
-
document.getElementById('pmStartBtn').onclick = () => startPmLoop(false);
|
|
175
|
-
document.getElementById('pmDryRunBtn').onclick = () => startPmLoop(true);
|
|
176
|
-
document.getElementById('pmStopBtn').onclick = stopPmLoop;
|
|
177
|
-
document.getElementById('pmRoadmapBtn').onclick = toggleRoadmap;
|
|
178
|
-
// Check PM status after picker loads so we use the right projectId
|
|
179
|
-
loadBuildProjectPicker().then(() => checkPmStatus());
|
|
180
|
-
// Re-check status whenever the project picker changes
|
|
181
|
-
document.getElementById('buildProjectPicker').addEventListener('change', () => {
|
|
182
|
-
if (pmPoller) { clearInterval(pmPoller); pmPoller = null; }
|
|
183
|
-
checkPmStatus();
|
|
184
|
-
});
|
|
185
|
-
}
|
|
Binary file
|