adaptive-memory-multi-model-router 2.11.0 → 2.12.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 (104) hide show
  1. package/.github/workflows/ci.yml +56 -0
  2. package/.github/workflows/pages.yml +37 -0
  3. package/Awesome-LLM/LICENSE.md +121 -0
  4. package/Awesome-LLM/README.md +625 -0
  5. package/Awesome-LLM/contributing.md +24 -0
  6. package/Awesome-LLM/paper_list/RLHF.md +1 -0
  7. package/Awesome-LLM/paper_list/Retrieval_Augmented_Generation.md +6 -0
  8. package/Awesome-LLM/paper_list/acceleration.md +10 -0
  9. package/Awesome-LLM/paper_list/alignment.md +20 -0
  10. package/Awesome-LLM/paper_list/application.md +19 -0
  11. package/Awesome-LLM/paper_list/augmentation.md +14 -0
  12. package/Awesome-LLM/paper_list/chain_of_thougt.md +16 -0
  13. package/Awesome-LLM/paper_list/code_pretraining.md +0 -0
  14. package/Awesome-LLM/paper_list/detection.md +11 -0
  15. package/Awesome-LLM/paper_list/evaluation.md +64 -0
  16. package/Awesome-LLM/paper_list/in_context_learning.md +4 -0
  17. package/Awesome-LLM/paper_list/instruction-tuning.md +35 -0
  18. package/Awesome-LLM/paper_list/moe.md +0 -0
  19. package/Awesome-LLM/paper_list/prompt_learning.md +15 -0
  20. package/Awesome-LLM/resources/DeepSpeed_light.svg +27 -0
  21. package/Awesome-LLM/resources/alpa-logo-cropped.png +0 -0
  22. package/Awesome-LLM/resources/colossal-ai_logo_vertical.png +0 -0
  23. package/Awesome-LLM/resources/creepy_llm.jpeg +0 -0
  24. package/Awesome-LLM/resources/image8.gif +0 -0
  25. package/Awesome-LLM/resources/jax_logo_250px.png +0 -0
  26. package/Awesome-LLM/resources/logo.png +0 -0
  27. package/Awesome-LLM/resources/nvidia.png +0 -0
  28. package/Awesome-LLM/resources/tensorflow.png +0 -0
  29. package/Awesome-LLM/resources//345/244/247/345/236/213/350/257/255/350/250/200/346/250/241/345/236/213/347/232/204/350/203/275/345/212/233/345/210/206/346/236/220/344/270/216/345/272/224/347/224/250 - 30min.pdf +0 -0
  30. package/LANDING.md +46 -0
  31. package/MANIFESTO.md +54 -0
  32. package/README.md +3 -3
  33. package/SUBMISSIONS.md +43 -0
  34. package/assets/chart-cost-v2.svg +91 -0
  35. package/assets/chart-cost-v3.svg +143 -0
  36. package/assets/chart-features-v2.svg +132 -0
  37. package/assets/chart-features-v3.svg +211 -0
  38. package/assets/chart-growth-v2.svg +122 -0
  39. package/assets/chart-growth-v3.svg +189 -0
  40. package/assets/cost-simple.svg +64 -0
  41. package/assets/growth-simple.svg +69 -0
  42. package/assets/hero-diagram.svg +81 -0
  43. package/assets/logo-new.svg +21 -0
  44. package/assets/social-preview-new.svg +100 -0
  45. package/assets/social-v2.svg +130 -0
  46. package/assets/social-v3.svg +212 -0
  47. package/awesome-ai-gateways/CONTRIBUTING.md +49 -0
  48. package/awesome-ai-gateways/LICENSE +3 -0
  49. package/awesome-ai-gateways/README.md +190 -0
  50. package/awesome-selfhosted/.github/ISSUE_TEMPLATE/config.yml +5 -0
  51. package/awesome-selfhosted/.github/PULL_REQUEST_TEMPLATE.md +1 -0
  52. package/awesome-selfhosted/LICENSE +376 -0
  53. package/awesome-selfhosted/README.md +2303 -0
  54. package/awesome-selfhosted/_static/awesome.png +0 -0
  55. package/awesome-selfhosted/non-free.md +377 -0
  56. package/demo-new.tape +71 -0
  57. package/demo-real.sh +198 -0
  58. package/demo-simple.tape +205 -0
  59. package/demo.html +520 -0
  60. package/demo.sh +85 -0
  61. package/demo.tape +259 -0
  62. package/dist/cli/setupWizard.js +194 -0
  63. package/dist/cli/tui.js +152 -0
  64. package/dist/cli.js +9 -2
  65. package/dist/routing/providerRetry.d.ts +5 -0
  66. package/dist/routing/providerRetry.js +37 -0
  67. package/dist/routing/providerRetry.js.map +1 -1
  68. package/docs/CHINESE_PROVIDER_RELIABILITY.md +37 -0
  69. package/docs/CLAIMS_AND_EVIDENCE.md +58 -0
  70. package/docs/ENGINEERING_SPEC.md +55 -0
  71. package/docs/RELEASE_CHECKLIST.md +32 -0
  72. package/docs/REPRODUCIBILITY.md +63 -0
  73. package/docs/demo-auto.html +264 -0
  74. package/docs/demo.html +416 -0
  75. package/docs/index.html +112 -599
  76. package/eval/README.md +46 -0
  77. package/eval/baselines/main.json +12 -0
  78. package/eval/benchmark_dataset.jsonl +16 -0
  79. package/eval/check_golden_routes.js +64 -0
  80. package/eval/datasets/catalog.json +33 -0
  81. package/eval/datasets/slices/cn_provider_reliability_v1.jsonl +3 -0
  82. package/eval/datasets/slices/cost_pressure_v1.jsonl +3 -0
  83. package/eval/datasets/slices/safety_guardrails_v1.jsonl +3 -0
  84. package/eval/fault_injection_thresholds.json +3 -0
  85. package/eval/generate_report.js +128 -0
  86. package/eval/golden_routes.json +114 -0
  87. package/eval/lib/experiment_registry.js +24 -0
  88. package/eval/run_eval.js +197 -0
  89. package/eval/run_fault_injection.js +201 -0
  90. package/eval/run_shadow_eval.js +85 -0
  91. package/eval/thresholds.json +9 -0
  92. package/index.html +667 -0
  93. package/package.json +29 -168
  94. package/pytest.ini +2 -0
  95. package/src/cli/setupWizard.ts +194 -0
  96. package/src/routing/providerRetry.ts +41 -1
  97. package/docs/assets/cost-comparison.svg +0 -134
  98. package/docs/assets/growth-chart-animated.svg +0 -76
  99. package/docs/assets/og-banner.svg +0 -194
  100. package/docs/assets/social-preview.svg +0 -194
  101. package/python/a3m/__pycache__/__init__.cpython-312.pyc +0 -0
  102. package/python/a3m/__pycache__/client.cpython-312.pyc +0 -0
  103. package/python/a3m/__pycache__/models.cpython-312.pyc +0 -0
  104. package/python/a3m/__pycache__/sync_client.cpython-312.pyc +0 -0
@@ -0,0 +1,194 @@
1
+ /**
2
+ * A3M Router Setup Wizard
3
+ * Interactive configuration wizard
4
+ */
5
+
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const readline = require('readline');
9
+
10
+ const CONFIG_DIR = path.join(process.env.HOME || '/tmp', '.config', 'a3m-router');
11
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'providers.json');
12
+
13
+ // API key environment variable mappings
14
+ const API_KEY_ENV_MAP = {
15
+ 'GROQ_API_KEY': 'groq',
16
+ 'OPENAI_API_KEY': 'openai',
17
+ 'ANTHROPIC_API_KEY': 'anthropic',
18
+ 'DEEPSEEK_API_KEY': 'deepseek',
19
+ 'MISTRAL_API_KEY': 'mistral',
20
+ 'GOOGLE_API_KEY': 'google',
21
+ 'CEREBRAS_API_KEY': 'cerebras',
22
+ 'TOGETHER_API_KEY': 'together',
23
+ 'AI21_API_KEY': 'ai21',
24
+ 'COHERE_API_KEY': 'cohere',
25
+ 'MINIMAX_API_KEY': 'minimax',
26
+ 'KIMI_API_KEY': 'kimi',
27
+ 'MOONSHOT_API_KEY': 'moonshot',
28
+ 'QWEN_API_KEY': 'qwen',
29
+ 'ZHIPU_API_KEY': 'zhipu',
30
+ 'YI_API_KEY': 'yi',
31
+ 'BAICHUAN_API_KEY': 'baichuan',
32
+ };
33
+
34
+ // Provider metadata
35
+ const PROVIDER_INFO = {
36
+ groq: { name: 'Groq', models: 'llama-3.3-70b-versatile', tier: 'free', strength: 'Fast, free tier' },
37
+ openai: { name: 'OpenAI', models: 'gpt-4o-mini', tier: 'paid', strength: 'GPT-4, most capable' },
38
+ anthropic: { name: 'Anthropic', models: 'claude-3.5-haiku', tier: 'paid', strength: 'Claude, best reasoning' },
39
+ deepseek: { name: 'DeepSeek', models: 'deepseek-chat-v3', tier: 'cheap', strength: 'Cheap, good code' },
40
+ mistral: { name: 'Mistral', models: 'mistral-small-latest', tier: 'cheap', strength: 'European, balanced' },
41
+ google: { name: 'Google AI', models: 'gemini-1.5-flash', tier: 'free', strength: 'Gemini, multimodal' },
42
+ cerebras: { name: 'Cerebras', models: 'llama-3.3-70b', tier: 'free', strength: 'Fastest inference' },
43
+ together: { name: 'Together AI', models: 'Llama-3.3-70B-Instruct', tier: 'cheap', strength: 'Managed, reliable' },
44
+ ai21: { name: 'AI21', models: 'jamba-1.5-medium', tier: 'paid', strength: 'Jamba, long context' },
45
+ cohere: { name: 'Cohere', models: 'command-r7b', tier: 'cheap', strength: 'Command series, fast' },
46
+ minimax: { name: 'MiniMax', models: 'abab6.5s-chat', tier: 'cheap', strength: 'Chinese, cheap' },
47
+ kimi: { name: 'Kimi/Moonshot', models: 'moonshot-v1-8k', tier: 'cheap', strength: 'Chinese, 128k context' },
48
+ moonshot: { name: 'Moonshot', models: 'moonshot-v1-8k', tier: 'cheap', strength: 'Chinese, good' },
49
+ qwen: { name: 'Qwen', models: 'qwen-turbo', tier: 'cheap', strength: 'Alibaba, multilingual' },
50
+ zhipu: { name: 'Zhipu GLM', models: 'glm-4', tier: 'cheap', strength: 'Chinese, smart' },
51
+ yi: { name: 'Yi', models: 'yi-large', tier: 'cheap', strength: 'Chinese, good reasoning' },
52
+ baichuan: { name: 'Baichuan', models: 'baichuan-4', tier: 'cheap', strength: 'Chinese, balanced' },
53
+ };
54
+
55
+ function createInterface() {
56
+ return readline.createInterface({
57
+ input: process.stdin,
58
+ output: process.stdout
59
+ });
60
+ }
61
+
62
+ function question(rl, text) {
63
+ return new Promise((resolve) => {
64
+ rl.question(text, (answer) => resolve(answer));
65
+ });
66
+ }
67
+
68
+ async function detectApiKeys() {
69
+ const detected = [];
70
+ for (const [envVar, providerId] of Object.entries(API_KEY_ENV_MAP)) {
71
+ if (process.env[envVar]) {
72
+ detected.push({ envVar, providerId, info: PROVIDER_INFO[providerId] });
73
+ }
74
+ }
75
+ return detected;
76
+ }
77
+
78
+ async function runWizard() {
79
+ console.log('\n🔧 A3M Router Setup Wizard');
80
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━\n');
81
+
82
+ const rl = createInterface();
83
+
84
+ // Ensure config directory exists
85
+ if (!fs.existsSync(CONFIG_DIR)) {
86
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
87
+ }
88
+
89
+ // Check for existing config
90
+ let existingConfig = {};
91
+ if (fs.existsSync(CONFIG_FILE)) {
92
+ try {
93
+ existingConfig = JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
94
+ console.log('✓ Found existing config at', CONFIG_FILE);
95
+ console.log(' Providers:', Object.keys(existingConfig.providers || {}).join(', '));
96
+ console.log('');
97
+ } catch (e) {
98
+ console.log('⚠ Could not read existing config, starting fresh\n');
99
+ }
100
+ }
101
+
102
+ // Auto-detect API keys
103
+ console.log('🔍 Scanning for API keys in environment...');
104
+ const detected = await detectApiKeys();
105
+
106
+ if (detected.length === 0) {
107
+ console.log('⚠ No API keys detected in environment.');
108
+ console.log(' Set any of: GROQ_API_KEY, OPENAI_API_KEY, DEEPSEEK_API_KEY, etc.\n');
109
+ } else {
110
+ console.log('✓ Found', detected.length, 'API key(s):');
111
+ detected.forEach(({ envVar, providerId, info }) => {
112
+ console.log(' ✓', envVar, '→', info?.name || providerId);
113
+ });
114
+ console.log('');
115
+ }
116
+
117
+ // Provider selection
118
+ const allProviders = Object.keys(PROVIDER_INFO);
119
+ const selected = new Set();
120
+
121
+ // Pre-select providers with detected keys
122
+ detected.forEach(({ providerId }) => selected.add(providerId));
123
+
124
+ console.log('📡 Select providers to configure (comma-separated numbers, or "all"):');
125
+ console.log('');
126
+
127
+ const numbered = allProviders.map((id, i) => ({ id, i }));
128
+ numbered.forEach(({ id, i }) => {
129
+ const info = PROVIDER_INFO[id];
130
+ const selected_mark = selected.has(id) ? '[x]' : '[ ]';
131
+ const tier_mark = info?.tier === 'free' ? '(FREE)' : info?.tier === 'cheap' ? '(cheap)' : '(paid)';
132
+ console.log(` ${String(i + 1).padStart(2)}. ${selected_mark} ${id.padEnd(12)} ${tier_mark} - ${info?.strength || ''}`);
133
+ });
134
+
135
+ console.log('');
136
+ const answer = await question(rl, ' Enter numbers or "all" [all with keys detected]: ');
137
+
138
+ if (answer.toLowerCase().trim() === 'all') {
139
+ allProviders.forEach(id => selected.add(id));
140
+ } else if (answer.trim()) {
141
+ const nums = answer.split(',').map(s => parseInt(s.trim())).filter(n => !isNaN(n));
142
+ nums.forEach(n => {
143
+ const idx = n - 1;
144
+ if (idx >= 0 && idx < allProviders.length) {
145
+ selected.add(allProviders[idx]);
146
+ }
147
+ });
148
+ }
149
+
150
+ console.log('\n✓ Selected providers:', Array.from(selected).join(', '));
151
+
152
+ // Build config
153
+ const config = {
154
+ version: '1.0',
155
+ providers: {}
156
+ };
157
+
158
+ selected.forEach(providerId => {
159
+ const info = PROVIDER_INFO[providerId];
160
+ const envKey = Object.entries(API_KEY_ENV_MAP).find(([k, v]) => v === providerId)?.[0];
161
+
162
+ config.providers[providerId] = {
163
+ name: info?.name || providerId,
164
+ apiKey: envKey ? process.env[envKey] : '',
165
+ models: [info?.models || 'default'],
166
+ type: 'api',
167
+ enabled: true
168
+ };
169
+ });
170
+
171
+ // Save config
172
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
173
+ console.log('\n✓ Config saved to', CONFIG_FILE);
174
+
175
+ // Test connections
176
+ console.log('\n🧪 Testing connections...');
177
+ console.log(' (Skipped in wizard mode - run "npx a3m-router test" to verify)\n');
178
+
179
+ // Ready message
180
+ console.log('━━━━━━━━━━━━━━━━━━━━━━━━━');
181
+ console.log('✅ A3M Router is ready!');
182
+ console.log('');
183
+ console.log(' Next steps:');
184
+ console.log(' 1. npx a3m-router serve # Start proxy server');
185
+ console.log(' 2. npx a3m-router test # Test provider connections');
186
+ console.log(' 3. npx a3m-router route "hi" # Try routing a query');
187
+ console.log('');
188
+ console.log(' Docs: https://github.com/Das-rebel/adaptive-memory-multi-model-router');
189
+ console.log('');
190
+
191
+ rl.close();
192
+ }
193
+
194
+ module.exports = { runWizard };
@@ -357,6 +357,11 @@ export class ProviderRetryHandler {
357
357
  isRetryableError(error: any): boolean {
358
358
  if (!error) return false;
359
359
 
360
+ // Known non-retryable account/quota/policy states seen in Chinese provider APIs.
361
+ if (this.isPermanentProviderStateError(error)) {
362
+ return false;
363
+ }
364
+
360
365
  const config = this.configs.get('default')!.retry;
361
366
  const retryableErrors = config.retryableErrors || DEFAULT_RETRY_CONFIG.retryableErrors!;
362
367
 
@@ -382,6 +387,41 @@ export class ProviderRetryHandler {
382
387
  return false;
383
388
  }
384
389
 
390
+ /**
391
+ * Detect hard non-retryable account/policy states.
392
+ * These should fail fast instead of wasting retries.
393
+ */
394
+ private isPermanentProviderStateError(error: any): boolean {
395
+ const status = Number(error?.status ?? error?.statusCode ?? 0);
396
+ const message = String(error?.message || '').toLowerCase();
397
+ const code = String(error?.code || '').toLowerCase();
398
+
399
+ // Payment/balance/account states are generally non-retryable without human action.
400
+ if (status === 402) return true;
401
+ if (status === 401 || status === 403) return true;
402
+
403
+ const hardPatterns = [
404
+ 'insufficient',
405
+ 'balance',
406
+ 'invalid api key',
407
+ 'incorrect api key',
408
+ 'access terminated',
409
+ 'account abnormal',
410
+ '违规',
411
+ 'quota exhausted',
412
+ 'hour allocated quota exceeded',
413
+ 'week allocated quota exceeded',
414
+ 'month allocated quota exceeded',
415
+ 'tpd rate limit'
416
+ ];
417
+
418
+ for (const p of hardPatterns) {
419
+ if (message.includes(p) || code.includes(p)) return true;
420
+ }
421
+
422
+ return false;
423
+ }
424
+
385
425
  /**
386
426
  * Check if error is a rate limit (429)
387
427
  */
@@ -575,4 +615,4 @@ export function getDefaultRetryHandler(): ProviderRetryHandler {
575
615
  export {
576
616
  DEFAULT_RETRY_CONFIG,
577
617
  PROVIDER_CONTEXT_LIMITS,
578
- };
618
+ };
@@ -1,134 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="780" height="520" viewBox="0 0 780 520">
2
- <defs>
3
- <linearGradient id="gpt4bar" x1="0" y1="0" x2="1" y2="0">
4
- <stop offset="0%" stop-color="#f85149"/>
5
- <stop offset="100%" stop-color="#da3633"/>
6
- </linearGradient>
7
- <linearGradient id="a3mbar" x1="0" y1="0" x2="1" y2="0">
8
- <stop offset="0%" stop-color="#3fb950"/>
9
- <stop offset="100%" stop-color="#238636"/>
10
- </linearGradient>
11
- <linearGradient id="header" x1="0" y1="0" x2="0" y2="1">
12
- <stop offset="0%" stop-color="#161b22"/>
13
- <stop offset="100%" stop-color="#0d1117"/>
14
- </linearGradient>
15
- </defs>
16
-
17
- <!-- Background -->
18
- <rect width="780" height="520" rx="12" fill="#0d1117" stroke="#30363d" stroke-width="1"/>
19
-
20
- <!-- Header -->
21
- <rect width="780" height="56" rx="12" fill="url(#header)"/>
22
- <rect y="44" width="780" height="12" fill="url(#header)"/>
23
- <text x="24" y="28" font-family="-apple-system,system-ui,sans-serif" font-size="18" fill="#c9d1d9" font-weight="700">💰 Cost Comparison</text>
24
- <text x="24" y="46" font-family="-apple-system,system-ui,sans-serif" font-size="12" fill="#8b949e">10,000 queries/month · Real provider pricing · Mixed query complexity</text>
25
-
26
- <!-- Legend -->
27
- <rect x="580" y="16" width="14" height="14" rx="3" fill="url(#gpt4bar)"/>
28
- <text x="600" y="27" font-family="-apple-system,system-ui,sans-serif" font-size="12" fill="#f85149">All → GPT-4o</text>
29
- <rect x="680" y="16" width="14" height="14" rx="3" fill="url(#a3mbar)"/>
30
- <text x="700" y="27" font-family="-apple-system,system-ui,sans-serif" font-size="12" fill="#3fb950">A3M Routed</text>
31
-
32
- <!-- Chart rows -->
33
- <g font-family="'SF Mono','Fira Code',monospace" font-size="12">
34
-
35
- <!-- Row 1: Simple Q&A -->
36
- <text x="24" y="82" fill="#8b949e">Simple Q&A</text>
37
- <text x="24" y="96" fill="#484f58" font-size="10">47% of traffic · "What is 2+2?"</text>
38
- <rect x="140" y="78" width="200" height="20" rx="4" fill="url(#gpt4bar)" opacity="0.9">
39
- <animate attributeName="width" from="0" to="200" dur="0.6s" fill="freeze"/>
40
- </rect>
41
- <text x="346" y="93" fill="#f85149" font-size="11">$4.94</text>
42
- <rect x="140" y="100" width="2" height="14" rx="2" fill="url(#a3mbar)">
43
- <animate attributeName="width" from="0" to="2" dur="0.4s" fill="freeze"/>
44
- </rect>
45
- <text x="148" y="112" fill="#3fb950" font-size="11">$0.00 → CommandCode (FREE)</text>
46
- <text x="720" y="96" fill="#3fb950" font-weight="bold" font-size="13">100% ✓</text>
47
-
48
- <!-- Row 2: Code generation -->
49
- <text x="24" y="148" fill="#8b949e">Code generation</text>
50
- <text x="24" y="162" fill="#484f58" font-size="10">15% of traffic · "Write Python sort"</text>
51
- <rect x="140" y="144" width="198" height="20" rx="4" fill="url(#gpt4bar)" opacity="0.9">
52
- <animate attributeName="width" from="0" to="198" dur="0.6s" begin="0.15s" fill="freeze"/>
53
- </rect>
54
- <text x="344" y="159" fill="#f85149" font-size="11">$4.88</text>
55
- <rect x="140" y="166" width="7" height="14" rx="2" fill="url(#a3mbar)">
56
- <animate attributeName="width" from="0" to="7" dur="0.4s" begin="0.15s" fill="freeze"/>
57
- </rect>
58
- <text x="153" y="178" fill="#3fb950" font-size="11">$0.17 → DeepSeek v3</text>
59
- <text x="720" y="162" fill="#3fb950" font-weight="bold" font-size="13">97% ✓</text>
60
-
61
- <!-- Row 3: Summarization -->
62
- <text x="24" y="214" fill="#8b949e">Summarization</text>
63
- <text x="24" y="228" fill="#484f58" font-size="10">18% of traffic · "Summarize this doc"</text>
64
- <rect x="140" y="210" width="292" height="20" rx="4" fill="url(#gpt4bar)" opacity="0.9">
65
- <animate attributeName="width" from="0" to="292" dur="0.6s" begin="0.3s" fill="freeze"/>
66
- </rect>
67
- <text x="438" y="225" fill="#f85149" font-size="11">$7.20</text>
68
- <rect x="140" y="232" width="17" height="14" rx="2" fill="url(#a3mbar)">
69
- <animate attributeName="width" from="0" to="17" dur="0.4s" begin="0.3s" fill="freeze"/>
70
- </rect>
71
- <text x="163" y="244" fill="#3fb950" font-size="11">$0.43 → GPT-4o-mini</text>
72
- <text x="720" y="228" fill="#3fb950" font-weight="bold" font-size="13">94% ✓</text>
73
-
74
- <!-- Row 4: Complex reasoning -->
75
- <text x="24" y="280" fill="#8b949e">Complex reasoning</text>
76
- <text x="24" y="294" fill="#484f58" font-size="10">12% of traffic · "Analyze economics of..."</text>
77
- <rect x="140" y="276" width="352" height="20" rx="4" fill="url(#gpt4bar)" opacity="0.9">
78
- <animate attributeName="width" from="0" to="352" dur="0.6s" begin="0.45s" fill="freeze"/>
79
- </rect>
80
- <text x="498" y="291" fill="#f85149" font-size="11">$8.70</text>
81
- <rect x="140" y="298" width="136" height="14" rx="2" fill="url(#a3mbar)">
82
- <animate attributeName="width" from="0" to="136" dur="0.4s" begin="0.45s" fill="freeze"/>
83
- </rect>
84
- <text x="282" y="310" fill="#3fb950" font-size="11">$3.36 → Claude Haiku</text>
85
- <text x="720" y="294" fill="#ffa657" font-weight="bold" font-size="13">61% ✓</text>
86
-
87
- <!-- Row 5: Expert analysis -->
88
- <text x="24" y="346" fill="#8b949e">Expert analysis</text>
89
- <text x="24" y="360" fill="#484f58" font-size="10">8% of traffic · "Legal contract review"</text>
90
- <rect x="140" y="342" width="340" height="20" rx="4" fill="url(#gpt4bar)" opacity="0.9">
91
- <animate attributeName="width" from="0" to="340" dur="0.6s" begin="0.6s" fill="freeze"/>
92
- </rect>
93
- <text x="486" y="357" fill="#f85149" font-size="11">$8.40</text>
94
- <rect x="140" y="364" width="340" height="14" rx="2" fill="url(#a3mbar)" opacity="0.5">
95
- <animate attributeName="width" from="0" to="340" dur="0.4s" begin="0.6s" fill="freeze"/>
96
- </rect>
97
- <text x="486" y="376" fill="#8b949e" font-size="11">$8.40 → GPT-4o (no shortcut)</text>
98
- <text x="720" y="360" fill="#8b949e" font-weight="bold" font-size="13">0%</text>
99
- </g>
100
-
101
- <!-- Divider -->
102
- <line x1="24" y1="396" x2="756" y2="396" stroke="#30363d" stroke-width="1"/>
103
-
104
- <!-- Total -->
105
- <g font-family="-apple-system,system-ui,sans-serif">
106
- <text x="24" y="418" fill="#c9d1d9" font-size="16" font-weight="700">Total: 10,000 queries</text>
107
- <text x="380" y="418" fill="#f85149" font-size="16" font-weight="700">$34.11/mo</text>
108
- <text x="470" y="418" fill="#484f58" font-size="16">→</text>
109
- <text x="498" y="418" fill="#3fb950" font-size="16" font-weight="700">$12.36/mo</text>
110
- <text x="600" y="418" fill="#3fb950" font-size="20" font-weight="800">Save 64%</text>
111
- </g>
112
-
113
- <!-- Scale projections -->
114
- <g font-family="'SF Mono','Fira Code',monospace" font-size="11">
115
- <text x="24" y="448" fill="#8b949e">At scale:</text>
116
- <text x="24" y="468" fill="#484f58"> 100K queries/mo</text>
117
- <text x="200" y="468" fill="#f85149">$341</text>
118
- <text x="240" y="468" fill="#484f58">→</text>
119
- <text x="260" y="468" fill="#3fb950">$124</text>
120
- <text x="310" y="468" fill="#3fb950">save $218/mo</text>
121
-
122
- <text x="420" y="468" fill="#484f58"> 1M queries/mo</text>
123
- <text x="580" y="468" fill="#f85149">$3,411</text>
124
- <text x="630" y="468" fill="#484f58">→</text>
125
- <text x="650" y="468" fill="#3fb950">$1,236</text>
126
- <text x="700" y="468" fill="#3fb950">save $2,175/mo</text>
127
- </g>
128
-
129
- <!-- Bottom note -->
130
- <rect x="16" y="488" width="748" height="24" rx="4" fill="#161b22"/>
131
- <text x="390" y="504" text-anchor="middle" font-family="-apple-system,system-ui,sans-serif" font-size="11" fill="#8b949e">
132
- Based on real provider pricing from providerConfig.ts · RouteLLM query distribution (47% simple) · Savings vary by usage
133
- </text>
134
- </svg>
@@ -1,76 +0,0 @@
1
- <svg xmlns="http://www.w3.org/2000/svg" width="600" height="280" viewBox="0 0 600 280">
2
- <style>
3
- .label { font-family: 'SF Mono', monospace; font-size: 12px; fill: #8b949e; }
4
- .value { font-family: 'SF Mono', monospace; font-size: 14px; font-weight: bold; }
5
- .title { font-family: -apple-system, system-ui, sans-serif; font-size: 16px; fill: #c9d1d9; font-weight: 600; }
6
- .subtitle { font-family: -apple-system, system-ui, sans-serif; font-size: 12px; fill: #8b949e; }
7
- </style>
8
-
9
- <!-- Background -->
10
- <rect width="600" height="280" rx="12" fill="#0d1117" stroke="#30363d" stroke-width="1"/>
11
-
12
- <!-- Title -->
13
- <text x="300" y="30" text-anchor="middle" class="title">📈 Downloads Over 3 Days</text>
14
- <text x="300" y="48" text-anchor="middle" class="subtitle">Zero marketing budget. Pure npm keyword discovery + word-of-mouth.</text>
15
-
16
- <!-- Chart area -->
17
- <g transform="translate(80, 70)">
18
- <!-- Grid lines -->
19
- <line x1="0" y1="0" x2="440" y2="0" stroke="#21262d" stroke-width="1"/>
20
- <line x1="0" y1="40" x2="440" y2="40" stroke="#21262d" stroke-width="1" stroke-dasharray="4"/>
21
- <line x1="0" y1="80" x2="440" y2="80" stroke="#21262d" stroke-width="1" stroke-dasharray="4"/>
22
- <line x1="0" y1="120" x2="440" y2="120" stroke="#21262d" stroke-width="1" stroke-dasharray="4"/>
23
- <line x1="0" y1="160" x2="440" y2="160" stroke="#21262d" stroke-width="1"/>
24
-
25
- <!-- Y-axis labels -->
26
- <text x="-10" y="4" text-anchor="end" class="label">2000</text>
27
- <text x="-10" y="44" text-anchor="end" class="label">1500</text>
28
- <text x="-10" y="84" text-anchor="end" class="label">1000</text>
29
- <text x="-10" y="124" text-anchor="end" class="label">500</text>
30
- <text x="-10" y="164" text-anchor="end" class="label">0</text>
31
-
32
- <!-- Area fill -->
33
- <polygon points="73,116 220,134 367,8 367,160 73,160" fill="#238636" fill-opacity="0.15">
34
- <animate attributeName="fill-opacity" from="0" to="0.15" dur="1s" fill="freeze"/>
35
- </polygon>
36
-
37
- <!-- Line -->
38
- <polyline points="73,116 220,134 367,8" fill="none" stroke="#3fb950" stroke-width="3" stroke-linecap="round" stroke-linejoin="round">
39
- <animate attributeName="stroke-dashoffset" from="400" to="0" dur="1.5s" fill="freeze"/>
40
- <set attributeName="stroke-dasharray" to="400"/>
41
- </polyline>
42
-
43
- <!-- Data points -->
44
- <circle cx="73" cy="116" r="6" fill="#0d1117" stroke="#3fb950" stroke-width="3">
45
- <animate attributeName="r" from="0" to="6" dur="0.3s" begin="0.5s" fill="freeze"/>
46
- </circle>
47
- <circle cx="220" cy="134" r="6" fill="#0d1117" stroke="#f0883e" stroke-width="3">
48
- <animate attributeName="r" from="0" to="6" dur="0.3s" begin="1s" fill="freeze"/>
49
- </circle>
50
- <circle cx="367" cy="8" r="6" fill="#0d1117" stroke="#3fb950" stroke-width="3">
51
- <animate attributeName="r" from="0" to="6" dur="0.3s" begin="1.5s" fill="freeze"/>
52
- </circle>
53
-
54
- <!-- Value labels -->
55
- <text x="73" y="106" text-anchor="middle" class="value" fill="#c9d1d9">552</text>
56
- <text x="220" y="152" text-anchor="middle" class="value" fill="#f0883e">320</text>
57
- <text x="367" y="-4" text-anchor="middle" class="value" fill="#3fb950">1,903</text>
58
-
59
- <!-- X-axis labels -->
60
- <text x="73" y="180" text-anchor="middle" class="label">Day 1</text>
61
- <text x="73" y="194" text-anchor="middle" class="subtitle">May 15</text>
62
- <text x="220" y="180" text-anchor="middle" class="label">Day 2</text>
63
- <text x="220" y="194" text-anchor="middle" class="subtitle">May 16</text>
64
- <text x="367" y="180" text-anchor="middle" class="label">Day 3</text>
65
- <text x="367" y="194" text-anchor="middle" class="subtitle">May 17</text>
66
-
67
- <!-- Annotations -->
68
- <text x="147" y="105" text-anchor="middle" class="subtitle" fill="#f0883e">-42%</text>
69
- <text x="293" y="60" text-anchor="middle" class="subtitle" fill="#3fb950">+495%</text>
70
- </g>
71
-
72
- <!-- Bottom stats -->
73
- <text x="150" y="270" text-anchor="middle" class="value" fill="#3fb950">2,775 total</text>
74
- <text x="300" y="270" text-anchor="middle" class="value" fill="#79c0ff">245% growth</text>
75
- <text x="450" y="270" text-anchor="middle" class="value" fill="#d2a8ff">$0 budget</text>
76
- </svg>