@brainfish-ai/devdoc 0.1.21
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/LICENSE +33 -0
- package/README.md +415 -0
- package/bin/devdoc.js +13 -0
- package/dist/cli/commands/build.d.ts +5 -0
- package/dist/cli/commands/build.js +87 -0
- package/dist/cli/commands/check.d.ts +1 -0
- package/dist/cli/commands/check.js +143 -0
- package/dist/cli/commands/create.d.ts +24 -0
- package/dist/cli/commands/create.js +387 -0
- package/dist/cli/commands/deploy.d.ts +9 -0
- package/dist/cli/commands/deploy.js +433 -0
- package/dist/cli/commands/dev.d.ts +6 -0
- package/dist/cli/commands/dev.js +139 -0
- package/dist/cli/commands/init.d.ts +11 -0
- package/dist/cli/commands/init.js +238 -0
- package/dist/cli/commands/keys.d.ts +12 -0
- package/dist/cli/commands/keys.js +165 -0
- package/dist/cli/commands/start.d.ts +5 -0
- package/dist/cli/commands/start.js +56 -0
- package/dist/cli/commands/upload.d.ts +13 -0
- package/dist/cli/commands/upload.js +238 -0
- package/dist/cli/commands/whoami.d.ts +8 -0
- package/dist/cli/commands/whoami.js +91 -0
- package/dist/cli/index.d.ts +1 -0
- package/dist/cli/index.js +106 -0
- package/dist/config/index.d.ts +80 -0
- package/dist/config/index.js +133 -0
- package/dist/constants.d.ts +9 -0
- package/dist/constants.js +13 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +12 -0
- package/dist/utils/logger.d.ts +16 -0
- package/dist/utils/logger.js +61 -0
- package/dist/utils/paths.d.ts +16 -0
- package/dist/utils/paths.js +50 -0
- package/package.json +51 -0
- package/renderer/app/api/assets/[...path]/route.ts +123 -0
- package/renderer/app/api/assets/route.ts +124 -0
- package/renderer/app/api/assets/upload/route.ts +177 -0
- package/renderer/app/api/auth-schemes/route.ts +77 -0
- package/renderer/app/api/chat/route.ts +858 -0
- package/renderer/app/api/codegen/route.ts +72 -0
- package/renderer/app/api/collections/route.ts +1016 -0
- package/renderer/app/api/debug/route.ts +53 -0
- package/renderer/app/api/deploy/route.ts +234 -0
- package/renderer/app/api/device/route.ts +42 -0
- package/renderer/app/api/docs/route.ts +187 -0
- package/renderer/app/api/keys/regenerate/route.ts +80 -0
- package/renderer/app/api/openapi-spec/route.ts +151 -0
- package/renderer/app/api/projects/[slug]/route.ts +153 -0
- package/renderer/app/api/projects/[slug]/stats/route.ts +96 -0
- package/renderer/app/api/projects/register/route.ts +152 -0
- package/renderer/app/api/proxy/route.ts +149 -0
- package/renderer/app/api/proxy-stream/route.ts +168 -0
- package/renderer/app/api/redirects/route.ts +47 -0
- package/renderer/app/api/schema/route.ts +65 -0
- package/renderer/app/api/subdomains/check/route.ts +172 -0
- package/renderer/app/api/suggestions/route.ts +144 -0
- package/renderer/app/favicon.ico +0 -0
- package/renderer/app/globals.css +1103 -0
- package/renderer/app/layout.tsx +47 -0
- package/renderer/app/llms-full.txt/route.ts +346 -0
- package/renderer/app/llms.txt/route.ts +279 -0
- package/renderer/app/page.tsx +14 -0
- package/renderer/app/robots.txt/route.ts +84 -0
- package/renderer/app/sitemap.xml/route.ts +199 -0
- package/renderer/components/docs/index.ts +12 -0
- package/renderer/components/docs/mdx/accordion.tsx +169 -0
- package/renderer/components/docs/mdx/badge.tsx +132 -0
- package/renderer/components/docs/mdx/callouts.tsx +154 -0
- package/renderer/components/docs/mdx/cards.tsx +213 -0
- package/renderer/components/docs/mdx/changelog.tsx +120 -0
- package/renderer/components/docs/mdx/code-block.tsx +186 -0
- package/renderer/components/docs/mdx/code-group.tsx +421 -0
- package/renderer/components/docs/mdx/file-embeds.tsx +105 -0
- package/renderer/components/docs/mdx/frame.tsx +112 -0
- package/renderer/components/docs/mdx/highlight.tsx +151 -0
- package/renderer/components/docs/mdx/iframe.tsx +134 -0
- package/renderer/components/docs/mdx/image.tsx +235 -0
- package/renderer/components/docs/mdx/index.ts +204 -0
- package/renderer/components/docs/mdx/mermaid.tsx +240 -0
- package/renderer/components/docs/mdx/param-field.tsx +200 -0
- package/renderer/components/docs/mdx/steps.tsx +113 -0
- package/renderer/components/docs/mdx/tabs.tsx +86 -0
- package/renderer/components/docs/mdx-renderer.tsx +100 -0
- package/renderer/components/docs/navigation/breadcrumbs.tsx +76 -0
- package/renderer/components/docs/navigation/index.ts +8 -0
- package/renderer/components/docs/navigation/page-nav.tsx +64 -0
- package/renderer/components/docs/navigation/sidebar.tsx +515 -0
- package/renderer/components/docs/navigation/toc.tsx +113 -0
- package/renderer/components/docs/notice.tsx +105 -0
- package/renderer/components/docs-header.tsx +274 -0
- package/renderer/components/docs-viewer/agent/agent-chat.tsx +2076 -0
- package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +90 -0
- package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +49 -0
- package/renderer/components/docs-viewer/agent/cards/index.tsx +50 -0
- package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +212 -0
- package/renderer/components/docs-viewer/agent/cards/types.ts +84 -0
- package/renderer/components/docs-viewer/agent/chat-message.tsx +17 -0
- package/renderer/components/docs-viewer/agent/index.tsx +6 -0
- package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +119 -0
- package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +46 -0
- package/renderer/components/docs-viewer/agent/messages/index.ts +17 -0
- package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +721 -0
- package/renderer/components/docs-viewer/agent/messages/types.ts +61 -0
- package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +24 -0
- package/renderer/components/docs-viewer/agent/messages/user-message.tsx +51 -0
- package/renderer/components/docs-viewer/code-editor/index.tsx +2 -0
- package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +1283 -0
- package/renderer/components/docs-viewer/content/changelog-page.tsx +331 -0
- package/renderer/components/docs-viewer/content/doc-page.tsx +285 -0
- package/renderer/components/docs-viewer/content/documentation-viewer.tsx +17 -0
- package/renderer/components/docs-viewer/content/index.tsx +29 -0
- package/renderer/components/docs-viewer/content/introduction.tsx +21 -0
- package/renderer/components/docs-viewer/content/request-details.tsx +330 -0
- package/renderer/components/docs-viewer/content/sections/auth.tsx +69 -0
- package/renderer/components/docs-viewer/content/sections/body.tsx +66 -0
- package/renderer/components/docs-viewer/content/sections/headers.tsx +43 -0
- package/renderer/components/docs-viewer/content/sections/overview.tsx +40 -0
- package/renderer/components/docs-viewer/content/sections/parameters.tsx +43 -0
- package/renderer/components/docs-viewer/content/sections/responses.tsx +87 -0
- package/renderer/components/docs-viewer/global-auth-modal.tsx +352 -0
- package/renderer/components/docs-viewer/index.tsx +1466 -0
- package/renderer/components/docs-viewer/playground/auth-editor.tsx +280 -0
- package/renderer/components/docs-viewer/playground/body-editor.tsx +221 -0
- package/renderer/components/docs-viewer/playground/code-editor.tsx +224 -0
- package/renderer/components/docs-viewer/playground/code-snippet.tsx +387 -0
- package/renderer/components/docs-viewer/playground/graphql-playground.tsx +745 -0
- package/renderer/components/docs-viewer/playground/index.tsx +671 -0
- package/renderer/components/docs-viewer/playground/key-value-editor.tsx +261 -0
- package/renderer/components/docs-viewer/playground/method-selector.tsx +60 -0
- package/renderer/components/docs-viewer/playground/request-builder.tsx +179 -0
- package/renderer/components/docs-viewer/playground/request-tabs.tsx +237 -0
- package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +21 -0
- package/renderer/components/docs-viewer/playground/response-cards/index.tsx +93 -0
- package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +16 -0
- package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +23 -0
- package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +268 -0
- package/renderer/components/docs-viewer/playground/response-cards/types.ts +82 -0
- package/renderer/components/docs-viewer/playground/response-viewer.tsx +43 -0
- package/renderer/components/docs-viewer/search/index.ts +2 -0
- package/renderer/components/docs-viewer/search/search-dialog.tsx +331 -0
- package/renderer/components/docs-viewer/search/use-search.ts +117 -0
- package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +431 -0
- package/renderer/components/docs-viewer/shared/method-badge.tsx +41 -0
- package/renderer/components/docs-viewer/shared/schema-viewer.tsx +349 -0
- package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +239 -0
- package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +316 -0
- package/renderer/components/docs-viewer/sidebar/index.tsx +343 -0
- package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +202 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +118 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +226 -0
- package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +52 -0
- package/renderer/components/theme-provider.tsx +11 -0
- package/renderer/components/theme-toggle.tsx +76 -0
- package/renderer/components/ui/badge.tsx +46 -0
- package/renderer/components/ui/button.tsx +59 -0
- package/renderer/components/ui/dialog.tsx +118 -0
- package/renderer/components/ui/dropdown-menu.tsx +257 -0
- package/renderer/components/ui/input.tsx +21 -0
- package/renderer/components/ui/label.tsx +24 -0
- package/renderer/components/ui/navigation-menu.tsx +168 -0
- package/renderer/components/ui/select.tsx +190 -0
- package/renderer/components/ui/spinner.tsx +114 -0
- package/renderer/components/ui/tabs.tsx +66 -0
- package/renderer/components/ui/tooltip.tsx +61 -0
- package/renderer/hooks/use-code-copy.ts +88 -0
- package/renderer/hooks/use-openapi-title.ts +44 -0
- package/renderer/lib/api-docs/agent/index.ts +6 -0
- package/renderer/lib/api-docs/agent/indexer.ts +323 -0
- package/renderer/lib/api-docs/agent/spec-summary.ts +335 -0
- package/renderer/lib/api-docs/agent/types.ts +116 -0
- package/renderer/lib/api-docs/auth/auth-context.tsx +225 -0
- package/renderer/lib/api-docs/auth/auth-storage.ts +87 -0
- package/renderer/lib/api-docs/auth/crypto.ts +89 -0
- package/renderer/lib/api-docs/auth/index.ts +4 -0
- package/renderer/lib/api-docs/code-editor/db.ts +164 -0
- package/renderer/lib/api-docs/code-editor/hooks.ts +266 -0
- package/renderer/lib/api-docs/code-editor/index.ts +6 -0
- package/renderer/lib/api-docs/code-editor/mode-context.tsx +207 -0
- package/renderer/lib/api-docs/code-editor/types.ts +105 -0
- package/renderer/lib/api-docs/codegen/definitions.ts +297 -0
- package/renderer/lib/api-docs/codegen/har.ts +251 -0
- package/renderer/lib/api-docs/codegen/index.ts +159 -0
- package/renderer/lib/api-docs/factories.ts +151 -0
- package/renderer/lib/api-docs/index.ts +17 -0
- package/renderer/lib/api-docs/mobile-context.tsx +112 -0
- package/renderer/lib/api-docs/navigation-context.tsx +88 -0
- package/renderer/lib/api-docs/parsers/graphql/README.md +129 -0
- package/renderer/lib/api-docs/parsers/graphql/index.ts +91 -0
- package/renderer/lib/api-docs/parsers/graphql/parser.ts +491 -0
- package/renderer/lib/api-docs/parsers/graphql/transformer.ts +246 -0
- package/renderer/lib/api-docs/parsers/graphql/types.ts +283 -0
- package/renderer/lib/api-docs/parsers/openapi/README.md +32 -0
- package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +60 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +574 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +403 -0
- package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +232 -0
- package/renderer/lib/api-docs/parsers/openapi/index.ts +171 -0
- package/renderer/lib/api-docs/parsers/openapi/transformer.ts +277 -0
- package/renderer/lib/api-docs/parsers/openapi/validator.ts +31 -0
- package/renderer/lib/api-docs/playground/context.tsx +107 -0
- package/renderer/lib/api-docs/playground/navigation-context.tsx +124 -0
- package/renderer/lib/api-docs/playground/request-builder.ts +223 -0
- package/renderer/lib/api-docs/playground/request-runner.ts +282 -0
- package/renderer/lib/api-docs/playground/types.ts +35 -0
- package/renderer/lib/api-docs/types.ts +269 -0
- package/renderer/lib/api-docs/utils.ts +311 -0
- package/renderer/lib/cache.ts +193 -0
- package/renderer/lib/docs/config/index.ts +29 -0
- package/renderer/lib/docs/config/loader.ts +142 -0
- package/renderer/lib/docs/config/schema.ts +298 -0
- package/renderer/lib/docs/index.ts +12 -0
- package/renderer/lib/docs/mdx/compiler.ts +176 -0
- package/renderer/lib/docs/mdx/frontmatter.ts +80 -0
- package/renderer/lib/docs/mdx/index.ts +26 -0
- package/renderer/lib/docs/navigation/generator.ts +348 -0
- package/renderer/lib/docs/navigation/index.ts +12 -0
- package/renderer/lib/docs/navigation/types.ts +123 -0
- package/renderer/lib/docs-navigation-context.tsx +80 -0
- package/renderer/lib/multi-tenant/context.ts +105 -0
- package/renderer/lib/storage/blob.ts +845 -0
- package/renderer/lib/utils.ts +6 -0
- package/renderer/next.config.ts +76 -0
- package/renderer/package.json +66 -0
- package/renderer/postcss.config.mjs +5 -0
- package/renderer/public/assets/images/screenshot.png +0 -0
- package/renderer/public/assets/logo/dark.svg +9 -0
- package/renderer/public/assets/logo/light.svg +9 -0
- package/renderer/public/assets/logo.svg +9 -0
- package/renderer/public/file.svg +1 -0
- package/renderer/public/globe.svg +1 -0
- package/renderer/public/icon.png +0 -0
- package/renderer/public/logo.svg +9 -0
- package/renderer/public/window.svg +1 -0
- package/renderer/tsconfig.json +28 -0
- package/templates/basic/README.md +139 -0
- package/templates/basic/assets/favicon.svg +4 -0
- package/templates/basic/assets/logo.svg +9 -0
- package/templates/basic/docs.json +47 -0
- package/templates/basic/guides/configuration.mdx +149 -0
- package/templates/basic/guides/overview.mdx +96 -0
- package/templates/basic/index.mdx +39 -0
- package/templates/basic/package.json +14 -0
- package/templates/basic/quickstart.mdx +92 -0
- package/templates/basic/vercel.json +6 -0
- package/templates/graphql/README.md +139 -0
- package/templates/graphql/api-reference/schema.graphql +305 -0
- package/templates/graphql/assets/favicon.svg +4 -0
- package/templates/graphql/assets/logo.svg +9 -0
- package/templates/graphql/docs.json +54 -0
- package/templates/graphql/guides/configuration.mdx +149 -0
- package/templates/graphql/guides/overview.mdx +96 -0
- package/templates/graphql/index.mdx +39 -0
- package/templates/graphql/package.json +14 -0
- package/templates/graphql/quickstart.mdx +92 -0
- package/templates/graphql/vercel.json +6 -0
- package/templates/openapi/README.md +139 -0
- package/templates/openapi/api-reference/openapi.json +419 -0
- package/templates/openapi/assets/favicon.svg +4 -0
- package/templates/openapi/assets/logo.svg +9 -0
- package/templates/openapi/docs.json +61 -0
- package/templates/openapi/guides/configuration.mdx +149 -0
- package/templates/openapi/guides/overview.mdx +96 -0
- package/templates/openapi/index.mdx +39 -0
- package/templates/openapi/package.json +14 -0
- package/templates/openapi/quickstart.mdx +92 -0
- package/templates/openapi/vercel.json +6 -0
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react'
|
|
4
|
+
import { CaretDown, CaretRight, Plus, Minus } from '@phosphor-icons/react'
|
|
5
|
+
|
|
6
|
+
interface SchemaProperty {
|
|
7
|
+
type?: string
|
|
8
|
+
description?: string
|
|
9
|
+
format?: string
|
|
10
|
+
example?: unknown
|
|
11
|
+
properties?: Record<string, SchemaProperty>
|
|
12
|
+
items?: SchemaProperty
|
|
13
|
+
required?: string[]
|
|
14
|
+
allOf?: SchemaProperty[]
|
|
15
|
+
oneOf?: SchemaProperty[]
|
|
16
|
+
anyOf?: SchemaProperty[]
|
|
17
|
+
enum?: unknown[]
|
|
18
|
+
default?: unknown
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface SchemaViewerProps {
|
|
22
|
+
schema: SchemaProperty | string | unknown
|
|
23
|
+
requiredFields?: string[]
|
|
24
|
+
level?: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function getTypeLabel(prop: SchemaProperty): string {
|
|
28
|
+
if (prop.type === 'array' && prop.items) {
|
|
29
|
+
const itemType = prop.items.type || 'object'
|
|
30
|
+
return `${itemType}[]`
|
|
31
|
+
}
|
|
32
|
+
return prop.type || 'object'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function hasChildProperties(prop: SchemaProperty): boolean {
|
|
36
|
+
if (prop.properties && Object.keys(prop.properties).length > 0) {
|
|
37
|
+
return true
|
|
38
|
+
}
|
|
39
|
+
if (prop.type === 'array' && prop.items?.properties) {
|
|
40
|
+
return true
|
|
41
|
+
}
|
|
42
|
+
if (prop.allOf || prop.oneOf || prop.anyOf) {
|
|
43
|
+
return true
|
|
44
|
+
}
|
|
45
|
+
return false
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function PropertyRow({
|
|
49
|
+
name,
|
|
50
|
+
prop,
|
|
51
|
+
isRequired,
|
|
52
|
+
level = 0,
|
|
53
|
+
}: {
|
|
54
|
+
name: string
|
|
55
|
+
prop: SchemaProperty
|
|
56
|
+
isRequired: boolean
|
|
57
|
+
level?: number
|
|
58
|
+
}) {
|
|
59
|
+
const [showChildren, setShowChildren] = useState(false)
|
|
60
|
+
const hasChildren = hasChildProperties(prop)
|
|
61
|
+
const typeLabel = getTypeLabel(prop)
|
|
62
|
+
|
|
63
|
+
// Get child properties
|
|
64
|
+
const getChildProperties = (): Record<string, SchemaProperty> | null => {
|
|
65
|
+
if (prop.properties) {
|
|
66
|
+
return prop.properties
|
|
67
|
+
}
|
|
68
|
+
if (prop.type === 'array' && prop.items?.properties) {
|
|
69
|
+
return prop.items.properties
|
|
70
|
+
}
|
|
71
|
+
if (prop.allOf) {
|
|
72
|
+
// Merge all schemas
|
|
73
|
+
const merged: Record<string, SchemaProperty> = {}
|
|
74
|
+
prop.allOf.forEach((schema) => {
|
|
75
|
+
if (schema.properties) {
|
|
76
|
+
Object.assign(merged, schema.properties)
|
|
77
|
+
}
|
|
78
|
+
})
|
|
79
|
+
return Object.keys(merged).length > 0 ? merged : null
|
|
80
|
+
}
|
|
81
|
+
return null
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const getChildRequired = (): string[] => {
|
|
85
|
+
if (prop.required) return prop.required
|
|
86
|
+
if (prop.type === 'array' && prop.items?.required) {
|
|
87
|
+
return prop.items.required
|
|
88
|
+
}
|
|
89
|
+
if (prop.allOf) {
|
|
90
|
+
const allRequired: string[] = []
|
|
91
|
+
prop.allOf.forEach((schema) => {
|
|
92
|
+
if (schema.required) {
|
|
93
|
+
allRequired.push(...schema.required)
|
|
94
|
+
}
|
|
95
|
+
})
|
|
96
|
+
return allRequired
|
|
97
|
+
}
|
|
98
|
+
return []
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const childProps = getChildProperties()
|
|
102
|
+
const childRequired = getChildRequired()
|
|
103
|
+
|
|
104
|
+
return (
|
|
105
|
+
<div className={`${level > 0 ? 'border-l-2 border-muted pl-4 ml-2' : ''}`}>
|
|
106
|
+
<div className="py-3 border-b border-border/50">
|
|
107
|
+
<div className="flex items-start gap-2 flex-wrap">
|
|
108
|
+
<span className="font-semibold text-sm text-foreground">{name}</span>
|
|
109
|
+
<span className="text-xs text-muted-foreground">{typeLabel}</span>
|
|
110
|
+
{prop.format && (
|
|
111
|
+
<>
|
|
112
|
+
<span className="text-xs text-muted-foreground">·</span>
|
|
113
|
+
<span className="text-xs text-muted-foreground">{prop.format}</span>
|
|
114
|
+
</>
|
|
115
|
+
)}
|
|
116
|
+
{isRequired && (
|
|
117
|
+
<span className="text-xs text-orange-500 font-medium">required</span>
|
|
118
|
+
)}
|
|
119
|
+
{prop.example !== undefined && (
|
|
120
|
+
<button className="text-xs text-muted-foreground hover:text-foreground underline">
|
|
121
|
+
Example
|
|
122
|
+
</button>
|
|
123
|
+
)}
|
|
124
|
+
</div>
|
|
125
|
+
{prop.description && (
|
|
126
|
+
<p className="text-sm text-muted-foreground mt-1">{prop.description}</p>
|
|
127
|
+
)}
|
|
128
|
+
{prop.enum && (
|
|
129
|
+
<div className="mt-2 flex flex-wrap gap-1">
|
|
130
|
+
<span className="text-xs text-muted-foreground">Enum:</span>
|
|
131
|
+
{prop.enum.map((val, idx) => (
|
|
132
|
+
<code key={idx} className="text-xs bg-muted px-1.5 py-0.5 rounded">
|
|
133
|
+
{JSON.stringify(val)}
|
|
134
|
+
</code>
|
|
135
|
+
))}
|
|
136
|
+
</div>
|
|
137
|
+
)}
|
|
138
|
+
{prop.default !== undefined && (
|
|
139
|
+
<div className="mt-1">
|
|
140
|
+
<span className="text-xs text-muted-foreground">Default: </span>
|
|
141
|
+
<code className="text-xs bg-muted px-1.5 py-0.5 rounded">
|
|
142
|
+
{JSON.stringify(prop.default)}
|
|
143
|
+
</code>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
{hasChildren && childProps && (
|
|
147
|
+
<button
|
|
148
|
+
onClick={() => setShowChildren(!showChildren)}
|
|
149
|
+
className="mt-2 flex items-center gap-1 text-xs text-muted-foreground hover:text-foreground border rounded-full px-3 py-1 hover:bg-muted/50 transition-colors"
|
|
150
|
+
>
|
|
151
|
+
{showChildren ? (
|
|
152
|
+
<>
|
|
153
|
+
<Minus className="h-3 w-3" weight="bold" />
|
|
154
|
+
Hide Child Attributes
|
|
155
|
+
</>
|
|
156
|
+
) : (
|
|
157
|
+
<>
|
|
158
|
+
<Plus className="h-3 w-3" weight="bold" />
|
|
159
|
+
Show Child Attributes
|
|
160
|
+
</>
|
|
161
|
+
)}
|
|
162
|
+
</button>
|
|
163
|
+
)}
|
|
164
|
+
</div>
|
|
165
|
+
{showChildren && childProps && (
|
|
166
|
+
<div className="mt-2">
|
|
167
|
+
{Object.entries(childProps).map(([childName, childProp]) => (
|
|
168
|
+
<PropertyRow
|
|
169
|
+
key={childName}
|
|
170
|
+
name={childName}
|
|
171
|
+
prop={childProp}
|
|
172
|
+
isRequired={childRequired.includes(childName)}
|
|
173
|
+
level={level + 1}
|
|
174
|
+
/>
|
|
175
|
+
))}
|
|
176
|
+
</div>
|
|
177
|
+
)}
|
|
178
|
+
</div>
|
|
179
|
+
)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
export function SchemaViewer({ schema, requiredFields = [], level = 0 }: SchemaViewerProps) {
|
|
183
|
+
// Parse schema if it's a string
|
|
184
|
+
let parsedSchema: SchemaProperty
|
|
185
|
+
try {
|
|
186
|
+
if (typeof schema === 'string') {
|
|
187
|
+
parsedSchema = JSON.parse(schema)
|
|
188
|
+
} else {
|
|
189
|
+
parsedSchema = schema as SchemaProperty
|
|
190
|
+
}
|
|
191
|
+
} catch {
|
|
192
|
+
// If parsing fails, show raw content
|
|
193
|
+
return (
|
|
194
|
+
<pre className="text-xs text-muted-foreground p-3 bg-muted/50 rounded overflow-auto">
|
|
195
|
+
{typeof schema === 'string' ? schema : JSON.stringify(schema, null, 2)}
|
|
196
|
+
</pre>
|
|
197
|
+
)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Handle nested schema property (common in OpenAPI responses)
|
|
201
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
202
|
+
if ((parsedSchema as any).schema) {
|
|
203
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
204
|
+
parsedSchema = (parsedSchema as any).schema as SchemaProperty
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Handle allOf, oneOf, anyOf at root level
|
|
208
|
+
if (parsedSchema.allOf) {
|
|
209
|
+
const mergedProps: Record<string, SchemaProperty> = {}
|
|
210
|
+
const mergedRequired: string[] = []
|
|
211
|
+
parsedSchema.allOf.forEach((s) => {
|
|
212
|
+
if (s.properties) Object.assign(mergedProps, s.properties)
|
|
213
|
+
if (s.required) mergedRequired.push(...s.required)
|
|
214
|
+
})
|
|
215
|
+
parsedSchema = { ...parsedSchema, properties: mergedProps, required: mergedRequired }
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const properties = parsedSchema.properties || {}
|
|
219
|
+
const required = parsedSchema.required || requiredFields
|
|
220
|
+
|
|
221
|
+
if (Object.keys(properties).length === 0) {
|
|
222
|
+
// No properties - maybe it's a simple type
|
|
223
|
+
if (parsedSchema.type) {
|
|
224
|
+
return (
|
|
225
|
+
<div className="py-2">
|
|
226
|
+
<span className="text-xs text-muted-foreground">{parsedSchema.type}</span>
|
|
227
|
+
{parsedSchema.description && (
|
|
228
|
+
<p className="text-sm text-muted-foreground mt-1">{parsedSchema.description}</p>
|
|
229
|
+
)}
|
|
230
|
+
</div>
|
|
231
|
+
)
|
|
232
|
+
}
|
|
233
|
+
return (
|
|
234
|
+
<pre className="text-xs text-muted-foreground p-3 bg-muted/50 rounded overflow-auto">
|
|
235
|
+
{JSON.stringify(parsedSchema, null, 2)}
|
|
236
|
+
</pre>
|
|
237
|
+
)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
return (
|
|
241
|
+
<div className={level > 0 ? 'pl-4 border-l-2 border-muted' : ''}>
|
|
242
|
+
{Object.entries(properties).map(([name, prop]) => (
|
|
243
|
+
<PropertyRow
|
|
244
|
+
key={name}
|
|
245
|
+
name={name}
|
|
246
|
+
prop={prop}
|
|
247
|
+
isRequired={required.includes(name)}
|
|
248
|
+
level={level}
|
|
249
|
+
/>
|
|
250
|
+
))}
|
|
251
|
+
</div>
|
|
252
|
+
)
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Collapsible response component with schema view
|
|
256
|
+
interface ResponseSchemaProps {
|
|
257
|
+
code: number
|
|
258
|
+
status: string
|
|
259
|
+
description?: string
|
|
260
|
+
schema: unknown
|
|
261
|
+
contentType?: string
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export function ResponseSchema({
|
|
265
|
+
code,
|
|
266
|
+
status,
|
|
267
|
+
description,
|
|
268
|
+
schema,
|
|
269
|
+
contentType = 'application/json',
|
|
270
|
+
}: ResponseSchemaProps) {
|
|
271
|
+
const [isOpen, setIsOpen] = useState(code >= 200 && code < 300)
|
|
272
|
+
const [viewMode, setViewMode] = useState<'schema' | 'example'>('schema')
|
|
273
|
+
|
|
274
|
+
// Try to parse schema
|
|
275
|
+
let parsedSchema: SchemaProperty | null = null
|
|
276
|
+
try {
|
|
277
|
+
if (typeof schema === 'string') {
|
|
278
|
+
parsedSchema = JSON.parse(schema)
|
|
279
|
+
} else {
|
|
280
|
+
parsedSchema = schema as SchemaProperty
|
|
281
|
+
}
|
|
282
|
+
// Handle nested schema property
|
|
283
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
284
|
+
if (parsedSchema && (parsedSchema as any).schema) {
|
|
285
|
+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
286
|
+
parsedSchema = (parsedSchema as any).schema as SchemaProperty
|
|
287
|
+
}
|
|
288
|
+
} catch {
|
|
289
|
+
parsedSchema = null
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const typeLabel = parsedSchema?.type || 'object'
|
|
293
|
+
|
|
294
|
+
return (
|
|
295
|
+
<div className="border-b border-border">
|
|
296
|
+
<button
|
|
297
|
+
onClick={() => setIsOpen(!isOpen)}
|
|
298
|
+
className="w-full py-3 flex items-center gap-3 hover:bg-muted/30 transition-colors text-left"
|
|
299
|
+
>
|
|
300
|
+
{isOpen ? (
|
|
301
|
+
<CaretDown className="h-4 w-4 text-muted-foreground shrink-0" weight="bold" />
|
|
302
|
+
) : (
|
|
303
|
+
<CaretRight className="h-4 w-4 text-muted-foreground shrink-0" weight="bold" />
|
|
304
|
+
)}
|
|
305
|
+
<span
|
|
306
|
+
className={`font-semibold text-sm ${
|
|
307
|
+
code >= 200 && code < 300
|
|
308
|
+
? 'text-green-600'
|
|
309
|
+
: code >= 400
|
|
310
|
+
? 'text-red-500'
|
|
311
|
+
: 'text-orange-500'
|
|
312
|
+
}`}
|
|
313
|
+
>
|
|
314
|
+
{code}
|
|
315
|
+
</span>
|
|
316
|
+
<span className="text-sm text-muted-foreground truncate">
|
|
317
|
+
{description || status}
|
|
318
|
+
</span>
|
|
319
|
+
</button>
|
|
320
|
+
|
|
321
|
+
{isOpen && (
|
|
322
|
+
<div className="pb-4 pl-7">
|
|
323
|
+
{/* Type and content type row */}
|
|
324
|
+
<div className="flex items-center justify-between mb-3">
|
|
325
|
+
<div className="flex items-center gap-2">
|
|
326
|
+
<span className="text-xs text-muted-foreground">{typeLabel}</span>
|
|
327
|
+
<button
|
|
328
|
+
onClick={() => setViewMode(viewMode === 'schema' ? 'example' : 'schema')}
|
|
329
|
+
className={`text-xs underline ${viewMode === 'example' ? 'text-foreground' : 'text-muted-foreground hover:text-foreground'}`}
|
|
330
|
+
>
|
|
331
|
+
Example
|
|
332
|
+
</button>
|
|
333
|
+
</div>
|
|
334
|
+
<span className="text-xs text-muted-foreground font-mono">{contentType}</span>
|
|
335
|
+
</div>
|
|
336
|
+
|
|
337
|
+
{/* Schema or Example view */}
|
|
338
|
+
{viewMode === 'schema' && parsedSchema ? (
|
|
339
|
+
<SchemaViewer schema={parsedSchema} />
|
|
340
|
+
) : (
|
|
341
|
+
<pre className="text-xs bg-muted/50 p-3 rounded overflow-auto">
|
|
342
|
+
{typeof schema === 'string' ? schema : JSON.stringify(schema, null, 2)}
|
|
343
|
+
</pre>
|
|
344
|
+
)}
|
|
345
|
+
</div>
|
|
346
|
+
)}
|
|
347
|
+
</div>
|
|
348
|
+
)
|
|
349
|
+
}
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useMemo, useEffect } from 'react'
|
|
4
|
+
import { Folder, FolderOpen } from '@phosphor-icons/react'
|
|
5
|
+
import type { BrainfishCollection, BrainfishRESTRequest } from '@/lib/api-docs/types'
|
|
6
|
+
import { MethodBadge } from '../shared/method-badge'
|
|
7
|
+
import { SidebarItem } from './sidebar-item'
|
|
8
|
+
import { SidebarGroup } from './sidebar-group'
|
|
9
|
+
|
|
10
|
+
interface CollectionTreeProps {
|
|
11
|
+
collection: BrainfishCollection
|
|
12
|
+
selectedRequest: BrainfishRESTRequest | null
|
|
13
|
+
onSelectRequest: (request: BrainfishRESTRequest) => void
|
|
14
|
+
searchQuery?: string
|
|
15
|
+
level?: number
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Find the folder ID that contains a request (recursive)
|
|
20
|
+
*/
|
|
21
|
+
function _findFolderContainingRequest(
|
|
22
|
+
collection: BrainfishCollection,
|
|
23
|
+
requestId: string
|
|
24
|
+
): string | null {
|
|
25
|
+
// Check if request is directly in this collection's folders
|
|
26
|
+
for (const folder of collection.folders) {
|
|
27
|
+
// Check direct requests in folder
|
|
28
|
+
if (folder.requests.some(r => r.id === requestId)) {
|
|
29
|
+
return folder.id
|
|
30
|
+
}
|
|
31
|
+
// Check nested folders recursively
|
|
32
|
+
const nestedFolderId = _findFolderContainingRequest(folder, requestId)
|
|
33
|
+
if (nestedFolderId) {
|
|
34
|
+
return folder.id // Return this folder's ID so it gets expanded too
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
return null
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Get all folder IDs that need to be expanded to show a request
|
|
42
|
+
*/
|
|
43
|
+
function getFoldersToExpand(
|
|
44
|
+
collection: BrainfishCollection,
|
|
45
|
+
requestId: string,
|
|
46
|
+
path: string[] = []
|
|
47
|
+
): string[] {
|
|
48
|
+
for (const folder of collection.folders) {
|
|
49
|
+
// Check direct requests in folder
|
|
50
|
+
if (folder.requests.some(r => r.id === requestId)) {
|
|
51
|
+
return [...path, folder.id]
|
|
52
|
+
}
|
|
53
|
+
// Check nested folders recursively
|
|
54
|
+
const nestedPath = getFoldersToExpand(folder, requestId, [...path, folder.id])
|
|
55
|
+
if (nestedPath.length > path.length) {
|
|
56
|
+
return nestedPath
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return path
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function CollectionTree({
|
|
63
|
+
collection,
|
|
64
|
+
selectedRequest,
|
|
65
|
+
onSelectRequest,
|
|
66
|
+
searchQuery = '',
|
|
67
|
+
level = 0,
|
|
68
|
+
}: CollectionTreeProps) {
|
|
69
|
+
// Track which folders are expanded
|
|
70
|
+
const [expandedFolders, setExpandedFolders] = useState<Set<string>>(() => {
|
|
71
|
+
const expanded = new Set<string>()
|
|
72
|
+
// Expand all folders at root level by default
|
|
73
|
+
if (level === 0) {
|
|
74
|
+
collection.folders.forEach((folder) => {
|
|
75
|
+
expanded.add(folder.id)
|
|
76
|
+
})
|
|
77
|
+
}
|
|
78
|
+
return expanded
|
|
79
|
+
})
|
|
80
|
+
|
|
81
|
+
// Auto-expand folder when selectedRequest changes (e.g., via agent navigation)
|
|
82
|
+
useEffect(() => {
|
|
83
|
+
if (selectedRequest && level === 0) {
|
|
84
|
+
const foldersToExpand = getFoldersToExpand(collection, selectedRequest.id)
|
|
85
|
+
if (foldersToExpand.length > 0) {
|
|
86
|
+
setExpandedFolders(prev => {
|
|
87
|
+
const newExpanded = new Set(prev)
|
|
88
|
+
foldersToExpand.forEach(folderId => newExpanded.add(folderId))
|
|
89
|
+
return newExpanded
|
|
90
|
+
})
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}, [selectedRequest, collection, level])
|
|
94
|
+
|
|
95
|
+
const toggleFolder = (folderId: string) => {
|
|
96
|
+
const newExpanded = new Set(expandedFolders)
|
|
97
|
+
if (newExpanded.has(folderId)) {
|
|
98
|
+
newExpanded.delete(folderId)
|
|
99
|
+
} else {
|
|
100
|
+
newExpanded.add(folderId)
|
|
101
|
+
}
|
|
102
|
+
setExpandedFolders(newExpanded)
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Filter items based on search query
|
|
106
|
+
const filteredData = useMemo(() => {
|
|
107
|
+
if (!searchQuery.trim()) {
|
|
108
|
+
return { folders: collection.folders, requests: collection.requests }
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const query = searchQuery.toLowerCase()
|
|
112
|
+
const filteredFolders: BrainfishCollection[] = []
|
|
113
|
+
const filteredRequests: BrainfishRESTRequest[] = []
|
|
114
|
+
|
|
115
|
+
// Filter folders
|
|
116
|
+
for (const folder of collection.folders) {
|
|
117
|
+
const folderMatches = folder.name.toLowerCase().includes(query)
|
|
118
|
+
const folderRequests = folder.requests.filter(
|
|
119
|
+
(req) =>
|
|
120
|
+
req.name.toLowerCase().includes(query) ||
|
|
121
|
+
req.endpoint.toLowerCase().includes(query) ||
|
|
122
|
+
req.description?.toLowerCase().includes(query)
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
if (folderMatches || folderRequests.length > 0) {
|
|
126
|
+
filteredFolders.push({
|
|
127
|
+
...folder,
|
|
128
|
+
requests: folderMatches ? folder.requests : folderRequests,
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Filter root requests
|
|
134
|
+
filteredRequests.push(
|
|
135
|
+
...collection.requests.filter(
|
|
136
|
+
(req) =>
|
|
137
|
+
req.name.toLowerCase().includes(query) ||
|
|
138
|
+
req.endpoint.toLowerCase().includes(query) ||
|
|
139
|
+
req.description?.toLowerCase().includes(query)
|
|
140
|
+
)
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
return { folders: filteredFolders, requests: filteredRequests }
|
|
144
|
+
}, [collection, searchQuery])
|
|
145
|
+
|
|
146
|
+
// Show message if no content
|
|
147
|
+
if (filteredData.folders.length === 0 && filteredData.requests.length === 0) {
|
|
148
|
+
if (level === 0) {
|
|
149
|
+
return (
|
|
150
|
+
<li className="py-4 px-2 text-center">
|
|
151
|
+
<p className="text-sm text-muted-foreground">No endpoints found</p>
|
|
152
|
+
</li>
|
|
153
|
+
)
|
|
154
|
+
}
|
|
155
|
+
return null
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return (
|
|
159
|
+
<>
|
|
160
|
+
{/* Folders */}
|
|
161
|
+
{filteredData.folders.map((folder) => {
|
|
162
|
+
const isExpanded = expandedFolders.has(folder.id)
|
|
163
|
+
const hasContent = folder.requests.length > 0 || folder.folders.length > 0
|
|
164
|
+
|
|
165
|
+
if (!hasContent) {
|
|
166
|
+
// Empty folder - render as disabled item
|
|
167
|
+
return (
|
|
168
|
+
<SidebarItem
|
|
169
|
+
key={folder.id}
|
|
170
|
+
indent={level}
|
|
171
|
+
disabled
|
|
172
|
+
asideContent={
|
|
173
|
+
<Folder className="h-4 w-4 text-sidebar-foreground/40" weight="fill" />
|
|
174
|
+
}
|
|
175
|
+
>
|
|
176
|
+
{folder.name}
|
|
177
|
+
</SidebarItem>
|
|
178
|
+
)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return (
|
|
182
|
+
<SidebarGroup
|
|
183
|
+
key={folder.id}
|
|
184
|
+
title={
|
|
185
|
+
<span className="flex items-center gap-2">
|
|
186
|
+
{isExpanded ? (
|
|
187
|
+
<FolderOpen className="h-4 w-4 text-sidebar-foreground/60 shrink-0" weight="fill" />
|
|
188
|
+
) : (
|
|
189
|
+
<Folder className="h-4 w-4 text-sidebar-foreground/60 shrink-0" weight="fill" />
|
|
190
|
+
)}
|
|
191
|
+
<span className="truncate">{folder.name}</span>
|
|
192
|
+
</span>
|
|
193
|
+
}
|
|
194
|
+
defaultOpen={isExpanded}
|
|
195
|
+
indent={level}
|
|
196
|
+
onClick={() => toggleFolder(folder.id)}
|
|
197
|
+
>
|
|
198
|
+
{/* Nested folders */}
|
|
199
|
+
{folder.folders.length > 0 && (
|
|
200
|
+
<CollectionTree
|
|
201
|
+
collection={folder}
|
|
202
|
+
selectedRequest={selectedRequest}
|
|
203
|
+
onSelectRequest={onSelectRequest}
|
|
204
|
+
searchQuery={searchQuery}
|
|
205
|
+
level={level + 1}
|
|
206
|
+
/>
|
|
207
|
+
)}
|
|
208
|
+
|
|
209
|
+
{/* Requests in folder */}
|
|
210
|
+
{folder.requests.map((request) => (
|
|
211
|
+
<SidebarItem
|
|
212
|
+
key={request.id}
|
|
213
|
+
selected={selectedRequest?.id === request.id}
|
|
214
|
+
indent={level + 1}
|
|
215
|
+
onClick={() => onSelectRequest(request)}
|
|
216
|
+
asideContent={<MethodBadge method={request.method} size="sm" />}
|
|
217
|
+
>
|
|
218
|
+
{request.name}
|
|
219
|
+
</SidebarItem>
|
|
220
|
+
))}
|
|
221
|
+
</SidebarGroup>
|
|
222
|
+
)
|
|
223
|
+
})}
|
|
224
|
+
|
|
225
|
+
{/* Root Requests */}
|
|
226
|
+
{filteredData.requests.map((request) => (
|
|
227
|
+
<SidebarItem
|
|
228
|
+
key={request.id}
|
|
229
|
+
selected={selectedRequest?.id === request.id}
|
|
230
|
+
indent={level}
|
|
231
|
+
onClick={() => onSelectRequest(request)}
|
|
232
|
+
asideContent={<MethodBadge method={request.method} size="sm" />}
|
|
233
|
+
>
|
|
234
|
+
{request.name}
|
|
235
|
+
</SidebarItem>
|
|
236
|
+
))}
|
|
237
|
+
</>
|
|
238
|
+
)
|
|
239
|
+
}
|