@brainfish-ai/devdoc 0.1.26 → 0.1.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cli/commands/ai.d.ts +6 -0
- package/dist/cli/commands/ai.js +280 -0
- package/dist/cli/commands/create.d.ts +3 -0
- package/dist/cli/commands/create.js +162 -16
- package/dist/cli/index.js +7 -1
- package/package.json +1 -1
- package/renderer/app/api/docs/route.ts +6 -2
- package/renderer/app/icon.svg +4 -0
- package/renderer/components/docs-viewer/content/doc-page.tsx +17 -2
- package/renderer/components/docs-viewer/content/not-found-page.tsx +330 -0
- package/renderer/components/docs-viewer/index.tsx +15 -2
- package/renderer/app/favicon.ico +0 -0
|
@@ -0,0 +1,330 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useEffect, useState, useCallback, useRef } from 'react'
|
|
4
|
+
import { FileX, House, ArrowLeft, MagnifyingGlass, Spinner } from '@phosphor-icons/react'
|
|
5
|
+
import { Button } from '@/components/ui/button'
|
|
6
|
+
import { useDocsNavigation } from '@/lib/docs-navigation-context'
|
|
7
|
+
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
|
|
8
|
+
import { useCodeCopy } from '@/hooks/use-code-copy'
|
|
9
|
+
|
|
10
|
+
// Import MDX components for custom 404 pages
|
|
11
|
+
import {
|
|
12
|
+
Note,
|
|
13
|
+
Warning,
|
|
14
|
+
Info,
|
|
15
|
+
Tip,
|
|
16
|
+
Check,
|
|
17
|
+
Error as ErrorCallout,
|
|
18
|
+
Callout,
|
|
19
|
+
Card,
|
|
20
|
+
CardGroup,
|
|
21
|
+
Accordion,
|
|
22
|
+
AccordionGroup,
|
|
23
|
+
Steps,
|
|
24
|
+
Step,
|
|
25
|
+
Tabs,
|
|
26
|
+
Tab,
|
|
27
|
+
CodeGroup,
|
|
28
|
+
Frame,
|
|
29
|
+
Columns,
|
|
30
|
+
Snippet,
|
|
31
|
+
Latex,
|
|
32
|
+
ParamField,
|
|
33
|
+
ResponseField,
|
|
34
|
+
Expandable,
|
|
35
|
+
Iframe,
|
|
36
|
+
Video,
|
|
37
|
+
Loom,
|
|
38
|
+
Image,
|
|
39
|
+
Screenshot,
|
|
40
|
+
Logo,
|
|
41
|
+
Icon,
|
|
42
|
+
Highlight,
|
|
43
|
+
Marker,
|
|
44
|
+
Underline,
|
|
45
|
+
Badge,
|
|
46
|
+
Mermaid,
|
|
47
|
+
PDF,
|
|
48
|
+
Audio,
|
|
49
|
+
Download,
|
|
50
|
+
Hero,
|
|
51
|
+
Pre,
|
|
52
|
+
Tagline,
|
|
53
|
+
Headline,
|
|
54
|
+
Description,
|
|
55
|
+
CommandBox,
|
|
56
|
+
Section,
|
|
57
|
+
Center,
|
|
58
|
+
FeatureGrid,
|
|
59
|
+
FeatureItem,
|
|
60
|
+
ButtonLink,
|
|
61
|
+
Spacer,
|
|
62
|
+
Divider,
|
|
63
|
+
} from '../../docs/mdx/index'
|
|
64
|
+
|
|
65
|
+
// MDX components mapping for custom 404 pages
|
|
66
|
+
const mdxComponents = {
|
|
67
|
+
Note,
|
|
68
|
+
Warning,
|
|
69
|
+
Info,
|
|
70
|
+
Tip,
|
|
71
|
+
Check,
|
|
72
|
+
Error: ErrorCallout,
|
|
73
|
+
Callout,
|
|
74
|
+
Card,
|
|
75
|
+
CardGroup,
|
|
76
|
+
Accordion,
|
|
77
|
+
AccordionGroup,
|
|
78
|
+
Steps,
|
|
79
|
+
Step,
|
|
80
|
+
Tabs,
|
|
81
|
+
Tab,
|
|
82
|
+
CodeGroup,
|
|
83
|
+
Frame,
|
|
84
|
+
Columns,
|
|
85
|
+
Snippet,
|
|
86
|
+
Latex,
|
|
87
|
+
ParamField,
|
|
88
|
+
ResponseField,
|
|
89
|
+
Expandable,
|
|
90
|
+
Iframe,
|
|
91
|
+
Video,
|
|
92
|
+
Loom,
|
|
93
|
+
Image,
|
|
94
|
+
Screenshot,
|
|
95
|
+
Logo,
|
|
96
|
+
Icon,
|
|
97
|
+
Highlight,
|
|
98
|
+
Marker,
|
|
99
|
+
Underline,
|
|
100
|
+
Badge,
|
|
101
|
+
Mermaid,
|
|
102
|
+
PDF,
|
|
103
|
+
Audio,
|
|
104
|
+
Download,
|
|
105
|
+
Hero,
|
|
106
|
+
Pre,
|
|
107
|
+
Tagline,
|
|
108
|
+
Headline,
|
|
109
|
+
Description,
|
|
110
|
+
CommandBox,
|
|
111
|
+
Section,
|
|
112
|
+
Center,
|
|
113
|
+
FeatureGrid,
|
|
114
|
+
FeatureItem,
|
|
115
|
+
ButtonLink,
|
|
116
|
+
Spacer,
|
|
117
|
+
Divider,
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
interface NotFoundPageProps {
|
|
121
|
+
slug?: string
|
|
122
|
+
onGoBack?: () => void
|
|
123
|
+
onSearch?: () => void
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
interface Custom404Data {
|
|
127
|
+
frontmatter: {
|
|
128
|
+
title?: string
|
|
129
|
+
description?: string
|
|
130
|
+
mode?: 'default' | 'wide' | 'custom'
|
|
131
|
+
hideHeader?: boolean
|
|
132
|
+
background?: string
|
|
133
|
+
}
|
|
134
|
+
mdxSource: MDXRemoteSerializeResult
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function NotFoundPage({ slug, onGoBack, onSearch }: NotFoundPageProps) {
|
|
138
|
+
const docsNav = useDocsNavigation()
|
|
139
|
+
const [customPage, setCustomPage] = useState<Custom404Data | null>(null)
|
|
140
|
+
const [loading, setLoading] = useState(true)
|
|
141
|
+
const contentRef = useRef<HTMLDivElement>(null)
|
|
142
|
+
|
|
143
|
+
// Add copy buttons to code blocks after content loads
|
|
144
|
+
useCodeCopy(contentRef, [customPage])
|
|
145
|
+
|
|
146
|
+
// Try to load custom 404.mdx page
|
|
147
|
+
useEffect(() => {
|
|
148
|
+
async function loadCustom404() {
|
|
149
|
+
try {
|
|
150
|
+
const response = await fetch('/api/docs?slug=404&is404=true')
|
|
151
|
+
if (response.ok) {
|
|
152
|
+
const data = await response.json()
|
|
153
|
+
setCustomPage(data)
|
|
154
|
+
}
|
|
155
|
+
} catch {
|
|
156
|
+
// No custom 404 page, use default
|
|
157
|
+
} finally {
|
|
158
|
+
setLoading(false)
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
loadCustom404()
|
|
162
|
+
}, [])
|
|
163
|
+
|
|
164
|
+
const handleGoHome = useCallback(() => {
|
|
165
|
+
// Navigate to the first page of the current tab or just switch to first tab
|
|
166
|
+
if (docsNav?.switchToTab) {
|
|
167
|
+
window.location.hash = ''
|
|
168
|
+
window.location.reload()
|
|
169
|
+
} else {
|
|
170
|
+
window.location.hash = ''
|
|
171
|
+
}
|
|
172
|
+
}, [docsNav])
|
|
173
|
+
|
|
174
|
+
const handleGoBack = useCallback(() => {
|
|
175
|
+
if (onGoBack) {
|
|
176
|
+
onGoBack()
|
|
177
|
+
} else {
|
|
178
|
+
window.history.back()
|
|
179
|
+
}
|
|
180
|
+
}, [onGoBack])
|
|
181
|
+
|
|
182
|
+
// Show loading state briefly while checking for custom 404
|
|
183
|
+
if (loading) {
|
|
184
|
+
return (
|
|
185
|
+
<div className="docs-page docs-page-not-found w-full min-h-[200px]">
|
|
186
|
+
<div className="flex items-center justify-center py-12">
|
|
187
|
+
<Spinner className="h-6 w-6 animate-spin text-muted-foreground" />
|
|
188
|
+
</div>
|
|
189
|
+
</div>
|
|
190
|
+
)
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Render custom 404 page if available
|
|
194
|
+
if (customPage) {
|
|
195
|
+
const { mode = 'default', hideHeader, background } = customPage.frontmatter
|
|
196
|
+
const isCustomMode = mode === 'custom'
|
|
197
|
+
const showHeader = !hideHeader && !isCustomMode
|
|
198
|
+
|
|
199
|
+
// Custom mode: Full-width layout
|
|
200
|
+
if (isCustomMode) {
|
|
201
|
+
return (
|
|
202
|
+
<div
|
|
203
|
+
ref={contentRef}
|
|
204
|
+
className="docs-page docs-page-not-found docs-content w-full min-h-full"
|
|
205
|
+
style={{ background: background || 'var(--background)' }}
|
|
206
|
+
>
|
|
207
|
+
<div className="docs-custom-content [&>*]:w-full">
|
|
208
|
+
<MDXRemote {...customPage.mdxSource} components={mdxComponents} />
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
)
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// Default mode with optional header
|
|
215
|
+
const containerClass = mode === 'wide' ? 'max-w-6xl' : 'max-w-4xl'
|
|
216
|
+
|
|
217
|
+
return (
|
|
218
|
+
<div ref={contentRef} className={`docs-page docs-page-not-found docs-content ${containerClass} mx-auto px-4 py-6 sm:px-8 sm:py-8`}>
|
|
219
|
+
{showHeader && customPage.frontmatter.title && (
|
|
220
|
+
<div className="docs-page-header mb-6">
|
|
221
|
+
<h1 className="docs-content-title text-2xl sm:text-3xl font-bold mb-2 text-foreground">
|
|
222
|
+
{customPage.frontmatter.title}
|
|
223
|
+
</h1>
|
|
224
|
+
{customPage.frontmatter.description && (
|
|
225
|
+
<p className="docs-content-description text-lg text-muted-foreground">
|
|
226
|
+
{customPage.frontmatter.description}
|
|
227
|
+
</p>
|
|
228
|
+
)}
|
|
229
|
+
</div>
|
|
230
|
+
)}
|
|
231
|
+
<div className="docs-prose prose prose-sm max-w-none prose-headings:text-foreground prose-p:text-muted-foreground prose-strong:text-foreground">
|
|
232
|
+
<MDXRemote {...customPage.mdxSource} components={mdxComponents} />
|
|
233
|
+
</div>
|
|
234
|
+
|
|
235
|
+
{/* Always show navigation actions for custom 404 */}
|
|
236
|
+
<div className="mt-8 pt-6 border-t border-border">
|
|
237
|
+
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
|
|
238
|
+
<Button variant="outline" size="sm" onClick={handleGoBack} className="gap-2 min-w-[120px]">
|
|
239
|
+
<ArrowLeft className="w-4 h-4" />
|
|
240
|
+
Go back
|
|
241
|
+
</Button>
|
|
242
|
+
<Button variant="default" size="sm" onClick={handleGoHome} className="gap-2 min-w-[120px]">
|
|
243
|
+
<House className="w-4 h-4" />
|
|
244
|
+
Go home
|
|
245
|
+
</Button>
|
|
246
|
+
{onSearch && (
|
|
247
|
+
<Button variant="outline" size="sm" onClick={onSearch} className="gap-2 min-w-[120px]">
|
|
248
|
+
<MagnifyingGlass className="w-4 h-4" />
|
|
249
|
+
Search
|
|
250
|
+
</Button>
|
|
251
|
+
)}
|
|
252
|
+
</div>
|
|
253
|
+
</div>
|
|
254
|
+
</div>
|
|
255
|
+
)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Default 404 page
|
|
259
|
+
return (
|
|
260
|
+
<div className="docs-page docs-page-not-found w-full min-h-[calc(100vh-200px)] flex items-center justify-center">
|
|
261
|
+
<div className="text-center px-4 py-12 max-w-md mx-auto">
|
|
262
|
+
{/* Icon */}
|
|
263
|
+
<div className="mb-6">
|
|
264
|
+
<div className="inline-flex items-center justify-center w-20 h-20 rounded-full bg-muted/50 text-muted-foreground">
|
|
265
|
+
<FileX className="w-10 h-10" weight="light" />
|
|
266
|
+
</div>
|
|
267
|
+
</div>
|
|
268
|
+
|
|
269
|
+
{/* Title */}
|
|
270
|
+
<h1 className="text-2xl font-semibold text-foreground mb-2">
|
|
271
|
+
Page not found
|
|
272
|
+
</h1>
|
|
273
|
+
|
|
274
|
+
{/* Description */}
|
|
275
|
+
<p className="text-muted-foreground mb-2">
|
|
276
|
+
The page you're looking for doesn't exist or has been moved.
|
|
277
|
+
</p>
|
|
278
|
+
|
|
279
|
+
{/* Show the slug that wasn't found */}
|
|
280
|
+
{slug && (
|
|
281
|
+
<p className="text-sm text-muted-foreground/70 mb-6 font-mono bg-muted/30 px-3 py-1.5 rounded-md inline-block">
|
|
282
|
+
/{slug}
|
|
283
|
+
</p>
|
|
284
|
+
)}
|
|
285
|
+
|
|
286
|
+
{!slug && <div className="mb-6" />}
|
|
287
|
+
|
|
288
|
+
{/* Actions */}
|
|
289
|
+
<div className="flex flex-col sm:flex-row items-center justify-center gap-3">
|
|
290
|
+
<Button
|
|
291
|
+
variant="outline"
|
|
292
|
+
size="sm"
|
|
293
|
+
onClick={handleGoBack}
|
|
294
|
+
className="gap-2 min-w-[120px]"
|
|
295
|
+
>
|
|
296
|
+
<ArrowLeft className="w-4 h-4" />
|
|
297
|
+
Go back
|
|
298
|
+
</Button>
|
|
299
|
+
|
|
300
|
+
<Button
|
|
301
|
+
variant="default"
|
|
302
|
+
size="sm"
|
|
303
|
+
onClick={handleGoHome}
|
|
304
|
+
className="gap-2 min-w-[120px]"
|
|
305
|
+
>
|
|
306
|
+
<House className="w-4 h-4" />
|
|
307
|
+
Go home
|
|
308
|
+
</Button>
|
|
309
|
+
|
|
310
|
+
{onSearch && (
|
|
311
|
+
<Button
|
|
312
|
+
variant="outline"
|
|
313
|
+
size="sm"
|
|
314
|
+
onClick={onSearch}
|
|
315
|
+
className="gap-2 min-w-[120px]"
|
|
316
|
+
>
|
|
317
|
+
<MagnifyingGlass className="w-4 h-4" />
|
|
318
|
+
Search
|
|
319
|
+
</Button>
|
|
320
|
+
)}
|
|
321
|
+
</div>
|
|
322
|
+
|
|
323
|
+
{/* Help text */}
|
|
324
|
+
<p className="text-xs text-muted-foreground/60 mt-8">
|
|
325
|
+
If you believe this is an error, please check the URL or contact the documentation maintainer.
|
|
326
|
+
</p>
|
|
327
|
+
</div>
|
|
328
|
+
</div>
|
|
329
|
+
)
|
|
330
|
+
}
|
|
@@ -4,6 +4,7 @@ import { useEffect, useState, useCallback, useRef } from 'react'
|
|
|
4
4
|
import { Spinner } from '@phosphor-icons/react'
|
|
5
5
|
import type { BrainfishCollection, BrainfishRESTRequest, BrainfishDocGroup } from '@/lib/api-docs/types'
|
|
6
6
|
import { DocsSidebar } from './sidebar'
|
|
7
|
+
import { NotFoundPage } from './content/not-found-page'
|
|
7
8
|
import { ApiPlayground } from './playground'
|
|
8
9
|
import type { DebugContext } from './playground/response-viewer'
|
|
9
10
|
import { RightSidebar } from './sidebar/right-sidebar'
|
|
@@ -391,6 +392,7 @@ function DocsContent() {
|
|
|
391
392
|
const [isVersionLoading, setIsVersionLoading] = useState(false) // For version switch only
|
|
392
393
|
const [error, setError] = useState<string | null>(null)
|
|
393
394
|
const [showAuthModal, setShowAuthModal] = useState(false)
|
|
395
|
+
const [notFoundSlug, setNotFoundSlug] = useState<string | null>(null) // Track 404 for invalid URLs
|
|
394
396
|
|
|
395
397
|
// Prefill context for agent
|
|
396
398
|
const { setPrefill } = usePlaygroundPrefill()
|
|
@@ -481,13 +483,16 @@ function DocsContent() {
|
|
|
481
483
|
setSelectedRequest(request)
|
|
482
484
|
setSelectedDocSection(null)
|
|
483
485
|
setSelectedDocPage(null)
|
|
486
|
+
setNotFoundSlug(null)
|
|
484
487
|
} else {
|
|
485
|
-
// Endpoint not found -
|
|
488
|
+
// Endpoint not found - show 404 page
|
|
486
489
|
setSelectedRequest(null)
|
|
487
490
|
setSelectedDocSection(null)
|
|
488
491
|
setSelectedDocPage(null)
|
|
492
|
+
setNotFoundSlug(`endpoint/${actualId}`)
|
|
489
493
|
}
|
|
490
494
|
} else if (actualType === 'page' && actualId) {
|
|
495
|
+
setNotFoundSlug(null) // Clear not found state - DocPage handles its own 404
|
|
491
496
|
setSelectedDocPage(actualId)
|
|
492
497
|
setSelectedRequest(null)
|
|
493
498
|
setSelectedDocSection(null)
|
|
@@ -515,6 +520,7 @@ function DocsContent() {
|
|
|
515
520
|
setSelectedRequest(request)
|
|
516
521
|
setSelectedDocSection(null)
|
|
517
522
|
setSelectedDocPage(null)
|
|
523
|
+
setNotFoundSlug(null) // Clear 404 state when selecting an endpoint
|
|
518
524
|
updateUrlHash(`endpoint/${request.id}`)
|
|
519
525
|
// Reset tab navigation so the new endpoint can determine its default tab
|
|
520
526
|
resetNavigation()
|
|
@@ -527,6 +533,7 @@ function DocsContent() {
|
|
|
527
533
|
setSelectedDocSection(isIntro ? null : headingId)
|
|
528
534
|
setSelectedRequest(null)
|
|
529
535
|
setSelectedDocPage(null)
|
|
536
|
+
setNotFoundSlug(null) // Clear 404 state
|
|
530
537
|
|
|
531
538
|
updateUrlHash(isIntro ? '' : `doc/${headingId}`)
|
|
532
539
|
|
|
@@ -599,6 +606,7 @@ function DocsContent() {
|
|
|
599
606
|
setSelectedDocPage(pageSlug)
|
|
600
607
|
setSelectedRequest(null)
|
|
601
608
|
setSelectedDocSection(null)
|
|
609
|
+
setNotFoundSlug(null) // Clear 404 state
|
|
602
610
|
updateUrlHash(`page/${pageSlug}`, targetTab)
|
|
603
611
|
switchToDocs()
|
|
604
612
|
|
|
@@ -1162,6 +1170,7 @@ function DocsContent() {
|
|
|
1162
1170
|
selectedApiVersion={selectedApiVersion}
|
|
1163
1171
|
handleApiVersionChange={handleApiVersionChange}
|
|
1164
1172
|
isVersionLoading={isVersionLoading}
|
|
1173
|
+
notFoundSlug={notFoundSlug}
|
|
1165
1174
|
/>
|
|
1166
1175
|
</NavigationProvider>
|
|
1167
1176
|
)
|
|
@@ -1193,6 +1202,7 @@ interface DocsWithModeProps {
|
|
|
1193
1202
|
selectedApiVersion: string | null
|
|
1194
1203
|
handleApiVersionChange: (version: string) => void
|
|
1195
1204
|
isVersionLoading: boolean
|
|
1205
|
+
notFoundSlug: string | null
|
|
1196
1206
|
}
|
|
1197
1207
|
|
|
1198
1208
|
// Mode toggle tabs - switches between Docs, API Client, and Notes
|
|
@@ -1268,6 +1278,7 @@ function DocsWithMode({
|
|
|
1268
1278
|
selectedApiVersion,
|
|
1269
1279
|
handleApiVersionChange,
|
|
1270
1280
|
isVersionLoading,
|
|
1281
|
+
notFoundSlug,
|
|
1271
1282
|
}: DocsWithModeProps) {
|
|
1272
1283
|
const { mode, switchToDocs } = useModeContext()
|
|
1273
1284
|
const { setTheme } = useTheme()
|
|
@@ -1562,7 +1573,9 @@ function DocsWithMode({
|
|
|
1562
1573
|
) : selectedRequest ? (
|
|
1563
1574
|
<RequestDetails request={selectedRequest} />
|
|
1564
1575
|
) : selectedDocPage ? (
|
|
1565
|
-
<DocPage slug={selectedDocPage} />
|
|
1576
|
+
<DocPage slug={selectedDocPage} onSearch={openSearch} />
|
|
1577
|
+
) : notFoundSlug ? (
|
|
1578
|
+
<NotFoundPage slug={notFoundSlug} onSearch={openSearch} />
|
|
1566
1579
|
) : (
|
|
1567
1580
|
<div className="flex-1 flex items-center justify-center bg-background">
|
|
1568
1581
|
<div className="text-center text-muted-foreground">
|
package/renderer/app/favicon.ico
DELETED
|
Binary file
|