@inferencesh/ui 0.0.0-stage → 0.1.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 (165) hide show
  1. package/LICENSE +22 -0
  2. package/README.md +68 -2
  3. package/components.json +22 -0
  4. package/package.json +231 -4
  5. package/src/components/audio-player.tsx +549 -0
  6. package/src/components/button.tsx +13 -0
  7. package/src/components/chart.tsx +403 -0
  8. package/src/components/circuit.tsx +276 -0
  9. package/src/components/code-block/code-block.tsx +260 -0
  10. package/src/components/code-block/index.ts +18 -0
  11. package/src/components/code-block/languages.ts +311 -0
  12. package/src/components/code-block/styles.ts +39 -0
  13. package/src/components/code-block/tokenizer.ts +290 -0
  14. package/src/components/code-block/types.ts +38 -0
  15. package/src/components/code-block/utils.ts +39 -0
  16. package/src/components/code.tsx +48 -0
  17. package/src/components/cookie-consent.tsx +137 -0
  18. package/src/components/copy-button.tsx +44 -0
  19. package/src/components/country-dropdown.tsx +188 -0
  20. package/src/components/glow-loader-fast.tsx +32 -0
  21. package/src/components/glow-loader-mono.tsx +33 -0
  22. package/src/components/glow-loader-sm-fast.tsx +32 -0
  23. package/src/components/glow-loader-sm-mono.tsx +34 -0
  24. package/src/components/glow-loader-sm.tsx +32 -0
  25. package/src/components/glow-loader.tsx +32 -0
  26. package/src/components/glow-logo.tsx +32 -0
  27. package/src/components/gradient.tsx +80 -0
  28. package/src/components/index.ts +45 -0
  29. package/src/components/inline-sidebar.tsx +351 -0
  30. package/src/components/input-color.tsx +349 -0
  31. package/src/components/logo-base.tsx +213 -0
  32. package/src/components/logo-color.tsx +23 -0
  33. package/src/components/logo-full.tsx +71 -0
  34. package/src/components/logo-svg.tsx +23 -0
  35. package/src/components/menu-navigation.tsx +359 -0
  36. package/src/components/multi-select.tsx +721 -0
  37. package/src/components/pastel-gradient.tsx +25 -0
  38. package/src/components/pixel-symmetry.tsx +151 -0
  39. package/src/components/progress.tsx +28 -0
  40. package/src/components/scroll-more.tsx +131 -0
  41. package/src/components/select.tsx +85 -0
  42. package/src/components/shimmering-text.tsx +108 -0
  43. package/src/components/sidebar-light.tsx +102 -0
  44. package/src/components/sonner.tsx +38 -0
  45. package/src/components/theme-switcher-sidebar.tsx +56 -0
  46. package/src/components/theme-switcher-simple.tsx +110 -0
  47. package/src/components/theme-switcher.tsx +47 -0
  48. package/src/components/toggle-group.tsx +55 -0
  49. package/src/components/tooltip-button.tsx +91 -0
  50. package/src/components/tooltip.tsx +71 -0
  51. package/src/components/tree-view.tsx +499 -0
  52. package/src/components/unglow-loader.tsx +33 -0
  53. package/src/components/waveform.tsx +1632 -0
  54. package/src/components/zoomable-image.tsx +76 -0
  55. package/src/docs/api-docs.tsx +763 -0
  56. package/src/docs/callout.tsx +160 -0
  57. package/src/docs/code-group.tsx +215 -0
  58. package/src/docs/copy-menu.tsx +143 -0
  59. package/src/docs/doc-tabs.tsx +84 -0
  60. package/src/docs/index.ts +16 -0
  61. package/src/docs/steps.tsx +105 -0
  62. package/src/docs/table-of-contents.tsx +174 -0
  63. package/src/examples/guide-markdown.ts +129 -0
  64. package/src/examples/index.ts +1153 -0
  65. package/src/global.d.ts +1 -0
  66. package/src/hooks/use-copy-to-clipboard.ts +36 -0
  67. package/src/hooks/use-mobile.ts +19 -0
  68. package/src/lib/display-name.ts +15 -0
  69. package/src/lib/example-input.ts +130 -0
  70. package/src/lib/plans.ts +44 -0
  71. package/src/lib/safe-content.ts +137 -0
  72. package/src/lib/schema.ts +320 -0
  73. package/src/lib/storage.ts +108 -0
  74. package/src/markdown/docs-markdown-renderer.tsx +518 -0
  75. package/src/markdown/helpers.ts +95 -0
  76. package/src/markdown/index.ts +24 -0
  77. package/src/markdown/markdown-renderer.tsx +232 -0
  78. package/src/markdown/youtube-embed.tsx +43 -0
  79. package/src/og/image.ts +312 -0
  80. package/src/og/index.tsx +430 -0
  81. package/src/og/query.ts +26 -0
  82. package/src/shadcn/accordion.tsx +71 -0
  83. package/src/shadcn/alert-dialog.tsx +187 -0
  84. package/src/shadcn/alert.tsx +75 -0
  85. package/src/shadcn/aspect-ratio.tsx +22 -0
  86. package/src/shadcn/attachment.tsx +207 -0
  87. package/src/shadcn/avatar.tsx +106 -0
  88. package/src/shadcn/badge.tsx +51 -0
  89. package/src/shadcn/breadcrumb.tsx +124 -0
  90. package/src/shadcn/bubble.tsx +127 -0
  91. package/src/shadcn/button-group.tsx +87 -0
  92. package/src/shadcn/button.tsx +57 -0
  93. package/src/shadcn/calendar.tsx +219 -0
  94. package/src/shadcn/card.tsx +102 -0
  95. package/src/shadcn/carousel.tsx +242 -0
  96. package/src/shadcn/chart.tsx +372 -0
  97. package/src/shadcn/checkbox.tsx +28 -0
  98. package/src/shadcn/collapsible.tsx +19 -0
  99. package/src/shadcn/combobox.tsx +297 -0
  100. package/src/shadcn/command.tsx +194 -0
  101. package/src/shadcn/context-menu.tsx +270 -0
  102. package/src/shadcn/dialog.tsx +158 -0
  103. package/src/shadcn/direction.tsx +4 -0
  104. package/src/shadcn/drawer.tsx +227 -0
  105. package/src/shadcn/dropdown-menu.tsx +265 -0
  106. package/src/shadcn/empty.tsx +103 -0
  107. package/src/shadcn/field.tsx +238 -0
  108. package/src/shadcn/form.tsx +173 -0
  109. package/src/shadcn/hover-card.tsx +50 -0
  110. package/src/shadcn/index.ts +53 -0
  111. package/src/shadcn/input-group.tsx +156 -0
  112. package/src/shadcn/input-otp.tsx +84 -0
  113. package/src/shadcn/input.tsx +19 -0
  114. package/src/shadcn/item.tsx +201 -0
  115. package/src/shadcn/kbd.tsx +26 -0
  116. package/src/shadcn/label.tsx +19 -0
  117. package/src/shadcn/marker.tsx +70 -0
  118. package/src/shadcn/menubar.tsx +278 -0
  119. package/src/shadcn/message-scroller.tsx +131 -0
  120. package/src/shadcn/message.tsx +91 -0
  121. package/src/shadcn/native-select.tsx +60 -0
  122. package/src/shadcn/navigation-menu.tsx +167 -0
  123. package/src/shadcn/pagination.tsx +130 -0
  124. package/src/shadcn/popover.tsx +87 -0
  125. package/src/shadcn/progress.tsx +82 -0
  126. package/src/shadcn/radio-group.tsx +35 -0
  127. package/src/shadcn/resizable.tsx +49 -0
  128. package/src/shadcn/scroll-area.tsx +52 -0
  129. package/src/shadcn/select.tsx +200 -0
  130. package/src/shadcn/separator.tsx +22 -0
  131. package/src/shadcn/sheet.tsx +136 -0
  132. package/src/shadcn/sidebar.tsx +721 -0
  133. package/src/shadcn/skeleton.tsx +13 -0
  134. package/src/shadcn/slider.tsx +51 -0
  135. package/src/shadcn/sonner.tsx +49 -0
  136. package/src/shadcn/spinner.tsx +10 -0
  137. package/src/shadcn/switch.tsx +29 -0
  138. package/src/shadcn/table.tsx +115 -0
  139. package/src/shadcn/tabs.tsx +79 -0
  140. package/src/shadcn/textarea.tsx +17 -0
  141. package/src/shadcn/toggle-group.tsx +89 -0
  142. package/src/shadcn/toggle.tsx +44 -0
  143. package/src/shadcn/tooltip.tsx +63 -0
  144. package/src/theme/config.ts +48 -0
  145. package/src/theme/css/base.css +92 -0
  146. package/src/theme/css/bitboy.css +75 -0
  147. package/src/theme/css/dark.css +47 -0
  148. package/src/theme/css/designer.css +122 -0
  149. package/src/theme/css/fonts.css +33 -0
  150. package/src/theme/css/globals.css +14 -0
  151. package/src/theme/css/hackintosh.css +57 -0
  152. package/src/theme/css/llama.css +103 -0
  153. package/src/theme/css/safelist.css +39 -0
  154. package/src/theme/css/shadcn.css +77 -0
  155. package/src/theme/css/theme.css +216 -0
  156. package/src/theme/embed-theme.ts +146 -0
  157. package/src/theme/fonts/FiraCode-VF.woff2 +0 -0
  158. package/src/theme/fonts/Hack-Regular.woff2 +0 -0
  159. package/src/theme/fonts/LICENSE-FiraCode.txt +93 -0
  160. package/src/theme/fonts/LICENSE-Hack.txt +45 -0
  161. package/src/theme/fonts/LICENSE-MonaSans.txt +93 -0
  162. package/src/theme/fonts/Mona-Sans.woff2 +0 -0
  163. package/src/theme/index.ts +15 -0
  164. package/src/utils/cn.ts +3 -0
  165. package/src/utils/index.ts +2 -0
@@ -0,0 +1,763 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect, useMemo, useState } from 'react'
4
+ import { CodeBlock } from "../components/code-block"
5
+ import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "../components/select"
6
+ import { Separator } from "../shadcn/separator"
7
+ import { TableOfContents, TocItem } from "./table-of-contents"
8
+ import { TaskStatusCancelled, TaskStatusCompleted, TaskStatusFailed, TaskStatusRejected } from '@inferencesh/sdk'
9
+ import { apiKeySetup, generateBackgroundExamples, generateFileExamples, generateResultOnlyExamples, generateStreamingExamples, generateSwiftWebhookExample, installCommands } from '../examples'
10
+ import { exampleInput } from '../lib/example-input'
11
+ import { getFieldsFromSchema, SchemaField, Schema } from '../lib/schema'
12
+ import { cn } from 'cn'
13
+ import { localStore } from '../lib/storage'
14
+ import { Hash } from 'lucide-react'
15
+
16
+ // Types
17
+ export interface ApiDocsApp {
18
+ /** The slug that addresses the app. Stays in refs, URLs and code samples. */
19
+ name: string
20
+ /** The name a person reads, when the app has one. Headings and prose only. */
21
+ title?: string
22
+ namespace?: string
23
+ description?: string
24
+ }
25
+
26
+ export interface ApiDocsVersion {
27
+ short_id?: string
28
+ input_schema?: Schema
29
+ output_schema?: Schema
30
+ setup_schema?: Schema
31
+ functions?: Record<string, { name?: string; description?: string; input_schema?: Schema; output_schema?: Schema }>
32
+ default_function?: string
33
+ }
34
+
35
+ export interface ApiDocsFormData {
36
+ input?: Record<string, unknown>
37
+ setup?: Record<string, unknown>
38
+ infra?: string
39
+ workers?: string[]
40
+ }
41
+
42
+ export interface ApiDocsProps {
43
+ /** App metadata */
44
+ app: ApiDocsApp
45
+ /** Version containing schemas and functions */
46
+ version: ApiDocsVersion
47
+ /** Active function name (falls back to version.default_function → "run") */
48
+ functionName?: string | null
49
+ /** Current form data for generating examples */
50
+ formData?: ApiDocsFormData
51
+ /** Optional className */
52
+ className?: string
53
+ /** Link component for routing (defaults to <a>) */
54
+ LinkComponent?: React.ComponentType<{ to: string; className?: string; children: React.ReactNode }>
55
+ /** Base path for docs links (defaults to /docs) */
56
+ docsBasePath?: string
57
+ /** Base path for settings links (defaults to /settings) */
58
+ settingsBasePath?: string
59
+ /** Controlled language (if provided, component is controlled) */
60
+ language?: Language
61
+ /** Callback when language changes */
62
+ onLanguageChange?: (language: Language) => void
63
+ }
64
+
65
+ export type Language = 'python' | 'typescript' | 'javascript' | 'swift' | 'curl' | 'belt'
66
+
67
+ /** The language a page shows first: plain HTTP works from anywhere. */
68
+ export const DEFAULT_LANGUAGE: Language = 'curl'
69
+
70
+ const LANGUAGE_KEY = 'api-docs-language'
71
+
72
+ /**
73
+ * The page language, remembered per viewer across app pages. Starts at
74
+ * DEFAULT_LANGUAGE so server and first client render agree, then switches
75
+ * to the saved choice.
76
+ */
77
+ export function useApiDocsLanguage(): [Language, (lang: Language) => void] {
78
+ const [language, setLanguageState] = useState<Language>(DEFAULT_LANGUAGE)
79
+ useEffect(() => {
80
+ const saved = localStore.getItem(LANGUAGE_KEY)
81
+ if (saved && LANGUAGES.some(l => l.value === saved)) setLanguageState(saved as Language)
82
+ }, [])
83
+ const setLanguage = useCallback((lang: Language) => {
84
+ setLanguageState(lang)
85
+ localStore.setItem(LANGUAGE_KEY, lang)
86
+ }, [])
87
+ return [language, setLanguage]
88
+ }
89
+
90
+ const LANGUAGES: { value: Language; label: string }[] = [
91
+ { value: 'curl', label: 'rest (curl)' },
92
+ { value: 'python', label: 'python' },
93
+ { value: 'typescript', label: 'typescript' },
94
+ { value: 'javascript', label: 'javascript' },
95
+ { value: 'swift', label: 'swift' },
96
+ { value: 'belt', label: 'belt cli' },
97
+ ]
98
+
99
+ // Table of Contents structure (lowercase style)
100
+ function buildTocItems(hasSetupSchema: boolean): TocItem[] {
101
+ const schemaChildren: TocItem[] = [
102
+ { id: 'schema-input', title: 'input' },
103
+ { id: 'schema-output', title: 'output' },
104
+ ]
105
+ if (hasSetupSchema) {
106
+ schemaChildren.push({ id: 'schema-setup', title: 'setup' })
107
+ }
108
+ return [
109
+ { id: 'api-key', title: 'get an api key' },
110
+ {
111
+ id: 'api-call',
112
+ title: '1. calling the api',
113
+ children: [
114
+ { id: 'api-call-install', title: 'install the client' },
115
+ { id: 'api-call-result', title: 'run and get result' },
116
+ { id: 'api-call-stream', title: 'stream live updates' },
117
+ { id: 'api-call-background', title: 'run in the background' },
118
+ ],
119
+ },
120
+ {
121
+ id: 'auth',
122
+ title: '2. authentication',
123
+ },
124
+ {
125
+ id: 'files',
126
+ title: '3. files',
127
+ children: [
128
+ { id: 'files-auto-upload', title: 'automatic upload' },
129
+ { id: 'files-manual', title: 'manual upload' },
130
+ ],
131
+ },
132
+ {
133
+ id: 'webhooks',
134
+ title: '4. webhooks',
135
+ },
136
+ {
137
+ id: 'schema',
138
+ title: '5. schema',
139
+ children: schemaChildren,
140
+ },
141
+ ]
142
+ }
143
+
144
+ // The webhook body: the task result in an event envelope (go/api projector deliverWebhook).
145
+ const WEBHOOK_FIELDS = [
146
+ { key: 'event', type: 'string', desc: 'task.completed, task.failed or task.cancelled' },
147
+ { key: 'timestamp', type: 'string', desc: 'iso timestamp of the delivery' },
148
+ { key: 'data.id', type: 'string', desc: 'task id' },
149
+ { key: 'data.short_id', type: 'string', desc: 'short task id' },
150
+ { key: 'data.status', type: 'number', desc: `terminal status (${TaskStatusCompleted}=completed, ${TaskStatusFailed}=failed, ${TaskStatusCancelled}=cancelled, ${TaskStatusRejected}=rejected)` },
151
+ { key: 'data.status_text', type: 'string', desc: 'the status as a word: completed, failed or cancelled' },
152
+ { key: 'data.output', type: 'object', desc: 'task output (when completed)' },
153
+ { key: 'data.error', type: 'string', desc: 'error message (when failed, omitted otherwise)' },
154
+ { key: 'data.session_id', type: 'string', desc: 'session id (when the task ran in a session, omitted otherwise)' },
155
+ { key: 'data.created_at', type: 'string', desc: 'iso timestamp' },
156
+ { key: 'data.updated_at', type: 'string', desc: 'iso timestamp' },
157
+ ]
158
+
159
+ // Default link component (plain anchor)
160
+ function DefaultLink({ to, className, children }: { to: string; className?: string; children: React.ReactNode }) {
161
+ return <a href={to} className={className}>{children}</a>
162
+ }
163
+
164
+ // Helper Components
165
+ function SectionHeading({
166
+ id,
167
+ children,
168
+ level = 2
169
+ }: {
170
+ id: string
171
+ children: React.ReactNode
172
+ level?: 2 | 3
173
+ }) {
174
+ const Tag = level === 2 ? 'h2' : 'h3'
175
+ const textSize = level === 2 ? 'text-lg' : 'text-base'
176
+
177
+ return (
178
+ <Tag
179
+ id={id}
180
+ className={cn("flex flex-row items-center gap-2 pt-2 font-medium text-foreground", textSize)}
181
+ >
182
+ <span>{children}</span>
183
+ <a
184
+ href={`#${id}`}
185
+ className="font-mono font-extralight text-muted-foreground/50 transition-colors hover:text-muted-foreground"
186
+ >
187
+ <Hash className="h-3.5 w-3.5" />
188
+ </a>
189
+ </Tag>
190
+ )
191
+ }
192
+
193
+ function SchemaProperty({
194
+ field,
195
+ isOutput = false,
196
+ }: {
197
+ field: SchemaField
198
+ isOutput?: boolean
199
+ }) {
200
+ const resolved = field.resolved || field
201
+ const type = field.type || resolved.type || 'any'
202
+ const description = field.description || resolved.description || field.title
203
+ const defaultValue = field.default ?? resolved.default
204
+ const example = Array.isArray(field.examples) ? field.examples[0] : field.examples
205
+ const resolvedExample = Array.isArray(resolved.examples) ? resolved.examples[0] : resolved.examples
206
+ const exampleValue = example || resolvedExample
207
+ const enumValues = resolved.enum || field.enum
208
+ const minimum = resolved.minimum ?? field.minimum
209
+ const maximum = resolved.maximum ?? field.maximum
210
+ const minLength = resolved.minLength ?? field.minLength
211
+ const maxLength = resolved.maxLength ?? field.maxLength
212
+ const pattern = resolved.pattern || field.pattern
213
+ const format = resolved.format || field.format
214
+
215
+ return (
216
+ <div className="border border-border rounded-lg p-3 space-y-2">
217
+ <div className="flex items-center flex-wrap">
218
+ <span className="text-sm font-semibold">
219
+ {field.key}
220
+ </span>
221
+ <span className="text-xs font-mono text-muted-foreground ml-1">
222
+ {type}
223
+ {format && <span className="text-muted-foreground ml-1">({format})</span>}
224
+ </span>
225
+ {field.isRequired && <span className="text-brand-pink ml-1 text-xs">*</span>}
226
+ </div>
227
+
228
+ {description && (
229
+ <p className="text-xs text-muted-foreground">{description.toLowerCase()}</p>
230
+ )}
231
+
232
+ {!isOutput && (
233
+ <>
234
+ <div className="flex flex-wrap gap-2 text-xs">
235
+ {defaultValue !== undefined && (
236
+ <span className="text-muted-foreground">
237
+ default: <span className="text-xs tracking-wider bg-muted px-1 rounded text-foreground">{JSON.stringify(defaultValue)}</span>
238
+ </span>
239
+ )}
240
+ {exampleValue !== undefined && (
241
+ <span className="text-muted-foreground">
242
+ example: <span className="text-xs tracking-wider bg-muted px-1 rounded text-foreground">{JSON.stringify(exampleValue)}</span>
243
+ </span>
244
+ )}
245
+ {minimum !== undefined && (
246
+ <span className="text-muted-foreground">min:
247
+ <span className="text-xs tracking-wider bg-muted px-1 rounded text-foreground">{minimum}</span>
248
+ </span>
249
+ )}
250
+ {maximum !== undefined && (
251
+ <span className="text-muted-foreground">max:
252
+ <span className="text-xs tracking-wider bg-muted px-1 rounded text-foreground">{maximum}</span>
253
+ </span>
254
+ )}
255
+ {minLength !== undefined && (
256
+ <span className="text-muted-foreground">minLength:
257
+ <span className="text-xs tracking-wider bg-muted px-1 rounded text-foreground">{minLength}</span>
258
+ </span>
259
+ )}
260
+ {maxLength !== undefined && (
261
+ <span className="text-muted-foreground">maxLength:
262
+ <span className="text-xs tracking-wider bg-muted px-1 rounded text-foreground">{maxLength}</span>
263
+ </span>
264
+ )}
265
+ </div>
266
+
267
+ {enumValues && enumValues.length > 0 && (
268
+ <div className="flex flex-wrap gap-1">
269
+ <span className="text-xs text-muted-foreground">options:</span>
270
+ {enumValues.map((v, i) => (
271
+ <span key={i} className="text-xs bg-muted px-1.5 py-0.5 rounded tracking-wider">
272
+ {JSON.stringify(v)}
273
+ </span>
274
+ ))}
275
+ </div>
276
+ )}
277
+
278
+ {pattern && (
279
+ <div className="text-xs text-muted-foreground">
280
+ pattern: <span className="bg-muted px-1 rounded">{pattern}</span>
281
+ </div>
282
+ )}
283
+ </>
284
+ )}
285
+ </div>
286
+ )
287
+ }
288
+
289
+ // Language selector for ToC header
290
+ function LanguageSelector({
291
+ language,
292
+ onLanguageChange
293
+ }: {
294
+ language: Language
295
+ onLanguageChange: (lang: Language) => void
296
+ }) {
297
+ return (
298
+ <div className="mb-4">
299
+ <Select value={language} onValueChange={(v) => onLanguageChange(v as Language)}>
300
+ <SelectTrigger className="w-full">
301
+ <SelectValue />
302
+ </SelectTrigger>
303
+ <SelectContent>
304
+ {LANGUAGES.map((lang) => (
305
+ <SelectItem key={lang.value} value={lang.value}>
306
+ {lang.label}
307
+ </SelectItem>
308
+ ))}
309
+ </SelectContent>
310
+ </Select>
311
+ </div>
312
+ )
313
+ }
314
+
315
+ // Main Component
316
+ export function ApiDocs({
317
+ app,
318
+ version,
319
+ functionName,
320
+ formData,
321
+ className,
322
+ LinkComponent = DefaultLink,
323
+ docsBasePath = '/docs',
324
+ settingsBasePath = '/settings',
325
+ language: controlledLanguage,
326
+ onLanguageChange,
327
+ }: ApiDocsProps) {
328
+ const [internalLanguage, setInternalLanguage] = useState<Language>(DEFAULT_LANGUAGE)
329
+ const language = controlledLanguage ?? internalLanguage
330
+ const setLanguage = (lang: Language) => {
331
+ if (onLanguageChange) onLanguageChange(lang)
332
+ else setInternalLanguage(lang)
333
+ }
334
+ const Link = LinkComponent
335
+
336
+ // Resolve effective schemas from version + function
337
+ const effectiveFn = functionName || version?.default_function || 'run'
338
+
339
+ // Build app reference — append :function when non-default and multiple functions exist
340
+ // Version is only appended when short_id is provided (caller controls via pinVersion toggle)
341
+ const namespace = app.namespace || ''
342
+ const shortId = version?.short_id
343
+ const defaultFn = version?.default_function || 'run'
344
+ const hasManyFunctions = version?.functions && Object.keys(version.functions).length > 1
345
+ const fnSuffix = (hasManyFunctions && effectiveFn !== defaultFn) ? `:${effectiveFn}` : ''
346
+ const versionSuffix = shortId ? `@${shortId}` : ''
347
+ const appRef = `${namespace}/${app.name}${versionSuffix}${fnSuffix}`
348
+ const func = version?.functions?.[effectiveFn]
349
+ const schema = func?.input_schema || version?.input_schema || {}
350
+ const outputSchema = func?.output_schema || version?.output_schema
351
+ const setupSchema = version?.setup_schema
352
+
353
+ // Check if setup has any actual values
354
+ const hasSetup = formData?.setup && Object.keys(formData.setup).length > 0
355
+
356
+ // Process input with schema defaults. Without form data (a public app page),
357
+ // the examples run the app's own example input, or one built from its schema.
358
+ const processedInput = useMemo(() => {
359
+ const input = formData?.input ?? exampleInput(schema)
360
+ if (!schema?.properties) return input
361
+
362
+ const result: Record<string, unknown> = {}
363
+ for (const [key, value] of Object.entries(input)) {
364
+ if (value === undefined && schema.properties[key]) {
365
+ const prop = schema.properties[key]
366
+ if (prop.default !== undefined) {
367
+ result[key] = prop.default
368
+ } else {
369
+ switch (prop.type) {
370
+ case "string": result[key] = ""; break
371
+ case "number":
372
+ case "integer": result[key] = 0; break
373
+ case "boolean": result[key] = false; break
374
+ case "array": result[key] = []; break
375
+ case "object": result[key] = {}; break
376
+ default: result[key] = value
377
+ }
378
+ }
379
+ } else {
380
+ result[key] = value
381
+ }
382
+ }
383
+ return result
384
+ }, [formData?.input, schema])
385
+
386
+ // Example params for code generation
387
+ const exampleParams = useMemo(() => ({
388
+ appRef,
389
+ shortId: version?.short_id,
390
+ input: processedInput,
391
+ setup: hasSetup ? formData?.setup : undefined,
392
+ infra: formData?.infra || undefined,
393
+ workers: formData?.infra === 'private' ? (formData?.workers || undefined) : undefined,
394
+ }), [appRef, version?.short_id, processedInput, formData?.setup, hasSetup, formData?.infra, formData?.workers])
395
+
396
+ // Generate separate examples for result-only and streaming
397
+ const resultOnlyExamples = useMemo(() => generateResultOnlyExamples(exampleParams), [exampleParams])
398
+ const streamingExamples = useMemo(() => generateStreamingExamples(exampleParams), [exampleParams])
399
+
400
+ // Get current code for selected language
401
+ const currentResultCode = resultOnlyExamples[language] || ''
402
+ const currentStreamCode = streamingExamples[language] || ''
403
+ const backgroundExamples = useMemo(() => generateBackgroundExamples(exampleParams), [exampleParams])
404
+ const currentBackgroundCode = backgroundExamples[language] || ''
405
+ const fileExamples = useMemo(() => generateFileExamples(appRef), [appRef])
406
+ const currentFileExamples = fileExamples[language] ?? fileExamples.curl
407
+ const currentInstall = installCommands[language]
408
+ const currentKeySetup = apiKeySetup[language]
409
+
410
+ // Parse schema properties for display
411
+ const inputFields = useMemo(() => {
412
+ if (!schema) return []
413
+ const { fields } = getFieldsFromSchema(schema)
414
+ return fields
415
+ }, [schema])
416
+
417
+ const outputFields = useMemo(() => {
418
+ if (!outputSchema) return []
419
+ const { fields } = getFieldsFromSchema(outputSchema)
420
+ return fields
421
+ }, [outputSchema])
422
+
423
+ const setupFields = useMemo(() => {
424
+ if (!setupSchema) return []
425
+ const { fields } = getFieldsFromSchema(setupSchema)
426
+ return fields
427
+ }, [setupSchema])
428
+
429
+ const hasSetupFields = setupFields.length > 0
430
+
431
+ // Build ToC with conditional setup section
432
+ const tocItems = useMemo(() => buildTocItems(hasSetupFields), [hasSetupFields])
433
+
434
+ // Footer link for ToC
435
+ const TocFooter = () => (
436
+ <div className="mt-4 pt-4 border-t border-border">
437
+ <Link
438
+ to={`${docsBasePath}/api/authentication`}
439
+ className="text-xs text-muted-foreground hover:text-foreground transition-colors"
440
+ >
441
+ → full api documentation
442
+ </Link>
443
+ </div>
444
+ )
445
+
446
+ return (
447
+ <div className={cn("", className)}>
448
+ <div className="grid grid-cols-1 gap-8 lg:grid-cols-12">
449
+ {/* table of contents - sticky sidebar */}
450
+ <div className="col-span-1 lg:col-span-3">
451
+ <div className="lg:sticky lg:top-4 border border-border rounded-xl bg-card p-4">
452
+ <TableOfContents
453
+ items={tocItems}
454
+ header={<LanguageSelector language={language} onLanguageChange={setLanguage} />}
455
+ footer={<TocFooter />}
456
+ />
457
+ </div>
458
+ </div>
459
+
460
+ {/* main content */}
461
+ <div className="col-span-1 lg:col-span-9 border border-border rounded-xl bg-card p-6 space-y-8">
462
+ {/* about section */}
463
+ <div>
464
+ <h3 className="text-lg font-medium mb-2">about</h3>
465
+ <p className="text-sm text-muted-foreground">{app.description?.toLowerCase() || `run ${app.title || app.name} via api.`}</p>
466
+ </div>
467
+
468
+ {/* get an api key */}
469
+ <div className="space-y-3">
470
+ <SectionHeading id="api-key">get an api key</SectionHeading>
471
+ <p className="text-sm text-muted-foreground">
472
+ create one in <Link to={`${settingsBasePath}/keys`} className="text-primary hover:underline">settings → api keys</Link>
473
+ {language === 'belt'
474
+ ? <>, or skip it and sign in with <code className="rounded bg-muted px-1 py-0.5 text-xs">belt login</code>. the examples read it from </>
475
+ : <>. the examples read it from </>}
476
+ <code className="rounded bg-muted px-1 py-0.5 text-xs">{currentKeySetup.variable}</code>.
477
+ </p>
478
+ <CodeBlock language="bash" textSize="text-xs">
479
+ {currentKeySetup.command}
480
+ </CodeBlock>
481
+ </div>
482
+
483
+ <Separator />
484
+
485
+ {/* 1. calling the api */}
486
+ <SectionHeading id="api-call">1. calling the api</SectionHeading>
487
+
488
+ <div className="space-y-6">
489
+ {/* install */}
490
+ <div className="space-y-3">
491
+ <SectionHeading id="api-call-install" level={3}>
492
+ install the client
493
+ </SectionHeading>
494
+ <p className="text-sm text-muted-foreground">
495
+ {currentInstall
496
+ ? 'the client provides a convenient way to interact with the api.'
497
+ : 'nothing to install. any http client works; the examples use curl, and jq to read responses. every response wraps its result in a data field.'}
498
+ </p>
499
+ {currentInstall && (
500
+ <CodeBlock
501
+ language={language === 'swift' ? 'swift' : 'bash'}
502
+ textSize="text-xs"
503
+ className="max-h-[200px]"
504
+ >
505
+ {currentInstall.command}
506
+ </CodeBlock>
507
+ )}
508
+ </div>
509
+
510
+ {/* run and get result */}
511
+ <div className="space-y-3">
512
+ <SectionHeading id="api-call-result" level={3}>
513
+ run and get result
514
+ </SectionHeading>
515
+ <p className="text-sm text-muted-foreground">
516
+ {language === 'curl'
517
+ ? <>send <code className="rounded bg-muted px-1 py-0.5 text-xs">"wait": true</code> to hold the connection until the task finishes. without it the call returns right away with the task still running.</>
518
+ : "submit a request and wait for the final result. best for batch processing or when you don't need progress updates."}
519
+ </p>
520
+ <CodeBlock
521
+ language={(language === 'curl' || language === 'belt') ? 'bash' : language}
522
+ textSize="text-xs"
523
+ className="max-h-[300px]"
524
+ >
525
+ {currentResultCode}
526
+ </CodeBlock>
527
+ </div>
528
+
529
+ {/* stream live updates */}
530
+ <div className="space-y-3">
531
+ <SectionHeading id="api-call-stream" level={3}>
532
+ stream live updates
533
+ </SectionHeading>
534
+ <p className="text-sm text-muted-foreground">
535
+ get real-time progress updates as the task runs. ideal for showing progress bars, partial results, or long-running tasks.
536
+ {language === 'curl' && <> send <code className="rounded bg-muted px-1 py-0.5 text-xs">"stream": true</code> and the response is a server-sent event stream that ends when the task does.</>}
537
+ </p>
538
+ <CodeBlock
539
+ language={(language === 'curl' || language === 'belt') ? 'bash' : language}
540
+ textSize="text-xs"
541
+ className="max-h-[300px]"
542
+ >
543
+ {currentStreamCode}
544
+ </CodeBlock>
545
+ </div>
546
+
547
+ {/* run in the background */}
548
+ <div className="space-y-3">
549
+ <SectionHeading id="api-call-background" level={3}>
550
+ run in the background
551
+ </SectionHeading>
552
+ <p className="text-sm text-muted-foreground">
553
+ start the task and get its id back right away, then check on it later. for long tasks, or when you'd rather be called back, see <a href="#webhooks" className="text-primary hover:underline">webhooks</a>.
554
+ </p>
555
+ <CodeBlock
556
+ language={(language === 'curl' || language === 'belt') ? 'bash' : language}
557
+ textSize="text-xs"
558
+ className="max-h-[300px]"
559
+ >
560
+ {currentBackgroundCode}
561
+ </CodeBlock>
562
+ </div>
563
+ </div>
564
+
565
+ <Separator />
566
+
567
+ {/* 2. authentication */}
568
+ <SectionHeading id="auth">2. authentication</SectionHeading>
569
+
570
+ <p className="text-sm text-muted-foreground">
571
+ the api uses api keys for authentication. see the{' '}
572
+ <Link to={`${docsBasePath}/api/authentication`} className="text-primary hover:underline">authentication docs</Link>{' '}
573
+ for detailed setup instructions.
574
+ </p>
575
+
576
+ <Separator />
577
+
578
+ {/* 3. files */}
579
+ <SectionHeading id="files">3. files</SectionHeading>
580
+
581
+ <p className="text-sm text-muted-foreground">
582
+ file inputs are automatically handled by the sdk. you can pass local paths, urls, or base64 data.
583
+ </p>
584
+
585
+ <div className="space-y-6">
586
+ {/* automatic upload */}
587
+ <div className="space-y-3">
588
+ <SectionHeading id="files-auto-upload" level={3}>
589
+ automatic upload
590
+ </SectionHeading>
591
+ <p className="text-sm text-muted-foreground">
592
+ {language === 'python'
593
+ ? 'the python sdk automatically detects local file paths and uploads them. urls are passed through as-is.'
594
+ : language === 'typescript' || language === 'javascript'
595
+ ? 'the javascript sdk automatically uploads blob/file objects and data uris. urls are passed through as-is.'
596
+ : language === 'swift'
597
+ ? 'pass urls directly. upload local files with client.files.upload (below) and pass the returned uri.'
598
+ : language === 'belt'
599
+ ? 'belt detects local file paths in the input and uploads them. urls are passed through as-is.'
600
+ : 'pass urls directly or use data uris for inline content.'}
601
+ </p>
602
+ <CodeBlock
603
+ language={(language === 'curl' || language === 'belt') ? 'bash' : language}
604
+ textSize="text-xs"
605
+ >
606
+ {currentFileExamples.auto}
607
+ </CodeBlock>
608
+ </div>
609
+
610
+ {/* manual upload */}
611
+ <div className="space-y-3">
612
+ <SectionHeading id="files-manual" level={3}>
613
+ manual upload
614
+ </SectionHeading>
615
+ <p className="text-sm text-muted-foreground">
616
+ you can also upload files manually and use the returned url.
617
+ </p>
618
+ <CodeBlock
619
+ language={(language === 'curl' || language === 'belt') ? 'bash' : language}
620
+ textSize="text-xs"
621
+ >
622
+ {currentFileExamples.manual}
623
+ </CodeBlock>
624
+ </div>
625
+ </div>
626
+
627
+ <Separator />
628
+
629
+ {/* 4. webhooks */}
630
+ <SectionHeading id="webhooks">4. webhooks</SectionHeading>
631
+
632
+ <p className="text-sm text-muted-foreground">
633
+ get notified when a task completes by providing a <code className="rounded bg-muted px-1 py-0.5 text-xs">webhook</code> url.
634
+ when the task reaches a terminal state (completed, failed, or cancelled), a POST request is sent to your url with the task result.
635
+ </p>
636
+
637
+ <CodeBlock
638
+ language={(language === 'curl' || language === 'belt') ? 'bash' : language}
639
+ textSize="text-xs"
640
+ className="max-h-[300px]"
641
+ >
642
+ {language === 'python' ? `result = client.run({
643
+ "app": "${appRef}",
644
+ "input": ${JSON.stringify(processedInput, null, 4).split('\n').map((l, i) => i === 0 ? l : ' ' + l).join('\n')},
645
+ "webhook": "https://your-server.com/webhook"
646
+ }, wait=False)` :
647
+ language === 'typescript' || language === 'javascript' ? `const result = await client.run({
648
+ app: "${appRef}",
649
+ input: ${JSON.stringify(processedInput, null, 2).split('\n').map((l, i) => i === 0 ? l : ' ' + l).join('\n')},
650
+ webhook: "https://your-server.com/webhook"
651
+ }, { wait: false });` :
652
+ language === 'swift' ? generateSwiftWebhookExample(appRef, processedInput) :
653
+ `curl -X POST https://api.inference.sh/apps/run \\
654
+ -H "Authorization: Bearer $INFERENCE_API_KEY" \\
655
+ -H "Content-Type: application/json" \\
656
+ -d '{
657
+ "app": "${appRef}",
658
+ "input": ${JSON.stringify(processedInput, null, 4).split('\n').map((l, i) => i === 0 ? l : ' ' + l).join('\n')},
659
+ "webhook": "https://your-server.com/webhook"
660
+ }'`}
661
+ </CodeBlock>
662
+
663
+ <div className="space-y-3">
664
+ <p className="text-xs font-medium text-foreground">webhook payload</p>
665
+ <p className="text-xs text-muted-foreground">
666
+ your endpoint receives a JSON POST with the task result:
667
+ </p>
668
+ <CodeBlock language="json" textSize="text-xs">
669
+ {`{
670
+ "event": "task.completed",
671
+ "timestamp": "2024-01-15T10:30:05Z",
672
+ "data": {
673
+ "id": "task_abc123",
674
+ "short_id": "abc123",
675
+ "status": ${TaskStatusCompleted},
676
+ "status_text": "completed",
677
+ "output": { ... },
678
+ "created_at": "2024-01-15T10:30:00Z",
679
+ "updated_at": "2024-01-15T10:30:05Z"
680
+ }
681
+ }`}
682
+ </CodeBlock>
683
+ <div className="space-y-2">
684
+ {WEBHOOK_FIELDS.map(f => (
685
+ <div key={f.key} className="flex items-center gap-2 text-xs">
686
+ <span className="font-semibold">{f.key}</span>
687
+ <span className="font-mono text-muted-foreground">{f.type}</span>
688
+ <span className="text-muted-foreground">{f.desc}</span>
689
+ </div>
690
+ ))}
691
+ </div>
692
+ </div>
693
+
694
+ <Separator />
695
+
696
+ {/* 5. schema */}
697
+ <SectionHeading id="schema">5. schema</SectionHeading>
698
+
699
+ <div className="space-y-6">
700
+ {/* input schema */}
701
+ <div className="space-y-4">
702
+ <SectionHeading id="schema-input" level={3}>
703
+ input
704
+ </SectionHeading>
705
+ {inputFields.length > 0 ? (
706
+ <div className="space-y-3">
707
+ {inputFields.map((field) => (
708
+ <SchemaProperty
709
+ key={field.key}
710
+ field={field}
711
+ />
712
+ ))}
713
+ </div>
714
+ ) : (
715
+ <p className="text-sm text-muted-foreground">no input parameters defined.</p>
716
+ )}
717
+ </div>
718
+
719
+ {/* output schema */}
720
+ <div className="space-y-4">
721
+ <SectionHeading id="schema-output" level={3}>
722
+ output
723
+ </SectionHeading>
724
+ {outputFields.length > 0 ? (
725
+ <div className="space-y-3">
726
+ {outputFields.map((field) => (
727
+ <SchemaProperty
728
+ key={field.key}
729
+ field={field}
730
+ isOutput={true}
731
+ />
732
+ ))}
733
+ </div>
734
+ ) : (
735
+ <p className="text-sm text-muted-foreground">see task output for result format.</p>
736
+ )}
737
+ </div>
738
+
739
+ {/* setup schema */}
740
+ {hasSetupFields && (
741
+ <div className="space-y-4">
742
+ <SectionHeading id="schema-setup" level={3}>
743
+ setup
744
+ </SectionHeading>
745
+ <p className="text-xs text-muted-foreground">setup parameters are provided once via the <code className="rounded bg-muted px-1 py-0.5 text-xs">setup</code> field and are persisted for a session.</p>
746
+ <div className="space-y-3">
747
+ {setupFields.map((field) => (
748
+ <SchemaProperty
749
+ key={field.key}
750
+ field={field}
751
+ />
752
+ ))}
753
+ </div>
754
+ </div>
755
+ )}
756
+ </div>
757
+ </div>
758
+ </div>
759
+ </div>
760
+ )
761
+ }
762
+
763
+ export default ApiDocs