@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,671 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
import { useState, useCallback, useEffect, useMemo, useRef } from 'react'
|
|
4
|
+
import { PaperPlaneRight, X, ArrowCounterClockwise, ShieldCheck, ShieldWarning } from '@phosphor-icons/react'
|
|
5
|
+
import { Button } from '@/components/ui/button'
|
|
6
|
+
import { Input } from '@/components/ui/input'
|
|
7
|
+
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip'
|
|
8
|
+
import type { BrainfishRESTRequest, BrainfishRESTReqBody, BrainfishRESTAuth, HTTPMethod } from '@/lib/api-docs/types'
|
|
9
|
+
import { MethodSelector } from './method-selector'
|
|
10
|
+
import { RequestTabs } from './request-tabs'
|
|
11
|
+
import { ResponseViewer, type DebugContext } from './response-viewer'
|
|
12
|
+
import type { PlaygroundResponseState } from '@/lib/api-docs/playground/types'
|
|
13
|
+
import { createCancelableRequest, createCancelableStreamingRequest } from '@/lib/api-docs/playground/request-runner'
|
|
14
|
+
import type { KeyValueItem } from './key-value-editor'
|
|
15
|
+
import { useAuth, getAuthTypeLabel } from '@/lib/api-docs/auth'
|
|
16
|
+
import { usePlaygroundPrefill, useCurrentRequestPayload, useSendTrigger } from '@/lib/api-docs/playground/context'
|
|
17
|
+
|
|
18
|
+
// LocalStorage key prefix for playground state
|
|
19
|
+
const PLAYGROUND_STORAGE_KEY = 'brainfish-playground-'
|
|
20
|
+
|
|
21
|
+
interface PlaygroundStoredState {
|
|
22
|
+
body?: string | null
|
|
23
|
+
headers?: KeyValueItem[]
|
|
24
|
+
params?: KeyValueItem[]
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Helper to get storage key for a request
|
|
28
|
+
function getStorageKey(request: BrainfishRESTRequest): string {
|
|
29
|
+
const id = request.name || request.endpoint
|
|
30
|
+
return `${PLAYGROUND_STORAGE_KEY}${btoa(id).replace(/[^a-zA-Z0-9]/g, '')}`
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Load stored state from localStorage
|
|
34
|
+
function loadStoredState(request: BrainfishRESTRequest): PlaygroundStoredState | null {
|
|
35
|
+
if (typeof window === 'undefined') return null
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
const key = getStorageKey(request)
|
|
39
|
+
const stored = localStorage.getItem(key)
|
|
40
|
+
if (stored) {
|
|
41
|
+
return JSON.parse(stored)
|
|
42
|
+
}
|
|
43
|
+
} catch (e) {
|
|
44
|
+
console.warn('[Playground] Failed to load stored state:', e)
|
|
45
|
+
}
|
|
46
|
+
return null
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Save state to localStorage
|
|
50
|
+
function saveStoredState(request: BrainfishRESTRequest, state: PlaygroundStoredState): void {
|
|
51
|
+
if (typeof window === 'undefined') return
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const key = getStorageKey(request)
|
|
55
|
+
localStorage.setItem(key, JSON.stringify(state))
|
|
56
|
+
} catch (e) {
|
|
57
|
+
console.warn('[Playground] Failed to save state:', e)
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Clear stored state
|
|
62
|
+
function clearStoredState(request: BrainfishRESTRequest): void {
|
|
63
|
+
if (typeof window === 'undefined') return
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const key = getStorageKey(request)
|
|
67
|
+
localStorage.removeItem(key)
|
|
68
|
+
} catch (e) {
|
|
69
|
+
console.warn('[Playground] Failed to clear state:', e)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
interface ApiPlaygroundProps {
|
|
74
|
+
request: BrainfishRESTRequest
|
|
75
|
+
onDebugRequest?: (context: DebugContext) => void
|
|
76
|
+
onExplainRequest?: (context: DebugContext) => void
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function ApiPlayground({ request, onDebugRequest, onExplainRequest }: ApiPlaygroundProps) {
|
|
80
|
+
// Global auth - takes priority over endpoint-specific auth
|
|
81
|
+
const { globalAuth } = useAuth()
|
|
82
|
+
|
|
83
|
+
// Agent prefill context
|
|
84
|
+
const { pendingPrefill, clearPrefill } = usePlaygroundPrefill()
|
|
85
|
+
|
|
86
|
+
// Current request payload context - share with agent
|
|
87
|
+
const { setCurrentRequestPayload } = useCurrentRequestPayload()
|
|
88
|
+
|
|
89
|
+
// Send trigger from agent
|
|
90
|
+
const { triggerSend, clearSendTrigger } = useSendTrigger()
|
|
91
|
+
|
|
92
|
+
// Track if we've loaded from localStorage
|
|
93
|
+
const hasLoadedRef = useRef(false)
|
|
94
|
+
|
|
95
|
+
// Request state - initialize with defaults, will load from localStorage in useEffect
|
|
96
|
+
const [method, setMethod] = useState<HTTPMethod>(request.method)
|
|
97
|
+
const [endpoint, setEndpoint] = useState(request.endpoint)
|
|
98
|
+
|
|
99
|
+
// Path variables (for URL path params like {id} or <<id>>)
|
|
100
|
+
const [pathVariables, setPathVariables] = useState<KeyValueItem[]>(
|
|
101
|
+
request.requestVariables.map((v, i) => ({
|
|
102
|
+
id: `pathvar_${i}`,
|
|
103
|
+
key: v.key,
|
|
104
|
+
value: v.value,
|
|
105
|
+
active: true,
|
|
106
|
+
description: '',
|
|
107
|
+
}))
|
|
108
|
+
)
|
|
109
|
+
|
|
110
|
+
// Store the original endpoint template for path variable substitution
|
|
111
|
+
const endpointTemplateRef = useRef(request.endpoint)
|
|
112
|
+
|
|
113
|
+
const [params, setParams] = useState<KeyValueItem[]>(
|
|
114
|
+
request.params.map((p, i) => ({
|
|
115
|
+
id: `param_${i}`,
|
|
116
|
+
key: p.key,
|
|
117
|
+
value: p.value,
|
|
118
|
+
active: p.active,
|
|
119
|
+
description: p.description,
|
|
120
|
+
}))
|
|
121
|
+
)
|
|
122
|
+
const [headers, setHeaders] = useState<KeyValueItem[]>(
|
|
123
|
+
request.headers.map((h, i) => ({
|
|
124
|
+
id: `header_${i}`,
|
|
125
|
+
key: h.key,
|
|
126
|
+
value: h.value,
|
|
127
|
+
active: h.active,
|
|
128
|
+
description: h.description,
|
|
129
|
+
}))
|
|
130
|
+
)
|
|
131
|
+
const [body, setBody] = useState<string | null>(
|
|
132
|
+
typeof request.body.body === 'string' ? request.body.body : null
|
|
133
|
+
)
|
|
134
|
+
const [bodyContentType, setBodyContentType] = useState<BrainfishRESTReqBody['contentType']>(
|
|
135
|
+
request.body.contentType
|
|
136
|
+
)
|
|
137
|
+
// Track if user has overridden global auth for this endpoint
|
|
138
|
+
const [authOverride, setAuthOverride] = useState<BrainfishRESTAuth | null>(null)
|
|
139
|
+
|
|
140
|
+
// Update endpoint URL when path variables change
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
let newEndpoint = endpointTemplateRef.current
|
|
143
|
+
pathVariables.forEach((variable) => {
|
|
144
|
+
if (variable.value) {
|
|
145
|
+
// Replace <<variable>> pattern
|
|
146
|
+
newEndpoint = newEndpoint.replace(new RegExp(`<<${variable.key}>>`, 'g'), variable.value)
|
|
147
|
+
// Replace {variable} pattern
|
|
148
|
+
newEndpoint = newEndpoint.replace(new RegExp(`\\{${variable.key}\\}`, 'g'), variable.value)
|
|
149
|
+
// Replace URL-encoded patterns
|
|
150
|
+
newEndpoint = newEndpoint.replace(new RegExp(`%3C%3C${variable.key}%3E%3E`, 'gi'), variable.value)
|
|
151
|
+
}
|
|
152
|
+
})
|
|
153
|
+
setEndpoint(newEndpoint)
|
|
154
|
+
}, [pathVariables])
|
|
155
|
+
|
|
156
|
+
// Response state
|
|
157
|
+
const [responseState, setResponseState] = useState<PlaygroundResponseState>({ type: 'idle' })
|
|
158
|
+
const [cancelFunc, setCancelFunc] = useState<(() => void) | null>(null)
|
|
159
|
+
|
|
160
|
+
// Save state to localStorage when values change (debounced)
|
|
161
|
+
useEffect(() => {
|
|
162
|
+
if (!hasLoadedRef.current) return // Don't save before initial load
|
|
163
|
+
|
|
164
|
+
const timeoutId = setTimeout(() => {
|
|
165
|
+
saveStoredState(request, {
|
|
166
|
+
body,
|
|
167
|
+
headers,
|
|
168
|
+
params,
|
|
169
|
+
})
|
|
170
|
+
}, 500) // Debounce 500ms
|
|
171
|
+
|
|
172
|
+
return () => clearTimeout(timeoutId)
|
|
173
|
+
}, [request, body, headers, params])
|
|
174
|
+
|
|
175
|
+
// Update current request payload context for agent access
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
setCurrentRequestPayload({
|
|
178
|
+
method,
|
|
179
|
+
endpoint,
|
|
180
|
+
params: params.filter(p => p.key).map(p => ({
|
|
181
|
+
key: p.key,
|
|
182
|
+
value: p.value,
|
|
183
|
+
active: p.active,
|
|
184
|
+
})),
|
|
185
|
+
headers: headers.filter(h => h.key).map(h => ({
|
|
186
|
+
key: h.key,
|
|
187
|
+
value: h.value,
|
|
188
|
+
active: h.active,
|
|
189
|
+
})),
|
|
190
|
+
body,
|
|
191
|
+
bodyContentType,
|
|
192
|
+
})
|
|
193
|
+
|
|
194
|
+
// Cleanup on unmount
|
|
195
|
+
return () => setCurrentRequestPayload(null)
|
|
196
|
+
}, [method, endpoint, params, headers, body, bodyContentType, setCurrentRequestPayload])
|
|
197
|
+
|
|
198
|
+
// Determine effective auth: override > global > endpoint default
|
|
199
|
+
// Global auth takes priority when available, unless user explicitly overrides
|
|
200
|
+
const effectiveAuth = useMemo(() => {
|
|
201
|
+
if (authOverride) {
|
|
202
|
+
return authOverride
|
|
203
|
+
}
|
|
204
|
+
// If global auth is configured, use it by default
|
|
205
|
+
if (globalAuth && globalAuth.authType !== 'none') {
|
|
206
|
+
return globalAuth
|
|
207
|
+
}
|
|
208
|
+
// Fall back to endpoint's own auth
|
|
209
|
+
return request.auth
|
|
210
|
+
}, [authOverride, globalAuth, request.auth])
|
|
211
|
+
|
|
212
|
+
// Check if currently using global auth (global is set and no override)
|
|
213
|
+
const usingGlobalAuth = !authOverride && globalAuth !== null && globalAuth.authType !== 'none'
|
|
214
|
+
|
|
215
|
+
// Reset state when request changes - but load from localStorage if available
|
|
216
|
+
useEffect(() => {
|
|
217
|
+
// Load stored state from localStorage
|
|
218
|
+
const stored = loadStoredState(request)
|
|
219
|
+
|
|
220
|
+
setMethod(request.method)
|
|
221
|
+
setEndpoint(request.endpoint)
|
|
222
|
+
|
|
223
|
+
// Update endpoint template reference
|
|
224
|
+
endpointTemplateRef.current = request.endpoint
|
|
225
|
+
|
|
226
|
+
// Reset path variables from request
|
|
227
|
+
setPathVariables(
|
|
228
|
+
request.requestVariables.map((v, i) => ({
|
|
229
|
+
id: `pathvar_${i}`,
|
|
230
|
+
key: v.key,
|
|
231
|
+
value: v.value,
|
|
232
|
+
active: true,
|
|
233
|
+
description: '',
|
|
234
|
+
}))
|
|
235
|
+
)
|
|
236
|
+
|
|
237
|
+
// Use stored params if available, otherwise use request defaults
|
|
238
|
+
if (stored?.params && stored.params.length > 0) {
|
|
239
|
+
setParams(stored.params)
|
|
240
|
+
} else {
|
|
241
|
+
setParams(
|
|
242
|
+
request.params.map((p, i) => ({
|
|
243
|
+
id: `param_${i}`,
|
|
244
|
+
key: p.key,
|
|
245
|
+
value: p.value,
|
|
246
|
+
active: p.active,
|
|
247
|
+
description: p.description,
|
|
248
|
+
}))
|
|
249
|
+
)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Use stored headers if available, otherwise use request defaults
|
|
253
|
+
if (stored?.headers && stored.headers.length > 0) {
|
|
254
|
+
setHeaders(stored.headers)
|
|
255
|
+
} else {
|
|
256
|
+
setHeaders(
|
|
257
|
+
request.headers.map((h, i) => ({
|
|
258
|
+
id: `header_${i}`,
|
|
259
|
+
key: h.key,
|
|
260
|
+
value: h.value,
|
|
261
|
+
active: h.active,
|
|
262
|
+
description: h.description,
|
|
263
|
+
}))
|
|
264
|
+
)
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
// Use stored body if available, otherwise use request default
|
|
268
|
+
if (stored?.body !== undefined) {
|
|
269
|
+
setBody(stored.body)
|
|
270
|
+
} else {
|
|
271
|
+
setBody(typeof request.body.body === 'string' ? request.body.body : null)
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
setBodyContentType(request.body.contentType)
|
|
275
|
+
setAuthOverride(null) // Reset override when changing endpoints
|
|
276
|
+
setResponseState({ type: 'idle' })
|
|
277
|
+
|
|
278
|
+
// Mark as loaded so saving can start
|
|
279
|
+
hasLoadedRef.current = true
|
|
280
|
+
}, [request])
|
|
281
|
+
|
|
282
|
+
// Apply prefill data from agent
|
|
283
|
+
useEffect(() => {
|
|
284
|
+
if (!pendingPrefill) return
|
|
285
|
+
|
|
286
|
+
console.log('[Playground] Applying prefill:', pendingPrefill)
|
|
287
|
+
|
|
288
|
+
// Apply params
|
|
289
|
+
if (pendingPrefill.params && pendingPrefill.params.length > 0) {
|
|
290
|
+
setParams(prev => {
|
|
291
|
+
const updated = [...prev]
|
|
292
|
+
pendingPrefill.params!.forEach(prefillParam => {
|
|
293
|
+
const existing = updated.find(p => p.key === prefillParam.key)
|
|
294
|
+
if (existing) {
|
|
295
|
+
existing.value = prefillParam.value
|
|
296
|
+
existing.active = true
|
|
297
|
+
} else {
|
|
298
|
+
updated.push({
|
|
299
|
+
id: `param_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
300
|
+
key: prefillParam.key,
|
|
301
|
+
value: prefillParam.value,
|
|
302
|
+
active: true,
|
|
303
|
+
description: '',
|
|
304
|
+
})
|
|
305
|
+
}
|
|
306
|
+
})
|
|
307
|
+
return updated
|
|
308
|
+
})
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// Apply headers
|
|
312
|
+
if (pendingPrefill.headers && pendingPrefill.headers.length > 0) {
|
|
313
|
+
setHeaders(prev => {
|
|
314
|
+
const updated = [...prev]
|
|
315
|
+
pendingPrefill.headers!.forEach(prefillHeader => {
|
|
316
|
+
const existing = updated.find(h => h.key === prefillHeader.key)
|
|
317
|
+
if (existing) {
|
|
318
|
+
existing.value = prefillHeader.value
|
|
319
|
+
existing.active = true
|
|
320
|
+
} else {
|
|
321
|
+
updated.push({
|
|
322
|
+
id: `header_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
323
|
+
key: prefillHeader.key,
|
|
324
|
+
value: prefillHeader.value,
|
|
325
|
+
active: true,
|
|
326
|
+
description: '',
|
|
327
|
+
})
|
|
328
|
+
}
|
|
329
|
+
})
|
|
330
|
+
return updated
|
|
331
|
+
})
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Apply body
|
|
335
|
+
if (pendingPrefill.body) {
|
|
336
|
+
setBody(pendingPrefill.body)
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Apply path variables (for URL path params like {id})
|
|
340
|
+
if (pendingPrefill.pathVariables && pendingPrefill.pathVariables.length > 0) {
|
|
341
|
+
setPathVariables(prev => {
|
|
342
|
+
const updated = [...prev]
|
|
343
|
+
pendingPrefill.pathVariables!.forEach(prefillVar => {
|
|
344
|
+
const existing = updated.find(v => v.key === prefillVar.key)
|
|
345
|
+
if (existing) {
|
|
346
|
+
existing.value = prefillVar.value
|
|
347
|
+
} else {
|
|
348
|
+
updated.push({
|
|
349
|
+
id: `pathvar_${Date.now()}_${Math.random().toString(36).slice(2)}`,
|
|
350
|
+
key: prefillVar.key,
|
|
351
|
+
value: prefillVar.value,
|
|
352
|
+
active: true,
|
|
353
|
+
description: '',
|
|
354
|
+
})
|
|
355
|
+
}
|
|
356
|
+
})
|
|
357
|
+
return updated
|
|
358
|
+
})
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Clear the prefill after applying
|
|
362
|
+
clearPrefill()
|
|
363
|
+
}, [pendingPrefill, clearPrefill])
|
|
364
|
+
|
|
365
|
+
// Handle send trigger from agent
|
|
366
|
+
useEffect(() => {
|
|
367
|
+
if (triggerSend) {
|
|
368
|
+
// Clear the trigger immediately
|
|
369
|
+
clearSendTrigger()
|
|
370
|
+
// Trigger the send (handleSend is called below)
|
|
371
|
+
// We need to call it after this effect, so we use a timeout
|
|
372
|
+
setTimeout(() => {
|
|
373
|
+
const sendButton = document.querySelector('[data-send-button]') as HTMLButtonElement
|
|
374
|
+
if (sendButton) {
|
|
375
|
+
sendButton.click()
|
|
376
|
+
}
|
|
377
|
+
}, 0)
|
|
378
|
+
}
|
|
379
|
+
}, [triggerSend, clearSendTrigger])
|
|
380
|
+
|
|
381
|
+
const handleSend = useCallback(async () => {
|
|
382
|
+
if (!endpoint.trim()) {
|
|
383
|
+
return
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
setResponseState({ type: 'loading' })
|
|
387
|
+
|
|
388
|
+
// Always use streaming proxy - it auto-detects SSE from response content-type
|
|
389
|
+
// and falls back to regular JSON handling for non-streaming responses
|
|
390
|
+
const shouldStream = true
|
|
391
|
+
|
|
392
|
+
// Build request object - use effectiveAuth for authentication
|
|
393
|
+
const requestToSend: BrainfishRESTRequest = {
|
|
394
|
+
...request,
|
|
395
|
+
method,
|
|
396
|
+
endpoint,
|
|
397
|
+
params: params.filter((p) => p.key).map((p) => ({
|
|
398
|
+
key: p.key,
|
|
399
|
+
value: p.value,
|
|
400
|
+
active: p.active,
|
|
401
|
+
description: p.description || '',
|
|
402
|
+
})),
|
|
403
|
+
headers: headers.filter((h) => h.key).map((h) => ({
|
|
404
|
+
key: h.key,
|
|
405
|
+
value: h.value,
|
|
406
|
+
active: h.active,
|
|
407
|
+
description: h.description || '',
|
|
408
|
+
})),
|
|
409
|
+
body: bodyContentType
|
|
410
|
+
? { contentType: bodyContentType, body: body || '' } as BrainfishRESTReqBody
|
|
411
|
+
: { contentType: null, body: null },
|
|
412
|
+
auth: effectiveAuth,
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const requestOptions = {
|
|
416
|
+
params: params.filter((p) => p.key).map((p) => ({
|
|
417
|
+
key: p.key,
|
|
418
|
+
value: p.value,
|
|
419
|
+
active: p.active,
|
|
420
|
+
})),
|
|
421
|
+
headers: headers.filter((h) => h.key).map((h) => ({
|
|
422
|
+
key: h.key,
|
|
423
|
+
value: h.value,
|
|
424
|
+
active: h.active,
|
|
425
|
+
})),
|
|
426
|
+
body,
|
|
427
|
+
variables: [],
|
|
428
|
+
// Streaming callbacks
|
|
429
|
+
onChunk: shouldStream ? (chunk: string, accumulated: string) => {
|
|
430
|
+
setResponseState(prev => {
|
|
431
|
+
if (prev.type === 'streaming' || prev.type === 'loading') {
|
|
432
|
+
return {
|
|
433
|
+
type: 'streaming',
|
|
434
|
+
response: {
|
|
435
|
+
...(prev.type === 'streaming' ? prev.response : {
|
|
436
|
+
status: 0,
|
|
437
|
+
statusText: 'Streaming...',
|
|
438
|
+
headers: {},
|
|
439
|
+
responseTime: 0,
|
|
440
|
+
size: 0,
|
|
441
|
+
}),
|
|
442
|
+
body: accumulated,
|
|
443
|
+
size: new Blob([accumulated]).size,
|
|
444
|
+
isStreaming: true,
|
|
445
|
+
},
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
return prev
|
|
449
|
+
})
|
|
450
|
+
} : undefined,
|
|
451
|
+
onStreamStart: shouldStream ? (metadata: { status: number; statusText: string; headers: Record<string, string> }) => {
|
|
452
|
+
setResponseState({
|
|
453
|
+
type: 'streaming',
|
|
454
|
+
response: {
|
|
455
|
+
status: metadata.status,
|
|
456
|
+
statusText: metadata.statusText,
|
|
457
|
+
headers: metadata.headers,
|
|
458
|
+
body: '',
|
|
459
|
+
responseTime: 0,
|
|
460
|
+
size: 0,
|
|
461
|
+
isStreaming: true,
|
|
462
|
+
},
|
|
463
|
+
})
|
|
464
|
+
} : undefined,
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
const createRequest = shouldStream ? createCancelableStreamingRequest : createCancelableRequest
|
|
468
|
+
const { execute, cancel } = createRequest(requestToSend, requestOptions)
|
|
469
|
+
|
|
470
|
+
setCancelFunc(() => cancel)
|
|
471
|
+
|
|
472
|
+
try {
|
|
473
|
+
const response = await execute()
|
|
474
|
+
if (response.error) {
|
|
475
|
+
setResponseState({
|
|
476
|
+
type: 'error',
|
|
477
|
+
response,
|
|
478
|
+
error: response.error,
|
|
479
|
+
})
|
|
480
|
+
} else {
|
|
481
|
+
setResponseState({
|
|
482
|
+
type: 'success',
|
|
483
|
+
response,
|
|
484
|
+
})
|
|
485
|
+
}
|
|
486
|
+
} catch (error) {
|
|
487
|
+
setResponseState({
|
|
488
|
+
type: 'error',
|
|
489
|
+
error: error instanceof Error ? error.message : 'Request failed',
|
|
490
|
+
})
|
|
491
|
+
} finally {
|
|
492
|
+
setCancelFunc(null)
|
|
493
|
+
}
|
|
494
|
+
}, [request, method, endpoint, params, headers, body, bodyContentType, effectiveAuth])
|
|
495
|
+
|
|
496
|
+
const handleCancel = useCallback(() => {
|
|
497
|
+
if (cancelFunc) {
|
|
498
|
+
cancelFunc()
|
|
499
|
+
setCancelFunc(null)
|
|
500
|
+
setResponseState({ type: 'idle' })
|
|
501
|
+
}
|
|
502
|
+
}, [cancelFunc])
|
|
503
|
+
|
|
504
|
+
const handleClear = useCallback(() => {
|
|
505
|
+
// Reset to original request values
|
|
506
|
+
setEndpoint(request.endpoint)
|
|
507
|
+
setParams(request.params.map((p, i) => ({
|
|
508
|
+
id: `param_${i}`,
|
|
509
|
+
key: p.key,
|
|
510
|
+
value: p.value,
|
|
511
|
+
active: p.active,
|
|
512
|
+
description: p.description,
|
|
513
|
+
})))
|
|
514
|
+
setHeaders(request.headers.map((h, i) => ({
|
|
515
|
+
id: `header_${i}`,
|
|
516
|
+
key: h.key,
|
|
517
|
+
value: h.value,
|
|
518
|
+
active: h.active,
|
|
519
|
+
description: h.description,
|
|
520
|
+
})))
|
|
521
|
+
setBody(typeof request.body.body === 'string' ? request.body.body : null)
|
|
522
|
+
setBodyContentType(request.body.contentType)
|
|
523
|
+
setAuthOverride(null) // Clear override, will use global auth
|
|
524
|
+
setResponseState({ type: 'idle' })
|
|
525
|
+
|
|
526
|
+
// Clear stored state from localStorage
|
|
527
|
+
clearStoredState(request)
|
|
528
|
+
}, [request])
|
|
529
|
+
|
|
530
|
+
const isLoading = responseState.type === 'loading' || responseState.type === 'streaming'
|
|
531
|
+
|
|
532
|
+
return (
|
|
533
|
+
<TooltipProvider>
|
|
534
|
+
<div className="flex flex-col h-full bg-background">
|
|
535
|
+
{/* Request Bar */}
|
|
536
|
+
<div className="sticky top-0 z-20 flex flex-shrink-0 flex-wrap items-center gap-2 bg-muted/50 p-2 sm:p-4 border-b border-border">
|
|
537
|
+
{/* Method + URL + Auth Indicator */}
|
|
538
|
+
<div className="flex flex-1 min-w-0 items-center rounded-sm border border-border bg-background overflow-hidden">
|
|
539
|
+
<MethodSelector value={method} onChange={setMethod} disabled={isLoading} />
|
|
540
|
+
<Input
|
|
541
|
+
type="text"
|
|
542
|
+
value={endpoint}
|
|
543
|
+
onChange={(e) => setEndpoint(e.target.value)}
|
|
544
|
+
onKeyDown={(e) => {
|
|
545
|
+
if (e.key === 'Enter' && !isLoading) {
|
|
546
|
+
handleSend()
|
|
547
|
+
}
|
|
548
|
+
}}
|
|
549
|
+
placeholder="Enter URL"
|
|
550
|
+
className="flex-1 border-0 rounded-none focus-visible:ring-0 focus-visible:ring-offset-0 text-sm"
|
|
551
|
+
disabled={isLoading}
|
|
552
|
+
/>
|
|
553
|
+
{/* Auth Indicator - hidden on very small screens */}
|
|
554
|
+
<Tooltip>
|
|
555
|
+
<TooltipTrigger asChild>
|
|
556
|
+
<div className={`hidden xs:flex items-center gap-1.5 px-2 sm:px-3 h-full border-l border-border text-xs font-medium cursor-default ${
|
|
557
|
+
effectiveAuth.authType !== 'none'
|
|
558
|
+
? 'text-green-600 dark:text-green-400'
|
|
559
|
+
: 'text-muted-foreground'
|
|
560
|
+
}`}>
|
|
561
|
+
{effectiveAuth.authType !== 'none' ? (
|
|
562
|
+
<ShieldCheck className="h-3.5 w-3.5" weight="fill" />
|
|
563
|
+
) : (
|
|
564
|
+
<ShieldWarning className="h-3.5 w-3.5" />
|
|
565
|
+
)}
|
|
566
|
+
<span className="hidden sm:inline">
|
|
567
|
+
{effectiveAuth.authType !== 'none'
|
|
568
|
+
? (usingGlobalAuth ? 'Global' : 'Custom')
|
|
569
|
+
: 'No Auth'}
|
|
570
|
+
</span>
|
|
571
|
+
</div>
|
|
572
|
+
</TooltipTrigger>
|
|
573
|
+
<TooltipContent side="bottom">
|
|
574
|
+
{effectiveAuth.authType !== 'none' ? (
|
|
575
|
+
<div className="text-xs">
|
|
576
|
+
<p className="font-medium">{getAuthTypeLabel(effectiveAuth)}</p>
|
|
577
|
+
<p className="text-muted-foreground">
|
|
578
|
+
{usingGlobalAuth ? 'Using global auth' : 'Custom for this endpoint'}
|
|
579
|
+
</p>
|
|
580
|
+
</div>
|
|
581
|
+
) : (
|
|
582
|
+
<p className="text-xs">No authentication configured</p>
|
|
583
|
+
)}
|
|
584
|
+
</TooltipContent>
|
|
585
|
+
</Tooltip>
|
|
586
|
+
</div>
|
|
587
|
+
|
|
588
|
+
{/* Send Button */}
|
|
589
|
+
<Button
|
|
590
|
+
onClick={isLoading ? handleCancel : handleSend}
|
|
591
|
+
variant={isLoading ? 'destructive' : 'default'}
|
|
592
|
+
className="px-3 sm:px-6"
|
|
593
|
+
data-send-button
|
|
594
|
+
>
|
|
595
|
+
{isLoading ? (
|
|
596
|
+
<>
|
|
597
|
+
<X className="h-4 w-4 sm:mr-2" weight="bold" />
|
|
598
|
+
<span className="hidden sm:inline">Cancel</span>
|
|
599
|
+
</>
|
|
600
|
+
) : (
|
|
601
|
+
<>
|
|
602
|
+
<PaperPlaneRight className="h-4 w-4 sm:mr-2" weight="fill" />
|
|
603
|
+
<span className="hidden sm:inline">Send</span>
|
|
604
|
+
</>
|
|
605
|
+
)}
|
|
606
|
+
</Button>
|
|
607
|
+
|
|
608
|
+
{/* Clear button */}
|
|
609
|
+
<Tooltip>
|
|
610
|
+
<TooltipTrigger asChild>
|
|
611
|
+
<Button variant="outline" size="icon" onClick={handleClear} className="shrink-0">
|
|
612
|
+
<ArrowCounterClockwise className="h-4 w-4" weight="bold" />
|
|
613
|
+
</Button>
|
|
614
|
+
</TooltipTrigger>
|
|
615
|
+
<TooltipContent>Clear all</TooltipContent>
|
|
616
|
+
</Tooltip>
|
|
617
|
+
</div>
|
|
618
|
+
|
|
619
|
+
{/* Main Content - Split view */}
|
|
620
|
+
<div className="flex-1 flex flex-col lg:flex-row overflow-hidden">
|
|
621
|
+
{/* Request Section */}
|
|
622
|
+
<div className="flex-1 flex flex-col border-b lg:border-b-0 lg:border-r border-border min-h-[200px] sm:min-h-[300px] lg:min-h-0">
|
|
623
|
+
<RequestTabs
|
|
624
|
+
key={request.id}
|
|
625
|
+
params={params}
|
|
626
|
+
headers={headers}
|
|
627
|
+
body={body}
|
|
628
|
+
bodyContentType={bodyContentType}
|
|
629
|
+
auth={effectiveAuth}
|
|
630
|
+
usingGlobalAuth={usingGlobalAuth}
|
|
631
|
+
globalAuth={globalAuth}
|
|
632
|
+
onParamsChange={setParams}
|
|
633
|
+
onHeadersChange={setHeaders}
|
|
634
|
+
onBodyChange={setBody}
|
|
635
|
+
onBodyContentTypeChange={setBodyContentType}
|
|
636
|
+
onAuthChange={setAuthOverride}
|
|
637
|
+
onUseGlobalAuth={() => setAuthOverride(null)}
|
|
638
|
+
/>
|
|
639
|
+
</div>
|
|
640
|
+
|
|
641
|
+
{/* Response Section */}
|
|
642
|
+
<div className="flex-1 flex flex-col min-h-[200px] sm:min-h-[300px] lg:min-h-0 overflow-hidden">
|
|
643
|
+
<ResponseViewer
|
|
644
|
+
responseState={responseState}
|
|
645
|
+
onDebugRequest={onDebugRequest ? (context) => {
|
|
646
|
+
// Enrich context with endpoint info
|
|
647
|
+
onDebugRequest({
|
|
648
|
+
...context,
|
|
649
|
+
endpointName: request.name,
|
|
650
|
+
endpointMethod: method,
|
|
651
|
+
endpointPath: endpoint,
|
|
652
|
+
requestBody: body || undefined,
|
|
653
|
+
})
|
|
654
|
+
} : undefined}
|
|
655
|
+
onExplainRequest={onExplainRequest ? (context) => {
|
|
656
|
+
// Enrich context with endpoint info
|
|
657
|
+
onExplainRequest({
|
|
658
|
+
...context,
|
|
659
|
+
endpointName: request.name,
|
|
660
|
+
endpointMethod: method,
|
|
661
|
+
endpointPath: endpoint,
|
|
662
|
+
requestBody: body || undefined,
|
|
663
|
+
})
|
|
664
|
+
} : undefined}
|
|
665
|
+
/>
|
|
666
|
+
</div>
|
|
667
|
+
</div>
|
|
668
|
+
</div>
|
|
669
|
+
</TooltipProvider>
|
|
670
|
+
)
|
|
671
|
+
}
|