waypoi 0.0.0

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 (260) hide show
  1. package/.github/instructions/ui.instructions.md +42 -0
  2. package/.github/workflows/ci.yml +35 -0
  3. package/.github/workflows/publish.yml +71 -0
  4. package/.github/workflows/release.yml +48 -0
  5. package/.playwright-mcp/console-2026-04-04T01-41-10-746Z.log +2 -0
  6. package/.playwright-mcp/console-2026-04-04T01-41-28-799Z.log +3 -0
  7. package/.playwright-mcp/console-2026-04-05T02-26-51-909Z.log +76 -0
  8. package/.playwright-mcp/page-2026-04-04T01-41-10-816Z.yml +1 -0
  9. package/.playwright-mcp/page-2026-04-04T01-41-29-141Z.yml +77 -0
  10. package/.playwright-mcp/page-2026-04-04T01-41-42-633Z.yml +190 -0
  11. package/.playwright-mcp/page-2026-04-04T01-42-03-929Z.yml +262 -0
  12. package/.playwright-mcp/page-2026-04-04T02-12-54-813Z.yml +6 -0
  13. package/.playwright-mcp/page-2026-04-04T02-14-58-600Z.yml +190 -0
  14. package/.playwright-mcp/page-2026-04-04T02-15-03-923Z.yml +190 -0
  15. package/.playwright-mcp/page-2026-04-04T02-15-07-426Z.yml +190 -0
  16. package/.playwright-mcp/page-2026-04-04T02-15-25-729Z.yml +262 -0
  17. package/.playwright-mcp/page-2026-04-04T02-16-22-984Z.yml +262 -0
  18. package/.playwright-mcp/page-2026-04-04T02-17-00-599Z.yml +190 -0
  19. package/.playwright-mcp/page-2026-04-04T02-17-50-874Z.yml +190 -0
  20. package/.playwright-mcp/page-2026-04-05T02-26-55-570Z.yml +6 -0
  21. package/AGENTS.md +48 -0
  22. package/CHANGELOG.md +131 -0
  23. package/README.md +552 -0
  24. package/assets/agent-mode.png +0 -0
  25. package/assets/categorize.png +0 -0
  26. package/assets/dashboard.png +0 -0
  27. package/assets/endpoint-proxy.png +0 -0
  28. package/assets/icon.png +0 -0
  29. package/assets/mcp-generate-image.png +0 -0
  30. package/assets/mcp-understand-image.png +0 -0
  31. package/assets/peek-token-flow.png +0 -0
  32. package/assets/playground.png +0 -0
  33. package/assets/sankey.png +0 -0
  34. package/cli/index.ts +2805 -0
  35. package/cli/legacyRewrite.ts +108 -0
  36. package/cli/modelRef.ts +24 -0
  37. package/dist/cli/index.js +2536 -0
  38. package/dist/cli/legacyRewrite.js +92 -0
  39. package/dist/cli/modelRef.js +20 -0
  40. package/dist/src/benchmark/artifacts.js +131 -0
  41. package/dist/src/benchmark/capabilityClassifier.js +81 -0
  42. package/dist/src/benchmark/capabilityStore.js +144 -0
  43. package/dist/src/benchmark/config.js +238 -0
  44. package/dist/src/benchmark/gates.js +118 -0
  45. package/dist/src/benchmark/jobs.js +252 -0
  46. package/dist/src/benchmark/runner.js +1847 -0
  47. package/dist/src/benchmark/schema.js +353 -0
  48. package/dist/src/benchmark/suites.js +314 -0
  49. package/dist/src/benchmark/tinyQaDataset.js +422 -0
  50. package/dist/src/benchmark/types.js +25 -0
  51. package/dist/src/config.js +47 -0
  52. package/dist/src/index.js +178 -0
  53. package/dist/src/mcp/client.js +215 -0
  54. package/dist/src/mcp/discovery.js +226 -0
  55. package/dist/src/mcp/policy.js +65 -0
  56. package/dist/src/mcp/registry.js +129 -0
  57. package/dist/src/mcp/service.js +460 -0
  58. package/dist/src/middleware/auth.js +179 -0
  59. package/dist/src/middleware/requestCapture.js +192 -0
  60. package/dist/src/middleware/requestStats.js +118 -0
  61. package/dist/src/pools/builder.js +132 -0
  62. package/dist/src/pools/repository.js +69 -0
  63. package/dist/src/pools/scheduler.js +360 -0
  64. package/dist/src/pools/types.js +2 -0
  65. package/dist/src/protocols/adapters/dashscope.js +267 -0
  66. package/dist/src/protocols/adapters/inferenceV2.js +346 -0
  67. package/dist/src/protocols/adapters/openai.js +27 -0
  68. package/dist/src/protocols/registry.js +99 -0
  69. package/dist/src/protocols/types.js +2 -0
  70. package/dist/src/providers/health.js +153 -0
  71. package/dist/src/providers/importer.js +289 -0
  72. package/dist/src/providers/modelRegistry.js +313 -0
  73. package/dist/src/providers/repository.js +361 -0
  74. package/dist/src/providers/types.js +2 -0
  75. package/dist/src/routes/admin.js +531 -0
  76. package/dist/src/routes/audio.js +295 -0
  77. package/dist/src/routes/chat.js +240 -0
  78. package/dist/src/routes/embeddings.js +157 -0
  79. package/dist/src/routes/images.js +288 -0
  80. package/dist/src/routes/mcp.js +256 -0
  81. package/dist/src/routes/mcpService.js +100 -0
  82. package/dist/src/routes/models.js +48 -0
  83. package/dist/src/routes/responses.js +711 -0
  84. package/dist/src/routes/sessions.js +450 -0
  85. package/dist/src/routes/stats.js +270 -0
  86. package/dist/src/routes/ui.js +97 -0
  87. package/dist/src/routes/videos.js +107 -0
  88. package/dist/src/routing/router.js +338 -0
  89. package/dist/src/services/imageGeneration.js +280 -0
  90. package/dist/src/services/imageUnderstanding.js +352 -0
  91. package/dist/src/services/videoGeneration.js +79 -0
  92. package/dist/src/storage/captureRepository.js +1591 -0
  93. package/dist/src/storage/files.js +157 -0
  94. package/dist/src/storage/imageCache.js +346 -0
  95. package/dist/src/storage/repositories.js +388 -0
  96. package/dist/src/storage/sessionRepository.js +370 -0
  97. package/dist/src/storage/statsRepository.js +204 -0
  98. package/dist/src/transport/httpClient.js +126 -0
  99. package/dist/src/types.js +2 -0
  100. package/dist/src/utils/messageMedia.js +285 -0
  101. package/dist/src/utils/modelCapabilities.js +108 -0
  102. package/dist/src/utils/modelDiscovery.js +170 -0
  103. package/dist/src/version.js +5 -0
  104. package/dist/src/workers/captureRetention.js +25 -0
  105. package/dist/src/workers/configWatcher.js +91 -0
  106. package/dist/src/workers/healthChecker.js +21 -0
  107. package/dist/src/workers/statsRotation.js +41 -0
  108. package/docs/LLM/output_schema.md +312 -0
  109. package/docs/benchmark.md +208 -0
  110. package/docs/mcp-guidelines.md +125 -0
  111. package/docs/mcp-service.md +178 -0
  112. package/docs/opencode.md +86 -0
  113. package/docs/providers.md +79 -0
  114. package/examples/benchmark.config.yaml +28 -0
  115. package/examples/providers/alibaba-dashscope.yaml +88 -0
  116. package/examples/providers/alibaba-llm.yaml +64 -0
  117. package/examples/providers/alibaba-registry.yaml +7 -0
  118. package/examples/providers/inference-v2-ray.yaml +29 -0
  119. package/examples/scenarios/assets/omni-call-sample.wav +0 -0
  120. package/examples/scenarios/custom.jsonl +5 -0
  121. package/examples/scenarios/custom.yaml +40 -0
  122. package/model-form-v2.png +0 -0
  123. package/package.json +66 -0
  124. package/provider-form-v2.png +0 -0
  125. package/provider-form.png +0 -0
  126. package/scripts/manual-test.sh +11 -0
  127. package/scripts/version-from-git.js +23 -0
  128. package/src/benchmark/artifacts.ts +149 -0
  129. package/src/benchmark/capabilityClassifier.ts +99 -0
  130. package/src/benchmark/capabilityStore.ts +174 -0
  131. package/src/benchmark/config.ts +337 -0
  132. package/src/benchmark/gates.ts +164 -0
  133. package/src/benchmark/jobs.ts +312 -0
  134. package/src/benchmark/runner.ts +2519 -0
  135. package/src/benchmark/schema.ts +443 -0
  136. package/src/benchmark/suites.ts +323 -0
  137. package/src/benchmark/tinyQaDataset.ts +428 -0
  138. package/src/benchmark/types.ts +442 -0
  139. package/src/config.ts +44 -0
  140. package/src/index.ts +195 -0
  141. package/src/mcp/client.ts +305 -0
  142. package/src/mcp/discovery.ts +266 -0
  143. package/src/mcp/policy.ts +105 -0
  144. package/src/mcp/registry.ts +164 -0
  145. package/src/mcp/service.ts +611 -0
  146. package/src/middleware/auth.ts +251 -0
  147. package/src/middleware/requestCapture.ts +245 -0
  148. package/src/middleware/requestStats.ts +163 -0
  149. package/src/pools/builder.ts +159 -0
  150. package/src/pools/repository.ts +71 -0
  151. package/src/pools/scheduler.ts +425 -0
  152. package/src/pools/types.ts +117 -0
  153. package/src/protocols/adapters/dashscope.ts +335 -0
  154. package/src/protocols/adapters/inferenceV2.ts +428 -0
  155. package/src/protocols/adapters/openai.ts +32 -0
  156. package/src/protocols/registry.ts +117 -0
  157. package/src/protocols/types.ts +81 -0
  158. package/src/providers/health.ts +207 -0
  159. package/src/providers/importer.ts +402 -0
  160. package/src/providers/modelRegistry.ts +415 -0
  161. package/src/providers/repository.ts +439 -0
  162. package/src/providers/types.ts +113 -0
  163. package/src/routes/admin.ts +666 -0
  164. package/src/routes/audio.ts +372 -0
  165. package/src/routes/chat.ts +301 -0
  166. package/src/routes/embeddings.ts +197 -0
  167. package/src/routes/images.ts +356 -0
  168. package/src/routes/mcp.ts +320 -0
  169. package/src/routes/mcpService.ts +114 -0
  170. package/src/routes/models.ts +50 -0
  171. package/src/routes/responses.ts +872 -0
  172. package/src/routes/sessions.ts +558 -0
  173. package/src/routes/stats.ts +312 -0
  174. package/src/routes/ui.ts +96 -0
  175. package/src/routes/videos.ts +132 -0
  176. package/src/routing/router.ts +501 -0
  177. package/src/services/imageGeneration.ts +396 -0
  178. package/src/services/imageUnderstanding.ts +449 -0
  179. package/src/services/videoGeneration.ts +127 -0
  180. package/src/storage/captureRepository.ts +1835 -0
  181. package/src/storage/files.ts +178 -0
  182. package/src/storage/imageCache.ts +405 -0
  183. package/src/storage/repositories.ts +494 -0
  184. package/src/storage/sessionRepository.ts +419 -0
  185. package/src/storage/statsRepository.ts +238 -0
  186. package/src/transport/httpClient.ts +145 -0
  187. package/src/types.ts +322 -0
  188. package/src/utils/messageMedia.ts +293 -0
  189. package/src/utils/modelCapabilities.ts +161 -0
  190. package/src/utils/modelDiscovery.ts +203 -0
  191. package/src/workers/captureRetention.ts +25 -0
  192. package/src/workers/configWatcher.ts +115 -0
  193. package/src/workers/healthChecker.ts +22 -0
  194. package/src/workers/statsRotation.ts +49 -0
  195. package/tests/benchmarkAdminRoutes.test.ts +82 -0
  196. package/tests/benchmarkBasics.test.ts +116 -0
  197. package/tests/captureAdminRoutes.test.ts +420 -0
  198. package/tests/captureRepository.test.ts +797 -0
  199. package/tests/cliLegacyRewrite.test.ts +45 -0
  200. package/tests/imageGeneration.service.test.ts +107 -0
  201. package/tests/imageUnderstanding.service.test.ts +123 -0
  202. package/tests/mcpPolicy.test.ts +105 -0
  203. package/tests/mcpService.test.ts +1245 -0
  204. package/tests/modelRef.test.ts +23 -0
  205. package/tests/modelsRoutes.test.ts +154 -0
  206. package/tests/sessionMediaCache.test.ts +167 -0
  207. package/tests/statsRoutes.test.ts +323 -0
  208. package/tsconfig.json +15 -0
  209. package/ui/index.html +16 -0
  210. package/ui/package-lock.json +8521 -0
  211. package/ui/package.json +52 -0
  212. package/ui/postcss.config.js +6 -0
  213. package/ui/public/assets/apple-touch-icon.png +0 -0
  214. package/ui/public/assets/favicon-16.png +0 -0
  215. package/ui/public/assets/favicon-32.png +0 -0
  216. package/ui/public/assets/icon-192.png +0 -0
  217. package/ui/public/assets/icon-512.png +0 -0
  218. package/ui/src/App.tsx +27 -0
  219. package/ui/src/api/client.ts +1503 -0
  220. package/ui/src/components/EndpointUsageGuide.tsx +361 -0
  221. package/ui/src/components/Layout.tsx +124 -0
  222. package/ui/src/components/MessageContent.tsx +365 -0
  223. package/ui/src/components/ToolCallMessage.tsx +179 -0
  224. package/ui/src/components/ToolPicker.tsx +442 -0
  225. package/ui/src/components/messageContentParser.test.ts +41 -0
  226. package/ui/src/components/messageContentParser.ts +73 -0
  227. package/ui/src/components/thinkingPreview.test.ts +27 -0
  228. package/ui/src/components/thinkingPreview.ts +15 -0
  229. package/ui/src/components/toMermaidSankey.test.ts +78 -0
  230. package/ui/src/components/toMermaidSankey.ts +56 -0
  231. package/ui/src/components/ui/button.tsx +58 -0
  232. package/ui/src/components/ui/input.tsx +21 -0
  233. package/ui/src/components/ui/textarea.tsx +21 -0
  234. package/ui/src/lib/utils.ts +6 -0
  235. package/ui/src/main.tsx +9 -0
  236. package/ui/src/pages/AgentPlayground.tsx +2010 -0
  237. package/ui/src/pages/Benchmark.tsx +988 -0
  238. package/ui/src/pages/Dashboard.tsx +581 -0
  239. package/ui/src/pages/Peek.tsx +962 -0
  240. package/ui/src/pages/Settings.tsx +2013 -0
  241. package/ui/src/pages/agentPlaygroundPayload.test.ts +109 -0
  242. package/ui/src/pages/agentPlaygroundPayload.ts +97 -0
  243. package/ui/src/pages/agentThinkingContent.test.ts +50 -0
  244. package/ui/src/pages/agentThinkingContent.ts +57 -0
  245. package/ui/src/pages/dashboardTokenUsage.test.ts +66 -0
  246. package/ui/src/pages/dashboardTokenUsage.ts +36 -0
  247. package/ui/src/pages/imageUpload.test.ts +39 -0
  248. package/ui/src/pages/imageUpload.ts +71 -0
  249. package/ui/src/pages/peekFilters.test.ts +29 -0
  250. package/ui/src/pages/peekFilters.ts +13 -0
  251. package/ui/src/pages/peekMedia.test.ts +58 -0
  252. package/ui/src/pages/peekMedia.ts +148 -0
  253. package/ui/src/pages/sessionAutoTitle.test.ts +128 -0
  254. package/ui/src/pages/sessionAutoTitle.ts +106 -0
  255. package/ui/src/stores/settings.ts +58 -0
  256. package/ui/src/styles/globals.css +223 -0
  257. package/ui/src/vite-env.d.ts +8 -0
  258. package/ui/tailwind.config.js +106 -0
  259. package/ui/tsconfig.json +32 -0
  260. package/ui/vite.config.ts +37 -0
@@ -0,0 +1,361 @@
1
+ import { useState } from 'react'
2
+ import {
3
+ ChevronDown,
4
+ ChevronUp,
5
+ Copy,
6
+ Check,
7
+ Terminal,
8
+ Code
9
+ } from 'lucide-react'
10
+ import { Button } from '@/components/ui/button'
11
+ import { cn } from '@/lib/utils'
12
+ import type { EndpointType } from '@/api/client'
13
+
14
+ export interface UsageGuideTarget {
15
+ id: string
16
+ type: EndpointType
17
+ models: Array<{ publicName: string }>
18
+ }
19
+
20
+ type TabId = 'curl' | 'python' | 'nodejs'
21
+
22
+ interface EndpointUsageGuideProps {
23
+ target: UsageGuideTarget
24
+ }
25
+
26
+ export function EndpointUsageGuide({ target }: EndpointUsageGuideProps) {
27
+ const [isOpen, setIsOpen] = useState(false)
28
+ const [activeTab, setActiveTab] = useState<TabId>('curl')
29
+ const [copiedId, setCopiedId] = useState<string | null>(null)
30
+
31
+ // Get the base URL from the current window location
32
+ const baseUrl = typeof window !== 'undefined'
33
+ ? `${window.location.protocol}//${window.location.host}`
34
+ : 'http://localhost:9469'
35
+
36
+ // Get the first model's public name (most common use case)
37
+ const modelName = target.models[0]?.publicName ?? 'model-name'
38
+
39
+ const copyToClipboard = async (text: string, id: string) => {
40
+ await navigator.clipboard.writeText(text)
41
+ setCopiedId(id)
42
+ setTimeout(() => setCopiedId(null), 2000)
43
+ }
44
+
45
+ const generateExamples = () => {
46
+ switch (target.type) {
47
+ case 'llm':
48
+ return generateLlmExamples()
49
+ case 'diffusion':
50
+ return generateDiffusionExamples()
51
+ case 'audio':
52
+ return generateAudioExamples()
53
+ case 'embedding':
54
+ return generateEmbeddingExamples()
55
+ default:
56
+ return generateLlmExamples()
57
+ }
58
+ }
59
+
60
+ const generateLlmExamples = () => ({
61
+ curl: `curl ${baseUrl}/v1/chat/completions \\
62
+ -H "Content-Type: application/json" \\
63
+ -d '{
64
+ "model": "${modelName}",
65
+ "messages": [
66
+ {"role": "user", "content": "Hello, how are you?"}
67
+ ],
68
+ "stream": false
69
+ }'`,
70
+ python: `from openai import OpenAI
71
+
72
+ client = OpenAI(
73
+ base_url="${baseUrl}/v1",
74
+ api_key="not-needed" # No API key required for local proxy
75
+ )
76
+
77
+ response = client.chat.completions.create(
78
+ model="${modelName}",
79
+ messages=[
80
+ {"role": "user", "content": "Hello, how are you?"}
81
+ ],
82
+ stream=False
83
+ )
84
+
85
+ print(response.choices[0].message.content)`,
86
+ nodejs: `import OpenAI from 'openai';
87
+
88
+ const client = new OpenAI({
89
+ baseURL: '${baseUrl}/v1',
90
+ apiKey: 'not-needed', // No API key required for local proxy
91
+ });
92
+
93
+ async function main() {
94
+ const response = await client.chat.completions.create({
95
+ model: '${modelName}',
96
+ messages: [
97
+ { role: 'user', content: 'Hello, how are you?' }
98
+ ],
99
+ stream: false,
100
+ });
101
+
102
+ console.log(response.choices[0].message.content);
103
+ }
104
+
105
+ main();`,
106
+ })
107
+
108
+ const generateDiffusionExamples = () => ({
109
+ curl: `curl ${baseUrl}/v1/images/generations \\
110
+ -H "Content-Type: application/json" \\
111
+ -d '{
112
+ "model": "${modelName}",
113
+ "prompt": "A beautiful sunset over mountains",
114
+ "n": 1,
115
+ "size": "1024x1024"
116
+ }'`,
117
+ python: `from openai import OpenAI
118
+
119
+ client = OpenAI(
120
+ base_url="${baseUrl}/v1",
121
+ api_key="not-needed"
122
+ )
123
+
124
+ response = client.images.generate(
125
+ model="${modelName}",
126
+ prompt="A beautiful sunset over mountains",
127
+ n=1,
128
+ size="1024x1024"
129
+ )
130
+
131
+ print(response.data[0].url)`,
132
+ nodejs: `import OpenAI from 'openai';
133
+
134
+ const client = new OpenAI({
135
+ baseURL: '${baseUrl}/v1',
136
+ apiKey: 'not-needed',
137
+ });
138
+
139
+ async function main() {
140
+ const response = await client.images.generate({
141
+ model: '${modelName}',
142
+ prompt: 'A beautiful sunset over mountains',
143
+ n: 1,
144
+ size: '1024x1024',
145
+ });
146
+
147
+ console.log(response.data[0].url);
148
+ }
149
+
150
+ main();`,
151
+ })
152
+
153
+ const generateAudioExamples = () => ({
154
+ curl: `# Text-to-Speech
155
+ curl ${baseUrl}/v1/audio/speech \\
156
+ -H "Content-Type: application/json" \\
157
+ -d '{
158
+ "model": "${modelName}",
159
+ "input": "Hello, this is a test.",
160
+ "voice": "alloy"
161
+ }' \\
162
+ --output speech.mp3
163
+
164
+ # Speech-to-Text
165
+ curl ${baseUrl}/v1/audio/transcriptions \\
166
+ -F "model=${modelName}" \\
167
+ -F "file=@audio.mp3"`,
168
+ python: `from openai import OpenAI
169
+
170
+ client = OpenAI(
171
+ base_url="${baseUrl}/v1",
172
+ api_key="not-needed"
173
+ )
174
+
175
+ # Text-to-Speech
176
+ response = client.audio.speech.create(
177
+ model="${modelName}",
178
+ input="Hello, this is a test.",
179
+ voice="alloy"
180
+ )
181
+ response.stream_to_file("speech.mp3")
182
+
183
+ # Speech-to-Text
184
+ with open("audio.mp3", "rb") as f:
185
+ transcription = client.audio.transcriptions.create(
186
+ model="${modelName}",
187
+ file=f
188
+ )
189
+ print(transcription.text)`,
190
+ nodejs: `import OpenAI from 'openai';
191
+ import fs from 'fs';
192
+
193
+ const client = new OpenAI({
194
+ baseURL: '${baseUrl}/v1',
195
+ apiKey: 'not-needed',
196
+ });
197
+
198
+ async function main() {
199
+ // Text-to-Speech
200
+ const mp3 = await client.audio.speech.create({
201
+ model: '${modelName}',
202
+ input: 'Hello, this is a test.',
203
+ voice: 'alloy',
204
+ });
205
+
206
+ const buffer = Buffer.from(await mp3.arrayBuffer());
207
+ await fs.promises.writeFile('speech.mp3', buffer);
208
+
209
+ // Speech-to-Text
210
+ const transcription = await client.audio.transcriptions.create({
211
+ model: '${modelName}',
212
+ file: fs.createReadStream('audio.mp3'),
213
+ });
214
+
215
+ console.log(transcription.text);
216
+ }
217
+
218
+ main();`,
219
+ })
220
+
221
+ const generateEmbeddingExamples = () => ({
222
+ curl: `curl ${baseUrl}/v1/embeddings \\
223
+ -H "Content-Type: application/json" \\
224
+ -d '{
225
+ "model": "${modelName}",
226
+ "input": "The quick brown fox jumps over the lazy dog"
227
+ }'`,
228
+ python: `from openai import OpenAI
229
+
230
+ client = OpenAI(
231
+ base_url="${baseUrl}/v1",
232
+ api_key="not-needed"
233
+ )
234
+
235
+ response = client.embeddings.create(
236
+ model="${modelName}",
237
+ input="The quick brown fox jumps over the lazy dog"
238
+ )
239
+
240
+ print(f"Embedding dimension: {len(response.data[0].embedding)}")
241
+ print(f"First 5 values: {response.data[0].embedding[:5]}")`,
242
+ nodejs: `import OpenAI from 'openai';
243
+
244
+ const client = new OpenAI({
245
+ baseURL: '${baseUrl}/v1',
246
+ apiKey: 'not-needed',
247
+ });
248
+
249
+ async function main() {
250
+ const response = await client.embeddings.create({
251
+ model: '${modelName}',
252
+ input: 'The quick brown fox jumps over the lazy dog',
253
+ });
254
+
255
+ console.log('Embedding dimension:', response.data[0].embedding.length);
256
+ console.log('First 5 values:', response.data[0].embedding.slice(0, 5));
257
+ }
258
+
259
+ main();`,
260
+ })
261
+
262
+ const examples = generateExamples()
263
+
264
+ const tabs: { id: TabId; label: string; icon: React.ElementType }[] = [
265
+ { id: 'curl', label: 'cURL', icon: Terminal },
266
+ { id: 'python', label: 'Python', icon: Code },
267
+ { id: 'nodejs', label: 'Node.js', icon: Code },
268
+ ]
269
+
270
+ return (
271
+ <div className="border-t border-border/50">
272
+ <button
273
+ onClick={() => setIsOpen(!isOpen)}
274
+ className="w-full px-4 py-2 flex items-center gap-2 text-xs text-muted-foreground hover:text-foreground hover:bg-secondary/50 transition-colors"
275
+ >
276
+ <Code className="w-3.5 h-3.5" />
277
+ <span>Usage Guide</span>
278
+ <span className="flex-1" />
279
+ {isOpen ? (
280
+ <ChevronUp className="w-3.5 h-3.5" />
281
+ ) : (
282
+ <ChevronDown className="w-3.5 h-3.5" />
283
+ )}
284
+ </button>
285
+
286
+ {isOpen && (
287
+ <div className="px-4 pb-4 space-y-3 animate-fade-in">
288
+ {/* Models List */}
289
+ <div className="space-y-1">
290
+ <p className="text-2xs font-mono uppercase text-muted-foreground">Available Models</p>
291
+ <div className="flex flex-wrap gap-1">
292
+ {target.models.map((model, i) => (
293
+ <span
294
+ key={i}
295
+ className="px-2 py-0.5 bg-secondary rounded text-xs font-mono"
296
+ >
297
+ {model.publicName}
298
+ </span>
299
+ ))}
300
+ </div>
301
+ </div>
302
+
303
+ {/* Tabs */}
304
+ <div className="flex items-center gap-1 bg-secondary/50 rounded-md p-1">
305
+ {tabs.map((tab) => (
306
+ <button
307
+ key={tab.id}
308
+ onClick={() => setActiveTab(tab.id)}
309
+ className={cn(
310
+ 'flex items-center gap-1.5 px-3 py-1 text-xs font-mono rounded transition-colors',
311
+ activeTab === tab.id
312
+ ? 'bg-background text-foreground shadow-sm'
313
+ : 'text-muted-foreground hover:text-foreground'
314
+ )}
315
+ >
316
+ <tab.icon className="w-3 h-3" />
317
+ {tab.label}
318
+ </button>
319
+ ))}
320
+ </div>
321
+
322
+ {/* Code Block */}
323
+ <div className="relative group">
324
+ <pre className="bg-zinc-950 text-zinc-100 rounded-lg p-4 text-xs font-mono overflow-x-auto leading-relaxed">
325
+ <code>{examples[activeTab]}</code>
326
+ </pre>
327
+ <Button
328
+ variant="ghost"
329
+ size="icon"
330
+ className="absolute top-2 right-2 h-7 w-7 opacity-0 group-hover:opacity-100 transition-opacity bg-zinc-800 hover:bg-zinc-700"
331
+ onClick={() => copyToClipboard(examples[activeTab], `${target.id}-${activeTab}`)}
332
+ >
333
+ {copiedId === `${target.id}-${activeTab}` ? (
334
+ <Check className="w-3.5 h-3.5 text-green-400" />
335
+ ) : (
336
+ <Copy className="w-3.5 h-3.5 text-zinc-400" />
337
+ )}
338
+ </Button>
339
+ </div>
340
+
341
+ {/* Endpoint Info */}
342
+ <div className="text-2xs text-muted-foreground space-y-0.5">
343
+ <p>
344
+ <span className="font-medium">Base URL:</span>{' '}
345
+ <code className="bg-secondary px-1 py-0.5 rounded">{baseUrl}/v1</code>
346
+ </p>
347
+ <p>
348
+ <span className="font-medium">Endpoint Type:</span>{' '}
349
+ <span className="uppercase">{target.type}</span>
350
+ </p>
351
+ {target.type === 'llm' && (
352
+ <p className="text-muted-foreground/70">
353
+ Supports streaming via <code className="bg-secondary px-1 py-0.5 rounded">stream: true</code>
354
+ </p>
355
+ )}
356
+ </div>
357
+ </div>
358
+ )}
359
+ </div>
360
+ )
361
+ }
@@ -0,0 +1,124 @@
1
+ import { Outlet, NavLink } from 'react-router-dom'
2
+ import {
3
+ MessageSquare,
4
+ LayoutDashboard,
5
+ Settings as SettingsIcon,
6
+ Radio,
7
+ Gauge,
8
+ Search
9
+ } from 'lucide-react'
10
+ import { cn } from '@/lib/utils'
11
+ import { useEffect, useState } from 'react'
12
+ import { getAdminMeta, listProviders } from '@/api/client'
13
+
14
+ const navItems = [
15
+ { to: '/playground', icon: MessageSquare, label: 'Playground' },
16
+ { to: '/benchmark', icon: Gauge, label: 'Benchmark' },
17
+ { to: '/dashboard', icon: LayoutDashboard, label: 'Dashboard' },
18
+ { to: '/peek', icon: Search, label: 'Peek' },
19
+ { to: '/settings', icon: SettingsIcon, label: 'Settings' },
20
+ ]
21
+
22
+ export function Layout() {
23
+ const [healthStatus, setHealthStatus] = useState<'live' | 'degraded' | 'down'>('down')
24
+ const [version, setVersion] = useState<string>('0.0.0')
25
+
26
+ useEffect(() => {
27
+ async function checkHealth() {
28
+ try {
29
+ const [providers, meta] = await Promise.all([
30
+ listProviders(),
31
+ getAdminMeta(),
32
+ ])
33
+ setVersion(meta.version)
34
+
35
+ if (providers.length === 0) {
36
+ setHealthStatus('down')
37
+ return
38
+ }
39
+
40
+ const enabledProviders = providers.filter((provider) => provider.enabled)
41
+ const enabledModels = providers.reduce(
42
+ (sum, provider) => sum + provider.models.filter((model) => model.enabled !== false).length,
43
+ 0
44
+ )
45
+
46
+ if (enabledProviders.length === providers.length && enabledModels > 0) {
47
+ setHealthStatus('live')
48
+ } else if (enabledModels > 0) {
49
+ setHealthStatus('degraded')
50
+ } else {
51
+ setHealthStatus('down')
52
+ }
53
+ } catch {
54
+ setHealthStatus('down')
55
+ }
56
+ }
57
+
58
+ checkHealth()
59
+ const interval = setInterval(checkHealth, 30000)
60
+ return () => clearInterval(interval)
61
+ }, [])
62
+
63
+ return (
64
+ <div className="h-screen overflow-hidden bg-background flex">
65
+ {/* Sidebar */}
66
+ <aside className="w-56 border-r border-border flex flex-col noise-overlay">
67
+ {/* Logo */}
68
+ <div className="h-14 border-b border-border flex items-center px-4 gap-3">
69
+ <div className="w-8 h-8 rounded overflow-hidden flex items-center justify-center">
70
+ <img src="/ui/assets/favicon-32.png" alt="Waypoi" className="w-8 h-8" />
71
+ </div>
72
+ <div>
73
+ <h1 className="font-mono font-semibold text-sm tracking-tight">WAYPOINT</h1>
74
+ <p className="text-2xs text-muted-foreground font-mono">v{version}</p>
75
+ </div>
76
+ </div>
77
+
78
+ {/* Navigation */}
79
+ <nav className="flex-1 py-4 px-2 space-y-1">
80
+ {navItems.map(({ to, icon: Icon, label }) => (
81
+ <NavLink
82
+ key={to}
83
+ to={to}
84
+ className={({ isActive }) =>
85
+ cn(
86
+ 'flex items-center gap-3 px-3 py-2 rounded-md text-sm font-medium transition-all duration-150',
87
+ isActive
88
+ ? 'nav-active'
89
+ : 'text-muted-foreground hover:text-foreground hover:bg-secondary'
90
+ )
91
+ }
92
+ >
93
+ <Icon className="w-4 h-4" />
94
+ <span>{label}</span>
95
+ </NavLink>
96
+ ))}
97
+ </nav>
98
+
99
+ {/* Status Footer */}
100
+ <div className="p-4 border-t border-border">
101
+ <div className="flex items-center gap-2 text-xs text-muted-foreground">
102
+ <Radio className="w-3 h-3" />
103
+ <span className="font-mono uppercase tracking-wider">Status</span>
104
+ <div className="flex-1" />
105
+ <div className="flex items-center gap-1.5">
106
+ <div className={cn(
107
+ 'status-dot',
108
+ healthStatus === 'live' && 'status-dot-live',
109
+ healthStatus === 'degraded' && 'status-dot-degraded',
110
+ healthStatus === 'down' && 'status-dot-down',
111
+ )} />
112
+ <span className="font-mono capitalize">{healthStatus}</span>
113
+ </div>
114
+ </div>
115
+ </div>
116
+ </aside>
117
+
118
+ {/* Main Content */}
119
+ <main className="flex-1 flex flex-col overflow-hidden">
120
+ <Outlet />
121
+ </main>
122
+ </div>
123
+ )
124
+ }