@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,237 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useEffect } from 'react'
|
|
4
|
+
import type { BrainfishRESTReqBody, BrainfishRESTAuth } from '@/lib/api-docs/types'
|
|
5
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
6
|
+
import { Badge } from '@/components/ui/badge'
|
|
7
|
+
// import { Button } from '@/components/ui/button'
|
|
8
|
+
import { ShieldCheck, Link as LinkIcon, WarningCircle } from '@phosphor-icons/react'
|
|
9
|
+
import { KeyValueEditor, KeyValueItem } from './key-value-editor'
|
|
10
|
+
import { BodyEditor } from './body-editor'
|
|
11
|
+
import { AuthEditor } from './auth-editor'
|
|
12
|
+
import { getAuthTypeLabel } from '@/lib/api-docs/auth'
|
|
13
|
+
import { usePlaygroundNavigation, type PlaygroundTab } from '@/lib/api-docs/playground/navigation-context'
|
|
14
|
+
import { cn } from '@/lib/utils'
|
|
15
|
+
|
|
16
|
+
interface RequestTabsProps {
|
|
17
|
+
params: KeyValueItem[]
|
|
18
|
+
headers: KeyValueItem[]
|
|
19
|
+
body: string | null
|
|
20
|
+
bodyContentType: BrainfishRESTReqBody['contentType']
|
|
21
|
+
auth: BrainfishRESTAuth
|
|
22
|
+
usingGlobalAuth?: boolean
|
|
23
|
+
globalAuth?: BrainfishRESTAuth | null
|
|
24
|
+
onParamsChange: (params: KeyValueItem[]) => void
|
|
25
|
+
onHeadersChange: (headers: KeyValueItem[]) => void
|
|
26
|
+
onBodyChange: (body: string | null) => void
|
|
27
|
+
onBodyContentTypeChange: (contentType: BrainfishRESTReqBody['contentType']) => void
|
|
28
|
+
onAuthChange: (auth: BrainfishRESTAuth) => void
|
|
29
|
+
onUseGlobalAuth?: () => void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function RequestTabs({
|
|
33
|
+
params,
|
|
34
|
+
headers,
|
|
35
|
+
body,
|
|
36
|
+
bodyContentType,
|
|
37
|
+
auth,
|
|
38
|
+
usingGlobalAuth = false,
|
|
39
|
+
globalAuth,
|
|
40
|
+
onParamsChange,
|
|
41
|
+
onHeadersChange,
|
|
42
|
+
onBodyChange,
|
|
43
|
+
onBodyContentTypeChange,
|
|
44
|
+
onAuthChange,
|
|
45
|
+
onUseGlobalAuth,
|
|
46
|
+
}: RequestTabsProps) {
|
|
47
|
+
const paramsCount = params.filter((p) => p.active && p.key).length
|
|
48
|
+
const headersCount = headers.filter((h) => h.active && h.key).length
|
|
49
|
+
const hasBody = body !== null && body.length > 0
|
|
50
|
+
const hasAuth = auth.authType !== 'none'
|
|
51
|
+
|
|
52
|
+
// Always default to Parameters tab for consistency
|
|
53
|
+
const [activeTab, setActiveTab] = useState<PlaygroundTab>('params')
|
|
54
|
+
|
|
55
|
+
// Get navigation context
|
|
56
|
+
const { state: navState, setActiveTab: syncActiveTab, clearHighlight } = usePlaygroundNavigation()
|
|
57
|
+
|
|
58
|
+
// Sync with navigation context
|
|
59
|
+
useEffect(() => {
|
|
60
|
+
if (navState.activeTab && navState.activeTab !== activeTab) {
|
|
61
|
+
setActiveTab(navState.activeTab)
|
|
62
|
+
}
|
|
63
|
+
}, [navState.activeTab, activeTab])
|
|
64
|
+
|
|
65
|
+
// Update context when tab changes locally
|
|
66
|
+
const handleTabChange = (tab: string) => {
|
|
67
|
+
setActiveTab(tab as PlaygroundTab)
|
|
68
|
+
syncActiveTab(tab as PlaygroundTab)
|
|
69
|
+
clearHighlight()
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Check if a tab should show error indicator
|
|
73
|
+
const shouldShowTabError = (tab: PlaygroundTab) => {
|
|
74
|
+
if (!navState.showError || !navState.highlightField) return false
|
|
75
|
+
|
|
76
|
+
switch (tab) {
|
|
77
|
+
case 'params':
|
|
78
|
+
return navState.highlightField.type === 'param'
|
|
79
|
+
case 'headers':
|
|
80
|
+
return navState.highlightField.type === 'header'
|
|
81
|
+
case 'body':
|
|
82
|
+
return navState.highlightField.type === 'body'
|
|
83
|
+
case 'auth':
|
|
84
|
+
return navState.highlightField.type === 'auth'
|
|
85
|
+
default:
|
|
86
|
+
return false
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
return (
|
|
91
|
+
<Tabs value={activeTab} onValueChange={handleTabChange} className="flex flex-col h-full gap-0">
|
|
92
|
+
<TabsList className="w-full justify-start rounded-none border-b bg-muted/30 h-auto p-0 overflow-x-auto overflow-y-hidden flex-shrink-0">
|
|
93
|
+
<TabsTrigger
|
|
94
|
+
value="params"
|
|
95
|
+
className={cn(
|
|
96
|
+
"rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-background px-2 sm:px-4 py-2 sm:py-3 text-xs sm:text-sm whitespace-nowrap",
|
|
97
|
+
shouldShowTabError('params') && "border-red-500 data-[state=active]:border-red-500"
|
|
98
|
+
)}
|
|
99
|
+
>
|
|
100
|
+
<span className="hidden sm:inline">Parameters</span>
|
|
101
|
+
<span className="sm:hidden">Params</span>
|
|
102
|
+
{shouldShowTabError('params') ? (
|
|
103
|
+
<WarningCircle className="ml-1 sm:ml-2 h-4 w-4 text-red-500 animate-pulse" weight="fill" />
|
|
104
|
+
) : paramsCount > 0 ? (
|
|
105
|
+
<Badge variant="secondary" className="ml-1 sm:ml-2 h-4 sm:h-5 px-1 sm:px-1.5 text-xs">
|
|
106
|
+
{paramsCount}
|
|
107
|
+
</Badge>
|
|
108
|
+
) : null}
|
|
109
|
+
</TabsTrigger>
|
|
110
|
+
<TabsTrigger
|
|
111
|
+
value="body"
|
|
112
|
+
className={cn(
|
|
113
|
+
"rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-background px-2 sm:px-4 py-2 sm:py-3 text-xs sm:text-sm whitespace-nowrap",
|
|
114
|
+
shouldShowTabError('body') && "border-red-500 data-[state=active]:border-red-500"
|
|
115
|
+
)}
|
|
116
|
+
>
|
|
117
|
+
Body
|
|
118
|
+
{shouldShowTabError('body') ? (
|
|
119
|
+
<WarningCircle className="ml-1 sm:ml-2 h-4 w-4 text-red-500 animate-pulse" weight="fill" />
|
|
120
|
+
) : hasBody ? (
|
|
121
|
+
<span className="ml-1 sm:ml-2 w-2 h-2 bg-primary rounded-full" />
|
|
122
|
+
) : null}
|
|
123
|
+
</TabsTrigger>
|
|
124
|
+
<TabsTrigger
|
|
125
|
+
value="headers"
|
|
126
|
+
className={cn(
|
|
127
|
+
"rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-background px-2 sm:px-4 py-2 sm:py-3 text-xs sm:text-sm whitespace-nowrap",
|
|
128
|
+
shouldShowTabError('headers') && "border-red-500 data-[state=active]:border-red-500"
|
|
129
|
+
)}
|
|
130
|
+
>
|
|
131
|
+
Headers
|
|
132
|
+
{shouldShowTabError('headers') ? (
|
|
133
|
+
<WarningCircle className="ml-1 sm:ml-2 h-4 w-4 text-red-500 animate-pulse" weight="fill" />
|
|
134
|
+
) : headersCount > 0 ? (
|
|
135
|
+
<Badge variant="secondary" className="ml-1 sm:ml-2 h-4 sm:h-5 px-1 sm:px-1.5 text-xs">
|
|
136
|
+
{headersCount}
|
|
137
|
+
</Badge>
|
|
138
|
+
) : null}
|
|
139
|
+
</TabsTrigger>
|
|
140
|
+
<TabsTrigger
|
|
141
|
+
value="auth"
|
|
142
|
+
className={cn(
|
|
143
|
+
"rounded-none border-b-2 border-transparent data-[state=active]:border-primary data-[state=active]:bg-background px-2 sm:px-4 py-2 sm:py-3 text-xs sm:text-sm whitespace-nowrap",
|
|
144
|
+
shouldShowTabError('auth') && "border-red-500 data-[state=active]:border-red-500"
|
|
145
|
+
)}
|
|
146
|
+
>
|
|
147
|
+
<span className="hidden sm:inline">Authorization</span>
|
|
148
|
+
<span className="sm:hidden">Auth</span>
|
|
149
|
+
{shouldShowTabError('auth') ? (
|
|
150
|
+
<WarningCircle className="ml-1 sm:ml-2 h-4 w-4 text-red-500 animate-pulse" weight="fill" />
|
|
151
|
+
) : usingGlobalAuth ? (
|
|
152
|
+
<LinkIcon className="ml-1 sm:ml-2 h-3.5 w-3.5 text-green-600 dark:text-green-400" weight="bold" />
|
|
153
|
+
) : hasAuth ? (
|
|
154
|
+
<span className="ml-1 sm:ml-2 w-2 h-2 bg-primary rounded-full" />
|
|
155
|
+
) : null}
|
|
156
|
+
</TabsTrigger>
|
|
157
|
+
</TabsList>
|
|
158
|
+
|
|
159
|
+
<TabsContent value="params" className="flex-1 m-0 overflow-auto">
|
|
160
|
+
<KeyValueEditor
|
|
161
|
+
items={params}
|
|
162
|
+
onChange={onParamsChange}
|
|
163
|
+
title="Query Parameters"
|
|
164
|
+
keyLabel="Name"
|
|
165
|
+
valueLabel="Value"
|
|
166
|
+
showError={navState.highlightField?.type === 'param' && navState.showError}
|
|
167
|
+
/>
|
|
168
|
+
</TabsContent>
|
|
169
|
+
|
|
170
|
+
<TabsContent value="body" className="flex-1 m-0 overflow-auto">
|
|
171
|
+
<BodyEditor
|
|
172
|
+
body={body}
|
|
173
|
+
contentType={bodyContentType}
|
|
174
|
+
onBodyChange={onBodyChange}
|
|
175
|
+
onContentTypeChange={onBodyContentTypeChange}
|
|
176
|
+
showError={navState.highlightField?.type === 'body' && navState.showError}
|
|
177
|
+
/>
|
|
178
|
+
</TabsContent>
|
|
179
|
+
|
|
180
|
+
<TabsContent value="headers" className="flex-1 m-0 overflow-auto">
|
|
181
|
+
<KeyValueEditor
|
|
182
|
+
items={headers}
|
|
183
|
+
onChange={onHeadersChange}
|
|
184
|
+
title="Request Headers"
|
|
185
|
+
keyLabel="Name"
|
|
186
|
+
valueLabel="Value"
|
|
187
|
+
showError={navState.highlightField?.type === 'header' && navState.showError}
|
|
188
|
+
/>
|
|
189
|
+
</TabsContent>
|
|
190
|
+
|
|
191
|
+
<TabsContent value="auth" className="flex-1 m-0 overflow-hidden flex flex-col">
|
|
192
|
+
{/* Subtle Auth Status Bar */}
|
|
193
|
+
{globalAuth && globalAuth.authType !== 'none' && (
|
|
194
|
+
<div className="flex items-center justify-between px-4 py-1.5 bg-muted/40 border-b border-border text-xs">
|
|
195
|
+
{usingGlobalAuth ? (
|
|
196
|
+
<>
|
|
197
|
+
<span className="flex items-center gap-1.5 text-muted-foreground">
|
|
198
|
+
<LinkIcon className="h-3 w-3 text-green-600 dark:text-green-400" weight="bold" />
|
|
199
|
+
<span>Global: <span className="text-foreground font-medium">{getAuthTypeLabel(globalAuth)}</span></span>
|
|
200
|
+
</span>
|
|
201
|
+
<button
|
|
202
|
+
onClick={() => onAuthChange({ ...globalAuth })}
|
|
203
|
+
className="text-muted-foreground hover:text-foreground transition-colors"
|
|
204
|
+
>
|
|
205
|
+
Customize
|
|
206
|
+
</button>
|
|
207
|
+
</>
|
|
208
|
+
) : (
|
|
209
|
+
<>
|
|
210
|
+
<span className="flex items-center gap-1.5 text-muted-foreground">
|
|
211
|
+
<ShieldCheck className="h-3 w-3" weight="bold" />
|
|
212
|
+
<span>Custom auth</span>
|
|
213
|
+
</span>
|
|
214
|
+
{onUseGlobalAuth && (
|
|
215
|
+
<button
|
|
216
|
+
onClick={onUseGlobalAuth}
|
|
217
|
+
className="text-muted-foreground hover:text-foreground transition-colors"
|
|
218
|
+
>
|
|
219
|
+
Use global
|
|
220
|
+
</button>
|
|
221
|
+
)}
|
|
222
|
+
</>
|
|
223
|
+
)}
|
|
224
|
+
</div>
|
|
225
|
+
)}
|
|
226
|
+
|
|
227
|
+
<div className="flex-1 overflow-auto">
|
|
228
|
+
<AuthEditor
|
|
229
|
+
auth={auth}
|
|
230
|
+
onChange={onAuthChange}
|
|
231
|
+
showError={navState.highlightField?.type === 'auth' && navState.showError}
|
|
232
|
+
/>
|
|
233
|
+
</div>
|
|
234
|
+
</TabsContent>
|
|
235
|
+
</Tabs>
|
|
236
|
+
)
|
|
237
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { WifiHigh } from '@phosphor-icons/react'
|
|
4
|
+
import { Badge } from '@/components/ui/badge'
|
|
5
|
+
|
|
6
|
+
export function IdleCard() {
|
|
7
|
+
return (
|
|
8
|
+
<div className="flex flex-col h-full">
|
|
9
|
+
<div className="flex items-center justify-center flex-1 p-8">
|
|
10
|
+
<div className="text-center">
|
|
11
|
+
<WifiHigh className="h-16 w-16 text-muted-foreground/30 mx-auto mb-4" weight="thin" />
|
|
12
|
+
<p className="text-muted-foreground mb-2">Send a request to see the response</p>
|
|
13
|
+
<p className="text-sm text-muted-foreground/70">
|
|
14
|
+
Press <kbd className="px-2 py-1 bg-muted rounded text-xs font-mono">Enter</kbd> or click{' '}
|
|
15
|
+
<Badge variant="default" className="text-xs">Send</Badge>
|
|
16
|
+
</p>
|
|
17
|
+
</div>
|
|
18
|
+
</div>
|
|
19
|
+
</div>
|
|
20
|
+
)
|
|
21
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
// Export types
|
|
4
|
+
export * from './types'
|
|
5
|
+
|
|
6
|
+
// Export individual cards
|
|
7
|
+
export { IdleCard } from './idle-card'
|
|
8
|
+
export { LoadingCard } from './loading-card'
|
|
9
|
+
export { NetworkErrorCard } from './network-error-card'
|
|
10
|
+
export { ResponseBodyCard } from './response-body-card'
|
|
11
|
+
|
|
12
|
+
import { IdleCard } from './idle-card'
|
|
13
|
+
import { LoadingCard } from './loading-card'
|
|
14
|
+
import { NetworkErrorCard } from './network-error-card'
|
|
15
|
+
import { ResponseBodyCard } from './response-body-card'
|
|
16
|
+
import type { PlaygroundResponseState } from '@/lib/api-docs/playground/types'
|
|
17
|
+
import type { DebugContext } from '../response-viewer'
|
|
18
|
+
|
|
19
|
+
interface ResponseCardRendererProps {
|
|
20
|
+
responseState: PlaygroundResponseState
|
|
21
|
+
onDebugRequest?: (context: DebugContext) => void
|
|
22
|
+
onExplainRequest?: (context: DebugContext) => void
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Renders the appropriate response card based on the response state.
|
|
27
|
+
* This is the main entry point for response visualization.
|
|
28
|
+
*/
|
|
29
|
+
export function ResponseCardRenderer({ responseState, onDebugRequest, onExplainRequest }: ResponseCardRendererProps) {
|
|
30
|
+
// Idle state
|
|
31
|
+
if (responseState.type === 'idle') {
|
|
32
|
+
return <IdleCard />
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Loading state (but not streaming)
|
|
36
|
+
if (responseState.type === 'loading') {
|
|
37
|
+
return <LoadingCard />
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Error state with no response (network error)
|
|
41
|
+
if (responseState.type === 'error' && !responseState.response) {
|
|
42
|
+
return <NetworkErrorCard errorMessage={responseState.error} />
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Get response for success, streaming, or error with response
|
|
46
|
+
const response = responseState.response
|
|
47
|
+
if (!response) {
|
|
48
|
+
return null
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const isStreaming = responseState.type === 'streaming'
|
|
52
|
+
const isError = response.status >= 400
|
|
53
|
+
|
|
54
|
+
// Get content type
|
|
55
|
+
const contentType = response.headers['content-type'] || response.headers['Content-Type'] || null
|
|
56
|
+
|
|
57
|
+
// Build context for AI
|
|
58
|
+
const buildContext = () => ({
|
|
59
|
+
status: response.status,
|
|
60
|
+
statusText: response.statusText,
|
|
61
|
+
responseBody: typeof response.body === 'string' ? response.body : '[Binary content]',
|
|
62
|
+
errorMessage: response.error,
|
|
63
|
+
})
|
|
64
|
+
|
|
65
|
+
// Handle debug request (for errors)
|
|
66
|
+
const handleDebug = onDebugRequest ? () => {
|
|
67
|
+
onDebugRequest(buildContext())
|
|
68
|
+
} : undefined
|
|
69
|
+
|
|
70
|
+
// Handle explain request (for success)
|
|
71
|
+
const handleExplain = onExplainRequest ? () => {
|
|
72
|
+
onExplainRequest(buildContext())
|
|
73
|
+
} : undefined
|
|
74
|
+
|
|
75
|
+
return (
|
|
76
|
+
<ResponseBodyCard
|
|
77
|
+
meta={{
|
|
78
|
+
status: response.status,
|
|
79
|
+
statusText: response.statusText,
|
|
80
|
+
responseTime: response.responseTime,
|
|
81
|
+
size: response.size,
|
|
82
|
+
isStreaming: isStreaming || response.isStreaming,
|
|
83
|
+
}}
|
|
84
|
+
body={typeof response.body === 'string' ? response.body : '[Binary content]'}
|
|
85
|
+
headers={response.headers}
|
|
86
|
+
contentType={contentType}
|
|
87
|
+
isError={isError}
|
|
88
|
+
errorMessage={response.error}
|
|
89
|
+
onDebug={isError ? handleDebug : undefined}
|
|
90
|
+
onExplain={!isError ? handleExplain : undefined}
|
|
91
|
+
/>
|
|
92
|
+
)
|
|
93
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { Spinner } from '@phosphor-icons/react'
|
|
4
|
+
|
|
5
|
+
export function LoadingCard() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="flex flex-col h-full">
|
|
8
|
+
<div className="flex items-center justify-center flex-1 p-8">
|
|
9
|
+
<div className="text-center">
|
|
10
|
+
<Spinner size={32} className="text-muted-foreground animate-spin mx-auto" />
|
|
11
|
+
<p className="text-sm text-muted-foreground mt-3">Sending request...</p>
|
|
12
|
+
</div>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
)
|
|
16
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { WifiSlash } from '@phosphor-icons/react'
|
|
4
|
+
|
|
5
|
+
interface NetworkErrorCardProps {
|
|
6
|
+
errorMessage?: string
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export function NetworkErrorCard({ errorMessage }: NetworkErrorCardProps) {
|
|
10
|
+
return (
|
|
11
|
+
<div className="flex flex-col h-full">
|
|
12
|
+
<div className="flex items-center justify-center flex-1 p-8">
|
|
13
|
+
<div className="text-center">
|
|
14
|
+
<WifiSlash className="h-16 w-16 text-red-500/50 mx-auto mb-4" weight="thin" />
|
|
15
|
+
<p className="text-red-500 font-medium mb-2">Request Failed</p>
|
|
16
|
+
<p className="text-sm text-muted-foreground max-w-md">
|
|
17
|
+
{errorMessage || 'An error occurred while sending the request'}
|
|
18
|
+
</p>
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
21
|
+
</div>
|
|
22
|
+
)
|
|
23
|
+
}
|
|
@@ -0,0 +1,268 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useMemo, useCallback } from 'react'
|
|
4
|
+
import { Copy, Check, Clock, Database, WarningCircle, Lightning, Bug, Lightbulb } from '@phosphor-icons/react'
|
|
5
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
|
|
6
|
+
import { Badge } from '@/components/ui/badge'
|
|
7
|
+
import { Button } from '@/components/ui/button'
|
|
8
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
|
9
|
+
import { CodeViewer, SupportedLanguage } from '../code-editor'
|
|
10
|
+
import type { ResponseMeta } from './types'
|
|
11
|
+
|
|
12
|
+
interface ResponseBodyCardProps {
|
|
13
|
+
meta: ResponseMeta
|
|
14
|
+
body: string
|
|
15
|
+
headers: Record<string, string>
|
|
16
|
+
contentType: string | null
|
|
17
|
+
isError?: boolean
|
|
18
|
+
errorMessage?: string
|
|
19
|
+
onDebug?: () => void
|
|
20
|
+
onExplain?: () => void
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Detect language from content type
|
|
24
|
+
function detectLanguage(contentType: string | null, body?: string): SupportedLanguage {
|
|
25
|
+
if (!contentType) return 'plaintext'
|
|
26
|
+
|
|
27
|
+
if (contentType.includes('json')) return 'json'
|
|
28
|
+
if (contentType.includes('xml')) return 'xml'
|
|
29
|
+
if (contentType.includes('html')) return 'html'
|
|
30
|
+
if (contentType.includes('javascript')) return 'javascript'
|
|
31
|
+
if (contentType.includes('typescript')) return 'typescript'
|
|
32
|
+
if (contentType.includes('event-stream')) return 'json'
|
|
33
|
+
if (body && body.trim().startsWith('{')) return 'json'
|
|
34
|
+
|
|
35
|
+
return 'plaintext'
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function formatSize(bytes: number): string {
|
|
39
|
+
if (bytes < 1024) return `${bytes} B`
|
|
40
|
+
if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)} KB`
|
|
41
|
+
return `${(bytes / (1024 * 1024)).toFixed(2)} MB`
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Format response body - handles both SSE and regular JSON
|
|
45
|
+
function formatBody(body: string, contentType: string | null, isSSE: boolean): string {
|
|
46
|
+
if (!body) return ''
|
|
47
|
+
|
|
48
|
+
// Check if this is SSE format
|
|
49
|
+
if (isSSE || body.includes('data: ')) {
|
|
50
|
+
const lines = body.split(/\r?\n/).filter(line => line.trim())
|
|
51
|
+
const events: string[] = []
|
|
52
|
+
|
|
53
|
+
for (const line of lines) {
|
|
54
|
+
if (line.startsWith('data: ')) {
|
|
55
|
+
const data = line.slice(6)
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(data)
|
|
58
|
+
events.push(JSON.stringify(parsed, null, 2))
|
|
59
|
+
} catch {
|
|
60
|
+
events.push(data)
|
|
61
|
+
}
|
|
62
|
+
} else {
|
|
63
|
+
events.push(line)
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
return events.join('\n')
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Regular JSON - try to parse and format
|
|
71
|
+
if (contentType?.includes('json')) {
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(body)
|
|
74
|
+
return JSON.stringify(parsed, null, 2)
|
|
75
|
+
} catch {
|
|
76
|
+
return body
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return body
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function ResponseBodyCard({
|
|
84
|
+
meta,
|
|
85
|
+
body,
|
|
86
|
+
headers,
|
|
87
|
+
contentType,
|
|
88
|
+
isError = false,
|
|
89
|
+
errorMessage,
|
|
90
|
+
onDebug,
|
|
91
|
+
onExplain
|
|
92
|
+
}: ResponseBodyCardProps) {
|
|
93
|
+
const [copied, setCopied] = useState(false)
|
|
94
|
+
|
|
95
|
+
const isSSE = contentType?.includes('event-stream')
|
|
96
|
+
const detectedLanguage = useMemo(() => detectLanguage(contentType, body), [contentType, body])
|
|
97
|
+
const formattedBody = useMemo(() => formatBody(body, contentType, !!isSSE), [body, contentType, isSSE])
|
|
98
|
+
const headersCount = Object.keys(headers).length
|
|
99
|
+
|
|
100
|
+
const copyToClipboard = useCallback((text: string) => {
|
|
101
|
+
navigator.clipboard.writeText(text)
|
|
102
|
+
setCopied(true)
|
|
103
|
+
setTimeout(() => setCopied(false), 2000)
|
|
104
|
+
}, [])
|
|
105
|
+
|
|
106
|
+
const statusVariant = isError ? 'destructive' : 'default'
|
|
107
|
+
|
|
108
|
+
return (
|
|
109
|
+
<TooltipProvider>
|
|
110
|
+
<div className="flex flex-col h-full">
|
|
111
|
+
{/* Response Meta */}
|
|
112
|
+
<div className="sticky top-0 z-10 flex items-center justify-between bg-muted/50 px-4 py-3 border-b border-border">
|
|
113
|
+
<div className="flex items-center gap-4 text-sm">
|
|
114
|
+
{/* Status */}
|
|
115
|
+
<Badge variant={statusVariant} className="font-semibold">
|
|
116
|
+
{meta.status} {meta.statusText}
|
|
117
|
+
</Badge>
|
|
118
|
+
|
|
119
|
+
{/* Time */}
|
|
120
|
+
<span className="flex items-center gap-1.5 text-muted-foreground">
|
|
121
|
+
<Clock className="h-4 w-4" weight="bold" />
|
|
122
|
+
<span>{meta.responseTime}ms</span>
|
|
123
|
+
</span>
|
|
124
|
+
|
|
125
|
+
{/* Size */}
|
|
126
|
+
<span className="flex items-center gap-1.5 text-muted-foreground">
|
|
127
|
+
<Database className="h-4 w-4" weight="bold" />
|
|
128
|
+
<span>{formatSize(meta.size)}</span>
|
|
129
|
+
</span>
|
|
130
|
+
|
|
131
|
+
{/* Streaming indicator */}
|
|
132
|
+
{meta.isStreaming && (
|
|
133
|
+
<span className="flex items-center gap-1.5 text-amber-600 dark:text-amber-400">
|
|
134
|
+
<Lightning className="h-4 w-4 animate-pulse" weight="fill" />
|
|
135
|
+
<span>SSE</span>
|
|
136
|
+
</span>
|
|
137
|
+
)}
|
|
138
|
+
</div>
|
|
139
|
+
|
|
140
|
+
<div className="flex items-center gap-2">
|
|
141
|
+
{errorMessage && (
|
|
142
|
+
<span className="flex items-center gap-1.5 text-red-500 text-sm">
|
|
143
|
+
<WarningCircle className="h-4 w-4" weight="fill" />
|
|
144
|
+
Error
|
|
145
|
+
</span>
|
|
146
|
+
)}
|
|
147
|
+
|
|
148
|
+
{/* Copy button */}
|
|
149
|
+
{formattedBody && (
|
|
150
|
+
<Tooltip>
|
|
151
|
+
<TooltipTrigger asChild>
|
|
152
|
+
<Button
|
|
153
|
+
variant="ghost"
|
|
154
|
+
size="icon"
|
|
155
|
+
className="h-8 w-8"
|
|
156
|
+
onClick={() => copyToClipboard(formattedBody)}
|
|
157
|
+
>
|
|
158
|
+
{copied ? (
|
|
159
|
+
<Check className="h-4 w-4 text-green-500" weight="bold" />
|
|
160
|
+
) : (
|
|
161
|
+
<Copy className="h-4 w-4" weight="bold" />
|
|
162
|
+
)}
|
|
163
|
+
</Button>
|
|
164
|
+
</TooltipTrigger>
|
|
165
|
+
<TooltipContent>{copied ? 'Copied!' : 'Copy response'}</TooltipContent>
|
|
166
|
+
</Tooltip>
|
|
167
|
+
)}
|
|
168
|
+
</div>
|
|
169
|
+
</div>
|
|
170
|
+
|
|
171
|
+
{/* Tabs */}
|
|
172
|
+
<Tabs defaultValue="body" className="flex flex-col flex-1 gap-0">
|
|
173
|
+
<TabsList className="w-full justify-start rounded-none border-b bg-background h-auto p-0">
|
|
174
|
+
<TabsTrigger
|
|
175
|
+
value="body"
|
|
176
|
+
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary px-4 py-3"
|
|
177
|
+
>
|
|
178
|
+
Body
|
|
179
|
+
</TabsTrigger>
|
|
180
|
+
<TabsTrigger
|
|
181
|
+
value="headers"
|
|
182
|
+
className="rounded-none border-b-2 border-transparent data-[state=active]:border-primary px-4 py-3"
|
|
183
|
+
>
|
|
184
|
+
Headers
|
|
185
|
+
<Badge variant="secondary" className="ml-2 h-5 px-1.5">
|
|
186
|
+
{headersCount}
|
|
187
|
+
</Badge>
|
|
188
|
+
</TabsTrigger>
|
|
189
|
+
</TabsList>
|
|
190
|
+
|
|
191
|
+
<TabsContent value="body" className="flex-1 m-0 overflow-hidden relative">
|
|
192
|
+
{formattedBody ? (
|
|
193
|
+
<CodeViewer
|
|
194
|
+
value={formattedBody}
|
|
195
|
+
language={detectedLanguage}
|
|
196
|
+
height="100%"
|
|
197
|
+
minHeight={200}
|
|
198
|
+
theme="dark"
|
|
199
|
+
showLineNumbers={false}
|
|
200
|
+
showBorder={false}
|
|
201
|
+
rounded={false}
|
|
202
|
+
/>
|
|
203
|
+
) : (
|
|
204
|
+
<div className="flex items-center justify-center h-full text-muted-foreground">
|
|
205
|
+
No response body
|
|
206
|
+
</div>
|
|
207
|
+
)}
|
|
208
|
+
|
|
209
|
+
{/* Action buttons - overlay on code viewer */}
|
|
210
|
+
<div className="absolute top-2.5 right-3 z-20 flex items-center gap-2">
|
|
211
|
+
{/* Explain button - for success responses */}
|
|
212
|
+
{!isError && onExplain && (
|
|
213
|
+
<Tooltip>
|
|
214
|
+
<TooltipTrigger asChild>
|
|
215
|
+
<button
|
|
216
|
+
className="flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-white/70 bg-white/10 hover:bg-white/20 hover:text-white transition-colors rounded-md backdrop-blur-sm"
|
|
217
|
+
onClick={onExplain}
|
|
218
|
+
>
|
|
219
|
+
<Lightbulb className="h-3.5 w-3.5" weight="bold" />
|
|
220
|
+
<span>Explain</span>
|
|
221
|
+
</button>
|
|
222
|
+
</TooltipTrigger>
|
|
223
|
+
<TooltipContent side="left">Ask AI to explain this response</TooltipContent>
|
|
224
|
+
</Tooltip>
|
|
225
|
+
)}
|
|
226
|
+
|
|
227
|
+
{/* Debug button - for error responses */}
|
|
228
|
+
{isError && onDebug && (
|
|
229
|
+
<Tooltip>
|
|
230
|
+
<TooltipTrigger asChild>
|
|
231
|
+
<button
|
|
232
|
+
className="flex items-center gap-1.5 px-2.5 py-1.5 text-xs font-medium text-white/70 bg-white/10 hover:bg-white/20 hover:text-white transition-colors rounded-md backdrop-blur-sm"
|
|
233
|
+
onClick={onDebug}
|
|
234
|
+
>
|
|
235
|
+
<Bug className="h-3.5 w-3.5" weight="bold" />
|
|
236
|
+
<span>Debug</span>
|
|
237
|
+
</button>
|
|
238
|
+
</TooltipTrigger>
|
|
239
|
+
<TooltipContent side="left">Ask AI to help debug this error</TooltipContent>
|
|
240
|
+
</Tooltip>
|
|
241
|
+
)}
|
|
242
|
+
</div>
|
|
243
|
+
</TabsContent>
|
|
244
|
+
|
|
245
|
+
<TabsContent value="headers" className="flex-1 m-0 overflow-auto">
|
|
246
|
+
<div className="divide-y divide-border">
|
|
247
|
+
{Object.entries(headers).map(([key, value]) => (
|
|
248
|
+
<div key={key} className="flex items-start gap-4 px-4 py-3">
|
|
249
|
+
<span className="font-mono text-sm font-semibold text-foreground min-w-[180px]">
|
|
250
|
+
{key}
|
|
251
|
+
</span>
|
|
252
|
+
<span className="font-mono text-sm text-muted-foreground break-all">
|
|
253
|
+
{value}
|
|
254
|
+
</span>
|
|
255
|
+
</div>
|
|
256
|
+
))}
|
|
257
|
+
{headersCount === 0 && (
|
|
258
|
+
<div className="flex items-center justify-center p-8 text-muted-foreground">
|
|
259
|
+
No response headers
|
|
260
|
+
</div>
|
|
261
|
+
)}
|
|
262
|
+
</div>
|
|
263
|
+
</TabsContent>
|
|
264
|
+
</Tabs>
|
|
265
|
+
</div>
|
|
266
|
+
</TooltipProvider>
|
|
267
|
+
)
|
|
268
|
+
}
|