@brainfish-ai/devdoc 0.1.42 → 0.1.44

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (398) hide show
  1. package/dist/cli/commands/create.js +2 -2
  2. package/dist/cli/commands/dev.js +19 -10
  3. package/package.json +3 -2
  4. package/renderer/app/api/assets/[...path]/route.js +108 -0
  5. package/renderer/app/api/assets/route.js +114 -0
  6. package/renderer/app/api/assets/upload/route.js +163 -0
  7. package/renderer/app/api/auth-schemes/route.js +58 -0
  8. package/renderer/app/api/chat/route.js +759 -0
  9. package/renderer/app/api/codegen/route.js +52 -0
  10. package/renderer/app/api/collections/route.js +706 -0
  11. package/renderer/app/api/debug/route.js +47 -0
  12. package/renderer/app/api/deploy/route.js +199 -0
  13. package/renderer/app/api/device/route.js +36 -0
  14. package/renderer/app/api/docs/route.js +205 -0
  15. package/renderer/app/api/domains/add/route.js +121 -0
  16. package/renderer/app/api/domains/lookup/route.js +43 -0
  17. package/renderer/app/api/domains/remove/route.js +89 -0
  18. package/renderer/app/api/domains/status/route.js +140 -0
  19. package/renderer/app/api/domains/verify/route.js +168 -0
  20. package/renderer/app/api/keys/regenerate/route.js +71 -0
  21. package/renderer/app/api/local-assets/[...path]/route.js +108 -0
  22. package/renderer/app/api/openapi-spec/route.js +73 -0
  23. package/renderer/app/api/projects/[slug]/route.js +129 -0
  24. package/renderer/app/api/projects/[slug]/stats/route.js +80 -0
  25. package/renderer/app/api/projects/register/route.js +176 -0
  26. package/renderer/app/api/proxy/route.js +139 -0
  27. package/renderer/app/api/proxy-stream/route.js +156 -0
  28. package/renderer/app/api/redirects/route.js +35 -0
  29. package/renderer/app/api/schema/route.js +85 -0
  30. package/renderer/app/api/subdomains/check/route.js +158 -0
  31. package/renderer/app/api/suggestions/route.js +195 -0
  32. package/renderer/app/globals.css +69 -0
  33. package/renderer/app/layout.js +47 -0
  34. package/renderer/app/llms-full.txt/route.js +266 -0
  35. package/renderer/app/llms.txt/route.js +228 -0
  36. package/renderer/app/page.js +12 -0
  37. package/renderer/app/robots.txt/route.js +66 -0
  38. package/renderer/app/sitemap.xml/route.js +155 -0
  39. package/renderer/components/docs/index.js +8 -0
  40. package/renderer/components/docs/mdx/accordion.js +113 -0
  41. package/renderer/components/docs/mdx/badge.js +72 -0
  42. package/renderer/components/docs/mdx/callouts.js +137 -0
  43. package/renderer/components/docs/mdx/cards.js +175 -0
  44. package/renderer/components/docs/mdx/changelog.js +100 -0
  45. package/renderer/components/docs/mdx/code-block.js +147 -0
  46. package/renderer/components/docs/mdx/code-group.js +287 -0
  47. package/renderer/components/docs/mdx/file-embeds.js +82 -0
  48. package/renderer/components/docs/mdx/frame.js +59 -0
  49. package/renderer/components/docs/mdx/highlight.js +90 -0
  50. package/renderer/components/docs/mdx/iframe.js +69 -0
  51. package/renderer/components/docs/mdx/image.js +135 -0
  52. package/renderer/components/docs/mdx/index.js +134 -0
  53. package/renderer/components/docs/mdx/landing.js +317 -0
  54. package/renderer/components/docs/mdx/mermaid.js +212 -0
  55. package/renderer/components/docs/mdx/param-field.js +112 -0
  56. package/renderer/components/docs/mdx/steps.js +74 -0
  57. package/renderer/components/docs/mdx/tabs.js +50 -0
  58. package/renderer/components/docs/mdx-renderer.js +77 -0
  59. package/renderer/components/docs/navigation/breadcrumbs.js +64 -0
  60. package/renderer/components/docs/navigation/index.js +6 -0
  61. package/renderer/components/docs/navigation/page-nav.js +57 -0
  62. package/renderer/components/docs/navigation/sidebar.js +375 -0
  63. package/renderer/components/docs/navigation/toc.js +89 -0
  64. package/renderer/components/docs/notice.js +77 -0
  65. package/renderer/components/docs-header.js +202 -0
  66. package/renderer/components/docs-viewer/agent/agent-chat.js +1831 -0
  67. package/renderer/components/docs-viewer/agent/agent-popup-button.js +99 -0
  68. package/renderer/components/docs-viewer/agent/cards/debug-context-card.js +107 -0
  69. package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.js +57 -0
  70. package/renderer/components/docs-viewer/agent/cards/index.js +45 -0
  71. package/renderer/components/docs-viewer/agent/cards/response-options-card.js +154 -0
  72. package/renderer/components/docs-viewer/agent/cards/types.js +22 -0
  73. package/renderer/components/docs-viewer/agent/chat-message.js +2 -0
  74. package/renderer/components/docs-viewer/agent/index.js +7 -0
  75. package/renderer/components/docs-viewer/agent/messages/assistant-message.js +108 -0
  76. package/renderer/components/docs-viewer/agent/messages/chat-message.js +34 -0
  77. package/renderer/components/docs-viewer/agent/messages/index.js +6 -0
  78. package/renderer/components/docs-viewer/agent/messages/tool-call-display.js +1065 -0
  79. package/renderer/components/docs-viewer/agent/messages/types.js +2 -0
  80. package/renderer/components/docs-viewer/agent/messages/typing-indicator.js +26 -0
  81. package/renderer/components/docs-viewer/agent/messages/user-message.js +37 -0
  82. package/renderer/components/docs-viewer/code-editor/{index.tsx → index.js} +1 -1
  83. package/renderer/components/docs-viewer/code-editor/notes-mode.js +1338 -0
  84. package/renderer/components/docs-viewer/content/changelog-page.js +297 -0
  85. package/renderer/components/docs-viewer/content/content-router.js +182 -0
  86. package/renderer/components/docs-viewer/content/doc-page.js +290 -0
  87. package/renderer/components/docs-viewer/content/documentation-viewer.js +14 -0
  88. package/renderer/components/docs-viewer/content/index.js +31 -0
  89. package/renderer/components/docs-viewer/content/not-found-page.js +300 -0
  90. package/renderer/components/docs-viewer/content/request-details.js +528 -0
  91. package/renderer/components/docs-viewer/content/sections/auth.js +108 -0
  92. package/renderer/components/docs-viewer/content/sections/body.js +80 -0
  93. package/renderer/components/docs-viewer/content/sections/headers.js +64 -0
  94. package/renderer/components/docs-viewer/content/sections/overview.js +56 -0
  95. package/renderer/components/docs-viewer/content/sections/parameters.js +64 -0
  96. package/renderer/components/docs-viewer/content/sections/responses.js +91 -0
  97. package/renderer/components/docs-viewer/global-auth-modal.js +427 -0
  98. package/renderer/components/docs-viewer/index.js +1448 -0
  99. package/renderer/components/docs-viewer/playground/auth-editor.js +418 -0
  100. package/renderer/components/docs-viewer/playground/body-editor.js +240 -0
  101. package/renderer/components/docs-viewer/playground/code-editor.js +135 -0
  102. package/renderer/components/docs-viewer/playground/code-snippet.js +393 -0
  103. package/renderer/components/docs-viewer/playground/graphql-playground.js +936 -0
  104. package/renderer/components/docs-viewer/playground/index.js +682 -0
  105. package/renderer/components/docs-viewer/playground/key-value-editor.js +317 -0
  106. package/renderer/components/docs-viewer/playground/method-selector.js +65 -0
  107. package/renderer/components/docs-viewer/playground/request-builder.js +181 -0
  108. package/renderer/components/docs-viewer/playground/request-tabs.js +240 -0
  109. package/renderer/components/docs-viewer/playground/response-cards/idle-card.js +42 -0
  110. package/renderer/components/docs-viewer/playground/response-cards/index.js +72 -0
  111. package/renderer/components/docs-viewer/playground/response-cards/loading-card.js +24 -0
  112. package/renderer/components/docs-viewer/playground/response-cards/network-error-card.js +28 -0
  113. package/renderer/components/docs-viewer/playground/response-cards/response-body-card.js +308 -0
  114. package/renderer/components/docs-viewer/playground/response-cards/types.js +9 -0
  115. package/renderer/components/docs-viewer/playground/response-viewer.js +18 -0
  116. package/renderer/components/docs-viewer/search/index.js +2 -0
  117. package/renderer/components/docs-viewer/search/search-dialog.js +367 -0
  118. package/renderer/components/docs-viewer/search/use-search.js +89 -0
  119. package/renderer/components/docs-viewer/shared/markdown-renderer.js +423 -0
  120. package/renderer/components/docs-viewer/shared/method-badge.js +23 -0
  121. package/renderer/components/docs-viewer/shared/schema-viewer.js +321 -0
  122. package/renderer/components/docs-viewer/sidebar/collection-tree.js +222 -0
  123. package/renderer/components/docs-viewer/sidebar/endpoint-options.js +512 -0
  124. package/renderer/components/docs-viewer/sidebar/index.js +196 -0
  125. package/renderer/components/docs-viewer/sidebar/right-sidebar.js +159 -0
  126. package/renderer/components/docs-viewer/sidebar/sidebar-group.js +87 -0
  127. package/renderer/components/docs-viewer/sidebar/sidebar-item.js +172 -0
  128. package/renderer/components/docs-viewer/sidebar/sidebar-section.js +31 -0
  129. package/renderer/components/theme-provider.js +10 -0
  130. package/renderer/components/theme-toggle.js +86 -0
  131. package/renderer/components/ui/badge.js +29 -0
  132. package/renderer/components/ui/button.js +40 -0
  133. package/renderer/components/ui/dialog.js +50 -0
  134. package/renderer/components/ui/dropdown-menu.js +143 -0
  135. package/renderer/components/ui/input.js +12 -0
  136. package/renderer/components/ui/label.js +13 -0
  137. package/renderer/components/ui/navigation-menu.js +83 -0
  138. package/renderer/components/ui/select.js +116 -0
  139. package/renderer/components/ui/spinner.js +92 -0
  140. package/renderer/components/ui/tabs.js +34 -0
  141. package/renderer/components/ui/tooltip.js +43 -0
  142. package/renderer/hooks/use-code-copy.js +76 -0
  143. package/renderer/hooks/use-openapi-title.js +33 -0
  144. package/renderer/hooks/use-route-state.js +159 -0
  145. package/renderer/lib/api-docs/agent/index.js +4 -0
  146. package/renderer/lib/api-docs/agent/indexer.js +254 -0
  147. package/renderer/lib/api-docs/agent/spec-summary.js +227 -0
  148. package/renderer/lib/api-docs/agent/types.js +5 -0
  149. package/renderer/lib/api-docs/agent/use-suggestions.js +97 -0
  150. package/renderer/lib/api-docs/auth/auth-context.js +157 -0
  151. package/renderer/lib/api-docs/auth/auth-storage.js +66 -0
  152. package/renderer/lib/api-docs/auth/crypto.js +64 -0
  153. package/renderer/lib/api-docs/auth/index.js +3 -0
  154. package/renderer/lib/api-docs/code-editor/db.js +145 -0
  155. package/renderer/lib/api-docs/code-editor/hooks.js +254 -0
  156. package/renderer/lib/api-docs/code-editor/{index.ts → index.js} +3 -4
  157. package/renderer/lib/api-docs/code-editor/mode-context.js +126 -0
  158. package/renderer/lib/api-docs/code-editor/types.js +53 -0
  159. package/renderer/lib/api-docs/codegen/definitions.js +258 -0
  160. package/renderer/lib/api-docs/codegen/har.js +171 -0
  161. package/renderer/lib/api-docs/codegen/index.js +118 -0
  162. package/renderer/lib/api-docs/factories.js +136 -0
  163. package/renderer/lib/api-docs/{index.ts → index.js} +5 -10
  164. package/renderer/lib/api-docs/mobile-context.js +116 -0
  165. package/renderer/lib/api-docs/navigation-context.js +62 -0
  166. package/renderer/lib/api-docs/parsers/graphql/index.js +50 -0
  167. package/renderer/lib/api-docs/parsers/graphql/parser.js +350 -0
  168. package/renderer/lib/api-docs/parsers/graphql/transformer.js +215 -0
  169. package/renderer/lib/api-docs/parsers/graphql/types.js +46 -0
  170. package/renderer/lib/api-docs/parsers/openapi/dereferencer.js +43 -0
  171. package/renderer/lib/api-docs/parsers/openapi/extractors/auth.js +486 -0
  172. package/renderer/lib/api-docs/parsers/openapi/extractors/body.js +295 -0
  173. package/renderer/lib/api-docs/parsers/openapi/extractors/index.js +132 -0
  174. package/renderer/lib/api-docs/parsers/openapi/index.js +127 -0
  175. package/renderer/lib/api-docs/parsers/openapi/transformer.js +192 -0
  176. package/renderer/lib/api-docs/parsers/openapi/validator.js +24 -0
  177. package/renderer/lib/api-docs/playground/context.js +65 -0
  178. package/renderer/lib/api-docs/playground/navigation-context.js +74 -0
  179. package/renderer/lib/api-docs/playground/request-builder.js +163 -0
  180. package/renderer/lib/api-docs/playground/request-runner.js +224 -0
  181. package/renderer/lib/api-docs/playground/types.js +5 -0
  182. package/renderer/lib/api-docs/types.js +23 -0
  183. package/renderer/lib/api-docs/utils.js +212 -0
  184. package/renderer/lib/cache.js +157 -0
  185. package/renderer/lib/docs/config/domain-schema.js +161 -0
  186. package/renderer/lib/docs/config/environment.js +38 -0
  187. package/renderer/lib/docs/config/index.js +6 -0
  188. package/renderer/lib/docs/config/loader.js +113 -0
  189. package/renderer/lib/docs/config/schema.js +281 -0
  190. package/renderer/lib/docs/index.js +8 -0
  191. package/renderer/lib/docs/mdx/compiler.js +131 -0
  192. package/renderer/lib/docs/mdx/frontmatter.js +73 -0
  193. package/renderer/lib/docs/mdx/index.js +10 -0
  194. package/renderer/lib/docs/mdx/remark-mermaid.js +63 -0
  195. package/renderer/lib/docs/navigation/generator.js +269 -0
  196. package/renderer/lib/docs/navigation/index.js +3 -0
  197. package/renderer/lib/docs/navigation/types.js +11 -0
  198. package/renderer/lib/docs-navigation-context.js +40 -0
  199. package/renderer/lib/docs-navigation.js +140 -0
  200. package/renderer/lib/multi-tenant/context.js +80 -0
  201. package/renderer/lib/storage/blob.js +767 -0
  202. package/renderer/lib/utils/icons.js +30 -0
  203. package/renderer/lib/utils.js +5 -0
  204. package/renderer/next.config.js +62 -0
  205. package/renderer/package.json +1 -0
  206. package/renderer/tsconfig.json +23 -5
  207. package/renderer/app/api/assets/[...path]/route.ts +0 -123
  208. package/renderer/app/api/assets/route.ts +0 -124
  209. package/renderer/app/api/assets/upload/route.ts +0 -177
  210. package/renderer/app/api/auth-schemes/route.ts +0 -77
  211. package/renderer/app/api/chat/route.ts +0 -858
  212. package/renderer/app/api/codegen/route.ts +0 -72
  213. package/renderer/app/api/collections/route.ts +0 -1002
  214. package/renderer/app/api/debug/route.ts +0 -53
  215. package/renderer/app/api/deploy/route.ts +0 -234
  216. package/renderer/app/api/device/route.ts +0 -42
  217. package/renderer/app/api/docs/route.ts +0 -201
  218. package/renderer/app/api/domains/add/route.ts +0 -132
  219. package/renderer/app/api/domains/lookup/route.ts +0 -43
  220. package/renderer/app/api/domains/remove/route.ts +0 -100
  221. package/renderer/app/api/domains/status/route.ts +0 -158
  222. package/renderer/app/api/domains/verify/route.ts +0 -181
  223. package/renderer/app/api/keys/regenerate/route.ts +0 -80
  224. package/renderer/app/api/local-assets/[...path]/route.ts +0 -122
  225. package/renderer/app/api/openapi-spec/route.ts +0 -151
  226. package/renderer/app/api/projects/[slug]/route.ts +0 -153
  227. package/renderer/app/api/projects/[slug]/stats/route.ts +0 -96
  228. package/renderer/app/api/projects/register/route.ts +0 -152
  229. package/renderer/app/api/proxy/route.ts +0 -149
  230. package/renderer/app/api/proxy-stream/route.ts +0 -168
  231. package/renderer/app/api/redirects/route.ts +0 -47
  232. package/renderer/app/api/schema/route.ts +0 -73
  233. package/renderer/app/api/subdomains/check/route.ts +0 -172
  234. package/renderer/app/api/suggestions/route.ts +0 -144
  235. package/renderer/app/layout.tsx +0 -54
  236. package/renderer/app/llms-full.txt/route.ts +0 -346
  237. package/renderer/app/llms.txt/route.ts +0 -279
  238. package/renderer/app/page.tsx +0 -14
  239. package/renderer/app/robots.txt/route.ts +0 -84
  240. package/renderer/app/sitemap.xml/route.ts +0 -199
  241. package/renderer/components/docs/index.ts +0 -12
  242. package/renderer/components/docs/mdx/accordion.tsx +0 -169
  243. package/renderer/components/docs/mdx/badge.tsx +0 -132
  244. package/renderer/components/docs/mdx/callouts.tsx +0 -154
  245. package/renderer/components/docs/mdx/cards.tsx +0 -241
  246. package/renderer/components/docs/mdx/changelog.tsx +0 -120
  247. package/renderer/components/docs/mdx/code-block.tsx +0 -186
  248. package/renderer/components/docs/mdx/code-group.tsx +0 -421
  249. package/renderer/components/docs/mdx/file-embeds.tsx +0 -105
  250. package/renderer/components/docs/mdx/frame.tsx +0 -112
  251. package/renderer/components/docs/mdx/highlight.tsx +0 -151
  252. package/renderer/components/docs/mdx/iframe.tsx +0 -134
  253. package/renderer/components/docs/mdx/image.tsx +0 -235
  254. package/renderer/components/docs/mdx/index.ts +0 -237
  255. package/renderer/components/docs/mdx/landing.tsx +0 -684
  256. package/renderer/components/docs/mdx/mermaid.tsx +0 -240
  257. package/renderer/components/docs/mdx/param-field.tsx +0 -200
  258. package/renderer/components/docs/mdx/steps.tsx +0 -113
  259. package/renderer/components/docs/mdx/tabs.tsx +0 -86
  260. package/renderer/components/docs/mdx-renderer.tsx +0 -100
  261. package/renderer/components/docs/navigation/breadcrumbs.tsx +0 -76
  262. package/renderer/components/docs/navigation/index.ts +0 -8
  263. package/renderer/components/docs/navigation/page-nav.tsx +0 -64
  264. package/renderer/components/docs/navigation/sidebar.tsx +0 -515
  265. package/renderer/components/docs/navigation/toc.tsx +0 -113
  266. package/renderer/components/docs/notice.tsx +0 -105
  267. package/renderer/components/docs-header.tsx +0 -278
  268. package/renderer/components/docs-viewer/agent/agent-chat.tsx +0 -2076
  269. package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +0 -90
  270. package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +0 -49
  271. package/renderer/components/docs-viewer/agent/cards/index.tsx +0 -50
  272. package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +0 -212
  273. package/renderer/components/docs-viewer/agent/cards/types.ts +0 -84
  274. package/renderer/components/docs-viewer/agent/chat-message.tsx +0 -17
  275. package/renderer/components/docs-viewer/agent/index.tsx +0 -6
  276. package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +0 -119
  277. package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +0 -46
  278. package/renderer/components/docs-viewer/agent/messages/index.ts +0 -17
  279. package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +0 -721
  280. package/renderer/components/docs-viewer/agent/messages/types.ts +0 -61
  281. package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +0 -24
  282. package/renderer/components/docs-viewer/agent/messages/user-message.tsx +0 -51
  283. package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +0 -1283
  284. package/renderer/components/docs-viewer/content/changelog-page.tsx +0 -331
  285. package/renderer/components/docs-viewer/content/doc-page.tsx +0 -367
  286. package/renderer/components/docs-viewer/content/documentation-viewer.tsx +0 -17
  287. package/renderer/components/docs-viewer/content/index.tsx +0 -29
  288. package/renderer/components/docs-viewer/content/not-found-page.tsx +0 -330
  289. package/renderer/components/docs-viewer/content/request-details.tsx +0 -330
  290. package/renderer/components/docs-viewer/content/sections/auth.tsx +0 -69
  291. package/renderer/components/docs-viewer/content/sections/body.tsx +0 -66
  292. package/renderer/components/docs-viewer/content/sections/headers.tsx +0 -43
  293. package/renderer/components/docs-viewer/content/sections/overview.tsx +0 -40
  294. package/renderer/components/docs-viewer/content/sections/parameters.tsx +0 -43
  295. package/renderer/components/docs-viewer/content/sections/responses.tsx +0 -87
  296. package/renderer/components/docs-viewer/global-auth-modal.tsx +0 -352
  297. package/renderer/components/docs-viewer/index.tsx +0 -1670
  298. package/renderer/components/docs-viewer/playground/auth-editor.tsx +0 -280
  299. package/renderer/components/docs-viewer/playground/body-editor.tsx +0 -221
  300. package/renderer/components/docs-viewer/playground/code-editor.tsx +0 -224
  301. package/renderer/components/docs-viewer/playground/code-snippet.tsx +0 -387
  302. package/renderer/components/docs-viewer/playground/graphql-playground.tsx +0 -745
  303. package/renderer/components/docs-viewer/playground/index.tsx +0 -671
  304. package/renderer/components/docs-viewer/playground/key-value-editor.tsx +0 -261
  305. package/renderer/components/docs-viewer/playground/method-selector.tsx +0 -60
  306. package/renderer/components/docs-viewer/playground/request-builder.tsx +0 -179
  307. package/renderer/components/docs-viewer/playground/request-tabs.tsx +0 -237
  308. package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +0 -21
  309. package/renderer/components/docs-viewer/playground/response-cards/index.tsx +0 -93
  310. package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +0 -16
  311. package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +0 -23
  312. package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +0 -268
  313. package/renderer/components/docs-viewer/playground/response-cards/types.ts +0 -82
  314. package/renderer/components/docs-viewer/playground/response-viewer.tsx +0 -43
  315. package/renderer/components/docs-viewer/search/index.ts +0 -2
  316. package/renderer/components/docs-viewer/search/search-dialog.tsx +0 -331
  317. package/renderer/components/docs-viewer/search/use-search.ts +0 -117
  318. package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +0 -431
  319. package/renderer/components/docs-viewer/shared/method-badge.tsx +0 -41
  320. package/renderer/components/docs-viewer/shared/schema-viewer.tsx +0 -349
  321. package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +0 -259
  322. package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +0 -316
  323. package/renderer/components/docs-viewer/sidebar/index.tsx +0 -282
  324. package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +0 -202
  325. package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +0 -118
  326. package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +0 -212
  327. package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +0 -38
  328. package/renderer/components/theme-provider.tsx +0 -11
  329. package/renderer/components/theme-toggle.tsx +0 -76
  330. package/renderer/components/ui/badge.tsx +0 -46
  331. package/renderer/components/ui/button.tsx +0 -59
  332. package/renderer/components/ui/dialog.tsx +0 -118
  333. package/renderer/components/ui/dropdown-menu.tsx +0 -257
  334. package/renderer/components/ui/input.tsx +0 -21
  335. package/renderer/components/ui/label.tsx +0 -24
  336. package/renderer/components/ui/navigation-menu.tsx +0 -168
  337. package/renderer/components/ui/select.tsx +0 -190
  338. package/renderer/components/ui/spinner.tsx +0 -114
  339. package/renderer/components/ui/tabs.tsx +0 -66
  340. package/renderer/components/ui/tooltip.tsx +0 -61
  341. package/renderer/hooks/use-code-copy.ts +0 -88
  342. package/renderer/hooks/use-openapi-title.ts +0 -44
  343. package/renderer/lib/api-docs/agent/index.ts +0 -6
  344. package/renderer/lib/api-docs/agent/indexer.ts +0 -323
  345. package/renderer/lib/api-docs/agent/spec-summary.ts +0 -335
  346. package/renderer/lib/api-docs/agent/types.ts +0 -116
  347. package/renderer/lib/api-docs/auth/auth-context.tsx +0 -225
  348. package/renderer/lib/api-docs/auth/auth-storage.ts +0 -87
  349. package/renderer/lib/api-docs/auth/crypto.ts +0 -89
  350. package/renderer/lib/api-docs/auth/index.ts +0 -4
  351. package/renderer/lib/api-docs/code-editor/db.ts +0 -164
  352. package/renderer/lib/api-docs/code-editor/hooks.ts +0 -266
  353. package/renderer/lib/api-docs/code-editor/mode-context.tsx +0 -207
  354. package/renderer/lib/api-docs/code-editor/types.ts +0 -105
  355. package/renderer/lib/api-docs/codegen/definitions.ts +0 -297
  356. package/renderer/lib/api-docs/codegen/har.ts +0 -251
  357. package/renderer/lib/api-docs/codegen/index.ts +0 -159
  358. package/renderer/lib/api-docs/factories.ts +0 -170
  359. package/renderer/lib/api-docs/mobile-context.tsx +0 -112
  360. package/renderer/lib/api-docs/navigation-context.tsx +0 -88
  361. package/renderer/lib/api-docs/parsers/graphql/README.md +0 -129
  362. package/renderer/lib/api-docs/parsers/graphql/index.ts +0 -91
  363. package/renderer/lib/api-docs/parsers/graphql/parser.ts +0 -491
  364. package/renderer/lib/api-docs/parsers/graphql/transformer.ts +0 -246
  365. package/renderer/lib/api-docs/parsers/graphql/types.ts +0 -283
  366. package/renderer/lib/api-docs/parsers/openapi/README.md +0 -32
  367. package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +0 -60
  368. package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +0 -574
  369. package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +0 -403
  370. package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +0 -232
  371. package/renderer/lib/api-docs/parsers/openapi/index.ts +0 -171
  372. package/renderer/lib/api-docs/parsers/openapi/transformer.ts +0 -278
  373. package/renderer/lib/api-docs/parsers/openapi/validator.ts +0 -31
  374. package/renderer/lib/api-docs/playground/context.tsx +0 -107
  375. package/renderer/lib/api-docs/playground/navigation-context.tsx +0 -124
  376. package/renderer/lib/api-docs/playground/request-builder.ts +0 -223
  377. package/renderer/lib/api-docs/playground/request-runner.ts +0 -282
  378. package/renderer/lib/api-docs/playground/types.ts +0 -35
  379. package/renderer/lib/api-docs/types.ts +0 -269
  380. package/renderer/lib/api-docs/utils.ts +0 -311
  381. package/renderer/lib/cache.ts +0 -193
  382. package/renderer/lib/docs/config/domain-schema.ts +0 -260
  383. package/renderer/lib/docs/config/index.ts +0 -43
  384. package/renderer/lib/docs/config/loader.ts +0 -142
  385. package/renderer/lib/docs/config/schema.ts +0 -308
  386. package/renderer/lib/docs/index.ts +0 -12
  387. package/renderer/lib/docs/mdx/compiler.ts +0 -176
  388. package/renderer/lib/docs/mdx/frontmatter.ts +0 -91
  389. package/renderer/lib/docs/mdx/index.ts +0 -26
  390. package/renderer/lib/docs/navigation/generator.ts +0 -348
  391. package/renderer/lib/docs/navigation/index.ts +0 -12
  392. package/renderer/lib/docs/navigation/types.ts +0 -123
  393. package/renderer/lib/docs-navigation-context.tsx +0 -80
  394. package/renderer/lib/multi-tenant/context.ts +0 -105
  395. package/renderer/lib/storage/blob.ts +0 -1083
  396. package/renderer/lib/utils/icons.ts +0 -48
  397. package/renderer/lib/utils.ts +0 -6
  398. package/renderer/next.config.ts +0 -76
@@ -1,1670 +0,0 @@
1
- 'use client'
2
-
3
- import { useEffect, useState, useCallback, useRef } from 'react'
4
- import { Spinner } from '@phosphor-icons/react'
5
- import type { BrainfishCollection, BrainfishRESTRequest, BrainfishDocGroup } from '@/lib/api-docs/types'
6
- import { DocsSidebar } from './sidebar'
7
- import { NotFoundPage } from './content/not-found-page'
8
- import { ApiPlayground } from './playground'
9
- import type { DebugContext } from './playground/response-viewer'
10
- import { RightSidebar } from './sidebar/right-sidebar'
11
- import { RequestDetails } from './content/request-details'
12
- import { DocPage } from './content/doc-page'
13
- import { ChangelogPage } from './content/changelog-page'
14
- import { GraphQLPlayground, type GraphQLOperationItem } from './playground/graphql-playground'
15
- import { makeBrainfishCollection } from '@/lib/api-docs/factories'
16
- import { parse, Kind, type DocumentNode, type FieldDefinitionNode, type InputValueDefinitionNode, type TypeNode } from 'graphql'
17
- import { GlobalAuthModal } from './global-auth-modal'
18
- import { AuthProvider } from '@/lib/api-docs/auth'
19
- import { PlaygroundProvider, usePlaygroundPrefill } from '@/lib/api-docs/playground/context'
20
- import { PlaygroundNavigationProvider, usePlaygroundNavigation, type PlaygroundTab, type HighlightField } from '@/lib/api-docs/playground/navigation-context'
21
- import { NavigationProvider } from '@/lib/api-docs/navigation-context'
22
- import type { PrefillData } from '@/lib/api-docs/agent/types'
23
- import { ModeProvider, useModeContext } from '@/lib/api-docs/code-editor'
24
- import { NotesMode } from './code-editor'
25
- import { DocsHeader } from '@/components/docs-header'
26
- import { Notice } from '../docs/notice'
27
- import { DocsNavigationProvider } from '@/lib/docs-navigation-context'
28
- import { Button } from '@/components/ui/button'
29
- import { Code, TestTube, Book } from '@phosphor-icons/react'
30
- import { cn } from '@/lib/utils'
31
- import { SearchDialog, useSearch } from './search'
32
- import { useTheme } from 'next-themes'
33
-
34
- // Helper to convert GraphQL TypeNode to string
35
- function typeNodeToString(typeNode: TypeNode): string {
36
- switch (typeNode.kind) {
37
- case Kind.NAMED_TYPE:
38
- return typeNode.name.value
39
- case Kind.NON_NULL_TYPE:
40
- return `${typeNodeToString(typeNode.type)}!`
41
- case Kind.LIST_TYPE:
42
- return `[${typeNodeToString(typeNode.type)}]`
43
- default:
44
- return 'Unknown'
45
- }
46
- }
47
-
48
- // Parse GraphQL schema using the graphql library
49
- function parseGraphQLSchema(schemaSDL: string): GraphQLOperationItem[] {
50
- const operations: GraphQLOperationItem[] = []
51
-
52
- try {
53
- const ast: DocumentNode = parse(schemaSDL)
54
-
55
- // Find Query, Mutation, Subscription type definitions
56
- for (const def of ast.definitions) {
57
- if (def.kind === Kind.OBJECT_TYPE_DEFINITION) {
58
- const typeName = def.name.value
59
-
60
- // Only process root operation types
61
- if (!['Query', 'Mutation', 'Subscription'].includes(typeName)) continue
62
-
63
- const operationType = typeName.toLowerCase() as 'query' | 'mutation' | 'subscription'
64
-
65
- // Process each field
66
- for (const field of def.fields || []) {
67
- const name = field.name.value
68
-
69
- // Skip internal fields
70
- if (name.startsWith('_')) continue
71
-
72
- // Get description
73
- const description = field.description?.value || null
74
-
75
- // Get return type
76
- const returnType = typeNodeToString(field.type)
77
-
78
- // Build args string for query generation
79
- const args = (field.arguments || [])
80
- .map((arg: InputValueDefinitionNode) => `${arg.name.value}: ${typeNodeToString(arg.type)}`)
81
- .join(', ')
82
-
83
- const query = generateGraphQLQuery(operationType, name, field.arguments || [], returnType)
84
- const exampleVariables = generateGraphQLVariables(field.arguments || [])
85
-
86
- operations.push({
87
- id: `${operationType}-${name}`,
88
- name,
89
- description,
90
- operationType,
91
- query,
92
- exampleVariables,
93
- })
94
- }
95
- }
96
- }
97
-
98
- } catch (err) {
99
- console.error('[GraphQL Parser] Failed to parse schema:', err)
100
- }
101
-
102
- return operations
103
- }
104
-
105
- // Generate example GraphQL query from AST
106
- function generateGraphQLQuery(
107
- operationType: string,
108
- name: string,
109
- args: readonly InputValueDefinitionNode[],
110
- returnType: string
111
- ): string {
112
- let query = `${operationType} ${name.charAt(0).toUpperCase() + name.slice(1)}`
113
-
114
- if (args.length > 0) {
115
- const varDefs = args.map(arg => `$${arg.name.value}: ${typeNodeToString(arg.type)}`).join(', ')
116
- query += `(${varDefs})`
117
- }
118
-
119
- query += ` {\n ${name}`
120
-
121
- if (args.length > 0) {
122
- const argPairs = args.map(arg => `${arg.name.value}: $${arg.name.value}`).join(', ')
123
- query += `(${argPairs})`
124
- }
125
-
126
- const baseType = returnType.replace(/[\[\]!]/g, '').trim()
127
- if (['String', 'Int', 'Float', 'Boolean', 'ID'].includes(baseType)) {
128
- query += '\n}'
129
- } else {
130
- query += ` {\n id\n __typename\n }\n}`
131
- }
132
-
133
- return query
134
- }
135
-
136
- // Generate example variables from AST args
137
- function generateGraphQLVariables(args: readonly InputValueDefinitionNode[]): Record<string, unknown> {
138
- const variables: Record<string, unknown> = {}
139
-
140
- for (const arg of args) {
141
- const name = arg.name.value
142
- const type = typeNodeToString(arg.type).replace(/[\[\]!]/g, '').trim()
143
-
144
- switch (type) {
145
- case 'String': variables[name] = 'example'; break
146
- case 'Int': variables[name] = 1; break
147
- case 'Float': variables[name] = 1.0; break
148
- case 'Boolean': variables[name] = true; break
149
- case 'ID': variables[name] = '1'; break
150
- default: variables[name] = {}
151
- }
152
- }
153
-
154
- return variables
155
- }
156
-
157
- // Convert GraphQL operations to BrainfishCollection for sidebar
158
- function convertGraphQLToCollection(
159
- operations: GraphQLOperationItem[],
160
- endpoint: string
161
- ): BrainfishCollection {
162
- const queries = operations.filter(op => op.operationType === 'query')
163
- const mutations = operations.filter(op => op.operationType === 'mutation')
164
- const subscriptions = operations.filter(op => op.operationType === 'subscription')
165
-
166
- const toRequest = (op: GraphQLOperationItem): BrainfishRESTRequest => ({
167
- id: op.id,
168
- name: op.name,
169
- description: op.description || '',
170
- method: 'POST',
171
- endpoint,
172
- params: [],
173
- headers: [],
174
- auth: { authType: 'none', authActive: false },
175
- body: { contentType: 'application/json', body: JSON.stringify({ query: op.query, variables: op.exampleVariables }, null, 2) },
176
- requestVariables: [],
177
- responses: {},
178
- tags: [op.operationType],
179
- })
180
-
181
- const folders: BrainfishCollection[] = []
182
-
183
- if (queries.length > 0) {
184
- folders.push(makeBrainfishCollection({
185
- name: 'Queries',
186
- description: 'GraphQL Query operations',
187
- requests: queries.map(toRequest),
188
- folders: [],
189
- variables: [],
190
- auth: { authType: 'none', authActive: false },
191
- headers: [],
192
- }))
193
- }
194
-
195
- if (mutations.length > 0) {
196
- folders.push(makeBrainfishCollection({
197
- name: 'Mutations',
198
- description: 'GraphQL Mutation operations',
199
- requests: mutations.map(toRequest),
200
- folders: [],
201
- variables: [],
202
- auth: { authType: 'none', authActive: false },
203
- headers: [],
204
- }))
205
- }
206
-
207
- if (subscriptions.length > 0) {
208
- folders.push(makeBrainfishCollection({
209
- name: 'Subscriptions',
210
- description: 'GraphQL Subscription operations',
211
- requests: subscriptions.map(toRequest),
212
- folders: [],
213
- variables: [],
214
- auth: { authType: 'none', authActive: false },
215
- headers: [],
216
- }))
217
- }
218
-
219
- return makeBrainfishCollection({
220
- name: 'GraphQL API',
221
- description: 'GraphQL operations',
222
- folders,
223
- requests: [],
224
- variables: [],
225
- auth: { authType: 'none', authActive: false },
226
- headers: [],
227
- })
228
- }
229
-
230
- // Helper to find request by ID in collection
231
- function findRequestById(collection: BrainfishCollection, id: string): BrainfishRESTRequest | null {
232
- // Check direct requests
233
- const found = collection.requests.find(r => r.id === id)
234
- if (found) return found
235
-
236
- // Check folders recursively
237
- for (const folder of collection.folders) {
238
- const request = findRequestById(folder, id)
239
- if (request) return request
240
- }
241
- return null
242
- }
243
-
244
- // Helper to get the first endpoint from collection
245
- function getFirstEndpoint(collection: BrainfishCollection): BrainfishRESTRequest | null {
246
- // Check direct requests first
247
- if (collection.requests.length > 0) {
248
- return collection.requests[0]
249
- }
250
-
251
- // Check folders recursively
252
- for (const folder of collection.folders) {
253
- const firstInFolder = getFirstEndpoint(folder)
254
- if (firstInFolder) return firstInFolder
255
- }
256
- return null
257
- }
258
-
259
- // Helper to check if a doc group belongs to a specific tab
260
- // Group IDs are formatted as: group-{tab-id}-{group-name}
261
- function isGroupForTab(groupId: string, tabId: string): boolean {
262
- // Check if the group ID starts with "group-{tabId}-"
263
- const prefix = `group-${tabId}-`
264
- return groupId.startsWith(prefix)
265
- }
266
-
267
- // Helper to check if a tab has doc groups (MDX pages)
268
- function hasDocGroupsForTab(
269
- docGroups: BrainfishDocGroup[] | undefined,
270
- tabId: string
271
- ): boolean {
272
- if (!docGroups || docGroups.length === 0) return false
273
-
274
- // Check if any doc group belongs to this tab and has pages
275
- return docGroups.some(g => isGroupForTab(g.id, tabId) && g.pages.length > 0)
276
- }
277
-
278
- // Helper to get the first page from doc groups for a tab
279
- function getFirstDocPageForTab(
280
- docGroups: BrainfishDocGroup[] | undefined,
281
- tabId: string
282
- ): string | null {
283
- if (!docGroups || docGroups.length === 0) return null
284
-
285
- const tabDocGroup = docGroups.find(g => isGroupForTab(g.id, tabId) && g.pages.length > 0)
286
-
287
- return tabDocGroup?.pages[0]?.slug || null
288
- }
289
-
290
-
291
- // API version
292
- interface ApiVersion {
293
- version: string
294
- spec: string
295
- default: boolean
296
- }
297
-
298
- // GraphQL schema info for graphql tabs
299
- interface GraphQLSchemaInfo {
300
- name: string
301
- schema: string
302
- endpoint: string
303
- default: boolean
304
- }
305
-
306
- // Navigation tab from config
307
- interface NavigationTab {
308
- id: string
309
- tab: string // Tab name (used for both navigation and page header)
310
- type: 'docs' | 'openapi' | 'changelog' | 'graphql'
311
- path?: string
312
- order: number
313
- versions?: ApiVersion[] // Available API versions (for openapi type)
314
- graphqlSchemas?: GraphQLSchemaInfo[] // GraphQL schemas (for graphql type)
315
- }
316
-
317
- interface ChangelogRelease {
318
- version: string
319
- date: string
320
- title: string
321
- slug: string
322
- }
323
-
324
- // Extended collection type with apiSummary from server
325
- interface DocsLogo {
326
- url?: string
327
- alt?: string
328
- width?: number
329
- height?: number
330
- light?: string
331
- dark?: string
332
- }
333
-
334
- interface DocsHeaderConfig {
335
- showSearch?: boolean
336
- showThemeToggle?: boolean
337
- }
338
-
339
- interface DocsNavbarLink {
340
- label: string
341
- href: string
342
- external?: boolean
343
- }
344
-
345
- interface DocsNavbarPrimary {
346
- label: string
347
- href: string
348
- }
349
-
350
- interface DocsNavbar {
351
- links?: DocsNavbarLink[]
352
- primary?: DocsNavbarPrimary
353
- }
354
-
355
- interface DocsColors {
356
- primary?: string
357
- primaryLight?: string
358
- primaryDark?: string
359
- }
360
-
361
- interface NoticeConfig {
362
- content: string
363
- dismissible?: boolean
364
- }
365
-
366
- interface CollectionWithSummary extends BrainfishCollection {
367
- apiSummary?: string | null
368
- specVersion?: string
369
- docsName?: string | null
370
- docsFavicon?: string | null
371
- docsLogo?: DocsLogo | null
372
- docsHeader?: DocsHeaderConfig | null
373
- docsNavbar?: DocsNavbar | null
374
- docsColors?: DocsColors | null
375
- defaultTheme?: 'light' | 'dark' | 'system' | null
376
- customCss?: string | null
377
- navigationTabs?: NavigationTab[]
378
- changelogReleases?: ChangelogRelease[]
379
- apiVersions?: ApiVersion[]
380
- selectedApiVersion?: string
381
- notice?: NoticeConfig | null
382
- }
383
-
384
- function DocsContent() {
385
- const [collection, setCollection] = useState<CollectionWithSummary | null>(null)
386
- const [selectedRequest, setSelectedRequest] = useState<BrainfishRESTRequest | null>(null)
387
- const [selectedDocSection, setSelectedDocSection] = useState<string | null>(null)
388
- const [selectedDocPage, setSelectedDocPage] = useState<string | null>(null)
389
- const [activeTab, setActiveTab] = useState<string>('api-reference')
390
- const [selectedApiVersion, setSelectedApiVersion] = useState<string | null>(null)
391
- const [loading, setLoading] = useState(true)
392
- const [isVersionLoading, setIsVersionLoading] = useState(false) // For version switch only
393
- const [error, setError] = useState<string | null>(null)
394
- const [showAuthModal, setShowAuthModal] = useState(false)
395
- const [notFoundSlug, setNotFoundSlug] = useState<string | null>(null) // Track 404 for invalid URLs
396
-
397
- // Prefill context for agent
398
- const { setPrefill } = usePlaygroundPrefill()
399
-
400
- // Playground navigation context for tab navigation
401
- const { navigateAndHighlight, resetNavigation } = usePlaygroundNavigation()
402
-
403
- // Mode context for switching modes
404
- const { switchToDocs } = useModeContext()
405
-
406
- // Ref for the scrollable content area
407
- const contentRef = useRef<HTMLDivElement>(null)
408
-
409
- // Update URL hash without triggering navigation
410
- // Format: #tab or #tab/type/id (e.g., #api-reference, #api-reference/endpoint/123, #guides/page/quickstart)
411
- const updateUrlHash = useCallback((hash: string, tab?: string) => {
412
- const currentTab = tab || activeTab
413
- let newUrl: string
414
-
415
- if (!hash) {
416
- // Just the tab
417
- newUrl = `#${currentTab}`
418
- } else {
419
- // Tab + path
420
- newUrl = `#${currentTab}/${hash}`
421
- }
422
-
423
- window.history.pushState(null, '', newUrl)
424
- }, [activeTab])
425
-
426
- // Navigate to hash - used for initial load and popstate
427
- // Parse hash format: #tab or #tab/type/id
428
- const parseHash = useCallback((hash: string) => {
429
- if (!hash) return { tab: null, type: null, id: null }
430
-
431
- const parts = hash.split('/')
432
- const tab = parts[0] || null
433
- const type = parts[1] || null
434
- const id = parts.slice(2).join('/') || null // Rejoin in case id has slashes
435
-
436
- return { tab, type, id }
437
- }, [])
438
-
439
- const navigateToHash = useCallback((collectionData: BrainfishCollection) => {
440
- const hash = window.location.hash.slice(1) // Remove #
441
-
442
- if (!hash) {
443
- // No hash - auto-select first endpoint
444
- setSelectedDocSection(null)
445
- setSelectedDocPage(null)
446
- const firstEndpoint = getFirstEndpoint(collectionData)
447
- if (firstEndpoint) {
448
- setSelectedRequest(firstEndpoint)
449
- } else {
450
- setSelectedRequest(null)
451
- }
452
- return
453
- }
454
-
455
- // Notes mode is handled by ModeContext, just clear API selection
456
- if (hash === 'notes' || hash.startsWith('notes/')) {
457
- setSelectedRequest(null)
458
- setSelectedDocSection(null)
459
- setSelectedDocPage(null)
460
- return
461
- }
462
-
463
- // Parse new format: #tab/type/id
464
- const { tab, type, id } = parseHash(hash)
465
-
466
- // Set the active tab if specified
467
- if (tab) {
468
- setActiveTab(tab)
469
- }
470
-
471
- // Handle legacy format (endpoint/xxx, page/xxx, doc/xxx without tab prefix)
472
- const legacyType = hash.startsWith('endpoint/') ? 'endpoint'
473
- : hash.startsWith('page/') ? 'page'
474
- : hash.startsWith('doc/') ? 'doc'
475
- : null
476
-
477
- const actualType = type || legacyType
478
- const actualId = id || (legacyType ? hash.replace(`${legacyType}/`, '') : null)
479
-
480
- if (actualType === 'endpoint' && actualId) {
481
- const request = findRequestById(collectionData, actualId)
482
- if (request) {
483
- setSelectedRequest(request)
484
- setSelectedDocSection(null)
485
- setSelectedDocPage(null)
486
- setNotFoundSlug(null)
487
- } else {
488
- // Endpoint not found - show 404 page
489
- setSelectedRequest(null)
490
- setSelectedDocSection(null)
491
- setSelectedDocPage(null)
492
- setNotFoundSlug(`endpoint/${actualId}`)
493
- }
494
- } else if (actualType === 'page' && actualId) {
495
- setNotFoundSlug(null) // Clear not found state - DocPage handles its own 404
496
- setSelectedDocPage(actualId)
497
- setSelectedRequest(null)
498
- setSelectedDocSection(null)
499
- } else if (actualType === 'doc' && actualId) {
500
- setSelectedDocSection(actualId)
501
- setSelectedRequest(null)
502
- setSelectedDocPage(null)
503
-
504
- // Scroll to section after DOM update
505
- setTimeout(() => {
506
- const element = document.getElementById(actualId)
507
- if (element) {
508
- element.scrollIntoView({ behavior: 'smooth', block: 'start' })
509
- }
510
- }, 100)
511
- } else if (tab && !type) {
512
- // Just a tab, no specific content - show default for that tab
513
- setSelectedRequest(null)
514
- setSelectedDocSection(null)
515
- setSelectedDocPage(null)
516
- }
517
- }, [parseHash])
518
-
519
- const handleSelectRequest = useCallback((request: BrainfishRESTRequest) => {
520
- setSelectedRequest(request)
521
- setSelectedDocSection(null)
522
- setSelectedDocPage(null)
523
- setNotFoundSlug(null) // Clear 404 state when selecting an endpoint
524
- updateUrlHash(`endpoint/${request.id}`)
525
- // Reset tab navigation so the new endpoint can determine its default tab
526
- resetNavigation()
527
- // Switch to Docs mode to show endpoint documentation first
528
- switchToDocs()
529
- }, [updateUrlHash, resetNavigation, switchToDocs])
530
-
531
- const handleSelectDocumentation = useCallback((headingId: string) => {
532
- const isIntro = headingId === 'introduction'
533
- setSelectedDocSection(isIntro ? null : headingId)
534
- setSelectedRequest(null)
535
- setSelectedDocPage(null)
536
- setNotFoundSlug(null) // Clear 404 state
537
-
538
- updateUrlHash(isIntro ? '' : `doc/${headingId}`)
539
-
540
- // Switch to Docs mode when selecting documentation
541
- switchToDocs()
542
-
543
- setTimeout(() => {
544
- if (isIntro) {
545
- contentRef.current?.scrollTo({ top: 0, behavior: 'smooth' })
546
- } else {
547
- const element = document.getElementById(headingId)
548
- if (element) {
549
- element.scrollIntoView({ behavior: 'smooth', block: 'start' })
550
- }
551
- }
552
- }, 50)
553
- }, [updateUrlHash, switchToDocs])
554
-
555
- const handleSelectDocPage = useCallback((slug: string) => {
556
- // Find which tab this doc page belongs to
557
- let targetTab = activeTab
558
- let releaseSlug: string | null = null
559
- let sectionId: string | null = null
560
-
561
- // Parse section from slug (e.g., "essentials/markdown#headings" -> slug: "essentials/markdown", section: "headings")
562
- let pageSlug = slug
563
- if (slug.includes('#')) {
564
- const [pagePart, sectionPart] = slug.split('#')
565
- pageSlug = pagePart
566
- sectionId = sectionPart
567
- }
568
-
569
- // Special handling for changelog pages
570
- if (pageSlug.startsWith('changelog/') || pageSlug === 'changelog') {
571
- // Find the changelog tab
572
- const changelogTab = collection?.navigationTabs?.find(t => t.type === 'changelog')
573
- if (changelogTab) {
574
- targetTab = changelogTab.id
575
- }
576
- // Extract the release slug for scrolling (e.g., "changelog/v1.2.0" -> "v1.2.0")
577
- if (pageSlug.startsWith('changelog/')) {
578
- releaseSlug = pageSlug.replace('changelog/', '')
579
- }
580
- } else if (collection?.docGroups) {
581
- for (const group of collection.docGroups) {
582
- const hasPage = (pages: typeof group.pages): boolean => {
583
- for (const page of pages) {
584
- if (page.slug === pageSlug) return true
585
- if (page.children && hasPage(page.children)) return true
586
- }
587
- return false
588
- }
589
- if (hasPage(group.pages)) {
590
- // Extract tab from group ID (e.g., "group-guides-getting-started" -> "guides")
591
- const tabPart = group.id.replace('group-', '').split('-')[0]
592
- // Find the actual tab ID that matches
593
- const matchingTab = collection.navigationTabs?.find(t =>
594
- t.id === tabPart || t.id.startsWith(tabPart)
595
- )
596
- if (matchingTab) {
597
- targetTab = matchingTab.id
598
- }
599
- break
600
- }
601
- }
602
- }
603
-
604
- // Switch to the correct tab
605
- setActiveTab(targetTab)
606
- setSelectedDocPage(pageSlug)
607
- setSelectedRequest(null)
608
- setSelectedDocSection(null)
609
- setNotFoundSlug(null) // Clear 404 state
610
- updateUrlHash(`page/${pageSlug}`, targetTab)
611
- switchToDocs()
612
-
613
- // Scroll to specific release if navigating to a changelog entry
614
- if (releaseSlug) {
615
- // Wait for the changelog page to render, then scroll to the release
616
- setTimeout(() => {
617
- const releaseElement = document.getElementById(`release-${releaseSlug}`)
618
- if (releaseElement) {
619
- releaseElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
620
- }
621
- }, 600)
622
- } else if (sectionId) {
623
- // Scroll to section within the page after it renders
624
- setTimeout(() => {
625
- const sectionElement = document.getElementById(sectionId)
626
- if (sectionElement) {
627
- sectionElement.scrollIntoView({ behavior: 'smooth', block: 'start' })
628
- }
629
- }, 300)
630
- } else {
631
- // Scroll to top of content when navigating to a new page
632
- setTimeout(() => {
633
- contentRef.current?.scrollTo({ top: 0, behavior: 'smooth' })
634
- }, 50)
635
- }
636
- }, [updateUrlHash, switchToDocs, collection, activeTab])
637
-
638
- // Handle API version change
639
- const handleApiVersionChange = useCallback((version: string) => {
640
- if (version !== selectedApiVersion) {
641
- setSelectedApiVersion(version)
642
- // Clear selected request when switching versions as endpoints may differ
643
- setSelectedRequest(null)
644
- setSelectedDocSection(null)
645
- }
646
- }, [selectedApiVersion])
647
-
648
- // Handle tab change from header
649
- const handleTabChange = useCallback((tabId: string) => {
650
- setActiveTab(tabId)
651
-
652
- // Reset mode to docs when switching tabs (exit sandbox/api-client mode)
653
- switchToDocs()
654
-
655
- // Find the tab config to check its type
656
- const tabConfig = collection?.navigationTabs?.find(t => t.id === tabId)
657
- const isApiTab = tabConfig?.type === 'openapi' || tabConfig?.type === 'graphql' || tabId === 'api-reference'
658
-
659
- if (tabId === 'changelog') {
660
- // Switch to Changelog tab
661
- setSelectedDocPage(null)
662
- setSelectedRequest(null)
663
- setSelectedDocSection(null)
664
- updateUrlHash('', tabId)
665
- } else if (isApiTab) {
666
- // API Reference or GraphQL tab
667
- setSelectedDocSection(null)
668
-
669
- // Check if there are doc groups for this tab (MDX pages)
670
- const hasGroups = hasDocGroupsForTab(collection?.docGroups, tabId)
671
-
672
- if (hasGroups) {
673
- // Has doc groups - select the first page from groups
674
- const firstPage = getFirstDocPageForTab(collection?.docGroups, tabId)
675
- if (firstPage) {
676
- setSelectedDocPage(firstPage)
677
- setSelectedRequest(null)
678
- updateUrlHash(`page/${firstPage}`, tabId)
679
- switchToDocs()
680
- } else {
681
- // No pages in groups - auto-select first endpoint
682
- setSelectedDocPage(null)
683
- const firstEndpoint = collection ? getFirstEndpoint(collection) : null
684
- if (firstEndpoint) {
685
- setSelectedRequest(firstEndpoint)
686
- updateUrlHash(`endpoint/${firstEndpoint.id}`, tabId)
687
- } else {
688
- setSelectedRequest(null)
689
- updateUrlHash('', tabId)
690
- }
691
- }
692
- } else {
693
- // No doc groups - auto-select first endpoint
694
- setSelectedDocPage(null)
695
- const firstEndpoint = collection ? getFirstEndpoint(collection) : null
696
- if (firstEndpoint) {
697
- setSelectedRequest(firstEndpoint)
698
- updateUrlHash(`endpoint/${firstEndpoint.id}`, tabId)
699
- } else {
700
- setSelectedRequest(null)
701
- updateUrlHash('', tabId)
702
- }
703
- }
704
- } else {
705
- // Switch to a doc group tab - find and select the first page in that tab
706
- setSelectedRequest(null)
707
- setSelectedDocSection(null)
708
-
709
- // Find the first doc group for this tab and select its first page
710
- if (collection?.docGroups) {
711
- const tabDocGroup = collection.docGroups.find(g => isGroupForTab(g.id, tabId))
712
-
713
- if (tabDocGroup && tabDocGroup.pages.length > 0) {
714
- const firstPage = tabDocGroup.pages[0]
715
- setSelectedDocPage(firstPage.slug)
716
- updateUrlHash(`page/${firstPage.slug}`, tabId)
717
- switchToDocs()
718
- } else {
719
- setSelectedDocPage(null)
720
- updateUrlHash('', tabId)
721
- }
722
- }
723
- }
724
- }, [collection, updateUrlHash, switchToDocs])
725
-
726
- // Handler for agent navigation
727
- const handleAgentNavigate = useCallback((endpointId: string) => {
728
- if (!collection) return
729
- const request = findRequestById(collection, endpointId)
730
- if (request) {
731
- // Set the tab to API Reference first
732
- setActiveTab('api-reference')
733
- // Then set the selected request
734
- setSelectedRequest(request)
735
- setSelectedDocSection(null)
736
- setSelectedDocPage(null)
737
- updateUrlHash(`endpoint/${endpointId}`, 'api-reference')
738
- // Reset tab navigation so the new endpoint can determine its default tab
739
- resetNavigation()
740
- }
741
- }, [collection, updateUrlHash, resetNavigation])
742
-
743
- // Handler for agent prefilling parameters
744
- const handleAgentPrefill = useCallback((data: PrefillData) => {
745
- console.log('[Agent] Prefill requested:', data)
746
- setPrefill(data)
747
- }, [setPrefill])
748
-
749
- // State for debug context - used to trigger agent debugging
750
- const [debugContext, setDebugContext] = useState<DebugContext | null>(null)
751
-
752
- // Handler for debug requests from playground
753
- const handleDebugRequest = useCallback((context: DebugContext) => {
754
- setDebugContext(context)
755
- }, [])
756
-
757
- // Clear debug context after it's been used
758
- const clearDebugContext = useCallback(() => {
759
- setDebugContext(null)
760
- }, [])
761
-
762
- // State for explain context - used to trigger agent explanation
763
- const [explainContext, setExplainContext] = useState<DebugContext | null>(null)
764
-
765
- // Handler for explain requests from playground
766
- const handleExplainRequest = useCallback((context: DebugContext) => {
767
- setExplainContext(context)
768
- }, [])
769
-
770
- // Clear explain context after it's been used
771
- const clearExplainContext = useCallback(() => {
772
- setExplainContext(null)
773
- }, [])
774
-
775
- // Handle browser back/forward
776
- useEffect(() => {
777
- if (!collection) return
778
-
779
- const handlePopState = () => {
780
- navigateToHash(collection)
781
- }
782
-
783
- window.addEventListener('popstate', handlePopState)
784
- return () => window.removeEventListener('popstate', handlePopState)
785
- }, [collection, navigateToHash])
786
-
787
- // Dynamically set favicon from docs.json config
788
- useEffect(() => {
789
- if (!collection?.docsFavicon) return
790
-
791
- const faviconPath = collection.docsFavicon
792
-
793
- // Update or create link elements for favicon
794
- const updateFaviconLink = (rel: string, href: string) => {
795
- let link = document.querySelector(`link[rel="${rel}"]`) as HTMLLinkElement
796
- if (!link) {
797
- link = document.createElement('link')
798
- link.rel = rel
799
- document.head.appendChild(link)
800
- }
801
- link.href = href
802
- }
803
-
804
- // Determine the type based on file extension
805
- const ext = faviconPath.split('.').pop()?.toLowerCase()
806
- const type = ext === 'svg' ? 'image/svg+xml'
807
- : ext === 'png' ? 'image/png'
808
- : ext === 'ico' ? 'image/x-icon'
809
- : 'image/png'
810
-
811
- // Update the favicon link with type
812
- let iconLink = document.querySelector('link[rel="icon"]') as HTMLLinkElement
813
- if (!iconLink) {
814
- iconLink = document.createElement('link')
815
- iconLink.rel = 'icon'
816
- document.head.appendChild(iconLink)
817
- }
818
- iconLink.type = type
819
- iconLink.href = faviconPath
820
-
821
- // Also update shortcut icon and apple-touch-icon
822
- updateFaviconLink('shortcut icon', faviconPath)
823
- updateFaviconLink('apple-touch-icon', faviconPath)
824
- }, [collection?.docsFavicon])
825
-
826
- useEffect(() => {
827
- async function fetchCollection() {
828
- try {
829
- // Only show full loading on initial load
830
- const isInitialLoad = !collection
831
- if (isInitialLoad) {
832
- setLoading(true)
833
- } else {
834
- setIsVersionLoading(true)
835
- }
836
- setError(null)
837
-
838
- // Build URL with version param if selected
839
- const url = selectedApiVersion
840
- ? `/api/collections?version=${encodeURIComponent(selectedApiVersion)}`
841
- : '/api/collections'
842
-
843
- const response = await fetch(url)
844
-
845
- if (!response.ok) {
846
- throw new Error(`Failed to fetch collection: ${response.status}`)
847
- }
848
-
849
- const data = await response.json()
850
- setCollection(data)
851
-
852
- // Set initial API version if not already set
853
- if (!selectedApiVersion && data?.selectedApiVersion) {
854
- setSelectedApiVersion(data.selectedApiVersion)
855
- }
856
-
857
- // Only run initial navigation logic on first load, not on version changes
858
- if (isInitialLoad) {
859
- // Set initial active tab from config (first tab) and select first item
860
- let initialTabId = 'api-reference'
861
- if (data?.navigationTabs && data.navigationTabs.length > 0) {
862
- const sortedTabs = [...data.navigationTabs].sort((a, b) => a.order - b.order)
863
- initialTabId = sortedTabs[0].id
864
- setActiveTab(initialTabId)
865
- }
866
-
867
- // Handle initial hash navigation after collection loads
868
- if (data) {
869
- // Use setTimeout to ensure state is set before navigation
870
- setTimeout(() => {
871
- const hash = window.location.hash.slice(1)
872
-
873
- if (!hash) {
874
- // No hash - set URL to initial tab
875
- setSelectedDocSection(null)
876
-
877
- // Find the tab config to check its type
878
- const tabConfig = data.navigationTabs?.find((t: NavigationTab) => t.id === initialTabId)
879
- const isApiTab = tabConfig?.type === 'openapi' || tabConfig?.type === 'graphql' || initialTabId === 'api-reference'
880
-
881
- if (isApiTab) {
882
- // API Reference or GraphQL tab
883
- setSelectedDocSection(null)
884
-
885
- // Check if there are doc groups for this tab (MDX pages)
886
- const hasGroups = hasDocGroupsForTab(data.docGroups, initialTabId)
887
-
888
- if (hasGroups) {
889
- // Has doc groups - select the first page from groups
890
- const firstPage = getFirstDocPageForTab(data.docGroups, initialTabId)
891
- if (firstPage) {
892
- setSelectedDocPage(firstPage)
893
- setSelectedRequest(null)
894
- updateUrlHash(`page/${firstPage}`, initialTabId)
895
- } else {
896
- // No pages in groups - auto-select first endpoint
897
- setSelectedDocPage(null)
898
- const firstEndpoint = getFirstEndpoint(data)
899
- if (firstEndpoint) {
900
- setSelectedRequest(firstEndpoint)
901
- updateUrlHash(`endpoint/${firstEndpoint.id}`, initialTabId)
902
- } else {
903
- setSelectedRequest(null)
904
- updateUrlHash('', initialTabId)
905
- }
906
- }
907
- } else {
908
- // No doc groups - auto-select first endpoint
909
- setSelectedDocPage(null)
910
- const firstEndpoint = getFirstEndpoint(data)
911
- if (firstEndpoint) {
912
- setSelectedRequest(firstEndpoint)
913
- updateUrlHash(`endpoint/${firstEndpoint.id}`, initialTabId)
914
- } else {
915
- setSelectedRequest(null)
916
- updateUrlHash('', initialTabId)
917
- }
918
- }
919
- switchToDocs()
920
- } else {
921
- // Doc group tab - select first page
922
- setSelectedRequest(null)
923
- const tabDocGroup = data.docGroups?.find((g: BrainfishDocGroup) =>
924
- isGroupForTab(g.id, initialTabId)
925
- )
926
-
927
- if (tabDocGroup && tabDocGroup.pages.length > 0) {
928
- const firstPage = tabDocGroup.pages[0]
929
- setSelectedDocPage(firstPage.slug)
930
- updateUrlHash(`page/${firstPage.slug}`, initialTabId)
931
- switchToDocs()
932
- } else {
933
- setSelectedDocPage(null)
934
- updateUrlHash('', initialTabId)
935
- switchToDocs()
936
- }
937
- }
938
- } else if (hash === 'notes' || hash.startsWith('notes/')) {
939
- // Notes mode - handled by ModeContext, just clear API selection
940
- setSelectedRequest(null)
941
- setSelectedDocSection(null)
942
- } else {
943
- // Parse the hash to get tab and content info
944
- // Format: #tab or #tab/type/id (e.g., #api-reference/endpoint/123)
945
- const parts = hash.split('/')
946
- const hashTab = parts[0]
947
- const hashType = parts[1]
948
- const hashId = parts.slice(2).join('/')
949
-
950
- // Set the tab from the hash
951
- if (hashTab && data.navigationTabs?.some((t: NavigationTab) => t.id === hashTab)) {
952
- setActiveTab(hashTab)
953
- }
954
-
955
- // Handle legacy format (endpoint/xxx without tab prefix)
956
- const isLegacyFormat = hash.startsWith('endpoint/') || hash.startsWith('page/') || hash.startsWith('doc/')
957
- const actualType = isLegacyFormat ? parts[0] : hashType
958
- const actualId = isLegacyFormat ? parts.slice(1).join('/') : hashId
959
-
960
- if (actualType === 'endpoint' && actualId) {
961
- const request = findRequestById(data, actualId)
962
- if (request) {
963
- setSelectedRequest(request)
964
- setSelectedDocSection(null)
965
- setSelectedDocPage(null)
966
- switchToDocs()
967
- return
968
- } else {
969
- setSelectedRequest(null)
970
- setSelectedDocSection(null)
971
- setSelectedDocPage(null)
972
- switchToDocs()
973
- }
974
- } else if (actualType === 'page' && actualId) {
975
- setSelectedDocPage(actualId)
976
- setSelectedRequest(null)
977
- setSelectedDocSection(null)
978
- switchToDocs()
979
- } else if (actualType === 'doc' && actualId) {
980
- setSelectedDocSection(actualId)
981
- setSelectedRequest(null)
982
- setSelectedDocPage(null)
983
- switchToDocs()
984
- setTimeout(() => {
985
- const element = document.getElementById(actualId)
986
- if (element) {
987
- element.scrollIntoView({ behavior: 'smooth', block: 'start' })
988
- }
989
- }, 100)
990
- } else if (hashTab && !hashType) {
991
- // Just a tab, show its default content
992
- setSelectedDocSection(null)
993
-
994
- // Check if this is an API tab
995
- const tabConfig = data.navigationTabs?.find((t: NavigationTab) => t.id === hashTab)
996
- const isApiTab = tabConfig?.type === 'openapi' || tabConfig?.type === 'graphql' || hashTab === 'api-reference'
997
-
998
- if (isApiTab) {
999
- // Check if there are doc groups for this tab (MDX pages)
1000
- const hasGroups = hasDocGroupsForTab(data.docGroups, hashTab)
1001
-
1002
- if (hasGroups) {
1003
- // Has doc groups - select the first page from groups
1004
- const firstPage = getFirstDocPageForTab(data.docGroups, hashTab)
1005
- if (firstPage) {
1006
- setSelectedDocPage(firstPage)
1007
- setSelectedRequest(null)
1008
- updateUrlHash(`page/${firstPage}`, hashTab)
1009
- } else {
1010
- // No pages in groups - auto-select first endpoint
1011
- setSelectedDocPage(null)
1012
- const firstEndpoint = getFirstEndpoint(data)
1013
- if (firstEndpoint) {
1014
- setSelectedRequest(firstEndpoint)
1015
- updateUrlHash(`endpoint/${firstEndpoint.id}`, hashTab)
1016
- } else {
1017
- setSelectedRequest(null)
1018
- }
1019
- }
1020
- } else {
1021
- // No doc groups - auto-select first endpoint
1022
- setSelectedDocPage(null)
1023
- const firstEndpoint = getFirstEndpoint(data)
1024
- if (firstEndpoint) {
1025
- setSelectedRequest(firstEndpoint)
1026
- updateUrlHash(`endpoint/${firstEndpoint.id}`, hashTab)
1027
- } else {
1028
- setSelectedRequest(null)
1029
- }
1030
- }
1031
- } else {
1032
- setSelectedDocPage(null)
1033
- setSelectedRequest(null)
1034
- }
1035
- switchToDocs()
1036
- }
1037
- }
1038
- }, 0)
1039
- }
1040
- } else {
1041
- // Version change - clear selected endpoint as it may not exist in the new version
1042
- setSelectedRequest(null)
1043
- setSelectedDocSection(null)
1044
- }
1045
- } catch (err) {
1046
- console.error('Error fetching collection:', err)
1047
- setError(err instanceof Error ? err.message : 'Failed to load API documentation')
1048
- } finally {
1049
- setLoading(false)
1050
- setIsVersionLoading(false)
1051
- }
1052
- }
1053
-
1054
- fetchCollection()
1055
- // eslint-disable-next-line react-hooks/exhaustive-deps
1056
- }, [switchToDocs, selectedApiVersion])
1057
-
1058
- // Only show full-page loading on initial load
1059
- if (loading && !collection) {
1060
- return (
1061
- <div className="flex items-center justify-center h-screen">
1062
- <div className="text-center">
1063
- <Spinner size={32} className="text-muted-foreground animate-spin mx-auto mb-3" />
1064
- <p className="text-sm text-muted-foreground">Loading API documentation...</p>
1065
- </div>
1066
- </div>
1067
- )
1068
- }
1069
-
1070
- if (error) {
1071
- return (
1072
- <div className="flex items-center justify-center h-screen">
1073
- <div className="text-center">
1074
- <p className="text-destructive text-lg mb-2">Error loading documentation</p>
1075
- <p className="text-muted-foreground">{error}</p>
1076
- </div>
1077
- </div>
1078
- )
1079
- }
1080
-
1081
- if (!collection) {
1082
- return (
1083
- <div className="flex items-center justify-center h-screen">
1084
- <div className="text-center max-w-md">
1085
- <p className="text-muted-foreground mb-2">No API documentation available</p>
1086
- <p className="text-sm text-muted-foreground">
1087
- Please configure your Brainfish API credentials in your environment variables.
1088
- </p>
1089
- </div>
1090
- </div>
1091
- )
1092
- }
1093
-
1094
- // Only show "configure credentials" if there are no endpoints AND no doc groups
1095
- // Multi-tenant docs may have only doc groups without API endpoints
1096
- const hasDocGroups = collection.docGroups && collection.docGroups.length > 0
1097
- const hasEndpoints = collection.requests.length > 0 || collection.folders.length > 0
1098
-
1099
- if (!hasEndpoints && !hasDocGroups) {
1100
- return (
1101
- <div className="flex items-center justify-center h-screen">
1102
- <div className="text-center max-w-lg p-8">
1103
- <h2 className="text-2xl font-semibold mb-2">{collection.name || 'Documentation'}</h2>
1104
- <p className="text-muted-foreground mb-6">
1105
- {collection.description || 'No documentation content available yet.'}
1106
- </p>
1107
- <div className="bg-muted/50 rounded-lg p-5 text-left text-sm space-y-4">
1108
- <p className="font-medium">Get started with DevDoc:</p>
1109
- <div className="space-y-3">
1110
- <div>
1111
- <p className="text-muted-foreground text-xs mb-1">Create a new project</p>
1112
- <code className="block bg-background px-3 py-2 rounded-md font-mono text-xs">
1113
- npx create-devdoc-doc my-docs
1114
- </code>
1115
- </div>
1116
- <div>
1117
- <p className="text-muted-foreground text-xs mb-1">Start local development</p>
1118
- <code className="block bg-background px-3 py-2 rounded-md font-mono text-xs">
1119
- npx devdoc dev
1120
- </code>
1121
- </div>
1122
- <div>
1123
- <p className="text-muted-foreground text-xs mb-1">Deploy to production</p>
1124
- <code className="block bg-background px-3 py-2 rounded-md font-mono text-xs">
1125
- npx devdoc deploy
1126
- </code>
1127
- </div>
1128
- </div>
1129
- <a
1130
- href="https://devdoc.sh"
1131
- target="_blank"
1132
- rel="noopener noreferrer"
1133
- className="inline-flex items-center gap-1.5 text-primary hover:underline text-sm font-medium"
1134
- >
1135
- Learn more at devdoc.sh
1136
- <svg className="h-3.5 w-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor" strokeWidth={2}>
1137
- <path strokeLinecap="round" strokeLinejoin="round" d="M10 6H6a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2v-4M14 4h6m0 0v6m0-6L10 14" />
1138
- </svg>
1139
- </a>
1140
- </div>
1141
- </div>
1142
- </div>
1143
- )
1144
- }
1145
-
1146
- return (
1147
- <NavigationProvider collection={collection} onSelectRequest={handleSelectRequest}>
1148
- <DocsWithMode
1149
- collection={collection}
1150
- selectedRequest={selectedRequest}
1151
- selectedDocSection={selectedDocSection}
1152
- selectedDocPage={selectedDocPage}
1153
- activeTab={activeTab}
1154
- onTabChange={handleTabChange}
1155
- contentRef={contentRef}
1156
- showAuthModal={showAuthModal}
1157
- setShowAuthModal={setShowAuthModal}
1158
- handleSelectRequest={handleSelectRequest}
1159
- handleSelectDocumentation={handleSelectDocumentation}
1160
- handleSelectDocPage={handleSelectDocPage}
1161
- handleDebugRequest={handleDebugRequest}
1162
- handleExplainRequest={handleExplainRequest}
1163
- handleAgentNavigate={handleAgentNavigate}
1164
- handleAgentPrefill={handleAgentPrefill}
1165
- debugContext={debugContext}
1166
- clearDebugContext={clearDebugContext}
1167
- explainContext={explainContext}
1168
- clearExplainContext={clearExplainContext}
1169
- navigateAndHighlight={navigateAndHighlight}
1170
- selectedApiVersion={selectedApiVersion}
1171
- handleApiVersionChange={handleApiVersionChange}
1172
- isVersionLoading={isVersionLoading}
1173
- notFoundSlug={notFoundSlug}
1174
- />
1175
- </NavigationProvider>
1176
- )
1177
- }
1178
-
1179
- // Mode-aware rendering component
1180
- interface DocsWithModeProps {
1181
- collection: CollectionWithSummary
1182
- selectedRequest: BrainfishRESTRequest | null
1183
- selectedDocSection: string | null
1184
- selectedDocPage: string | null
1185
- activeTab: string
1186
- onTabChange: (tabId: string) => void
1187
- contentRef: React.RefObject<HTMLDivElement | null>
1188
- showAuthModal: boolean
1189
- setShowAuthModal: (show: boolean) => void
1190
- handleSelectRequest: (request: BrainfishRESTRequest) => void
1191
- handleSelectDocumentation: (headingId: string) => void
1192
- handleSelectDocPage: (slug: string) => void
1193
- handleDebugRequest: (context: DebugContext) => void
1194
- handleExplainRequest: (context: DebugContext) => void
1195
- handleAgentNavigate: (endpointId: string) => void
1196
- handleAgentPrefill: (data: PrefillData) => void
1197
- debugContext: DebugContext | null
1198
- clearDebugContext: () => void
1199
- explainContext: DebugContext | null
1200
- clearExplainContext: () => void
1201
- navigateAndHighlight: (tab: PlaygroundTab, field?: HighlightField, showError?: boolean) => void
1202
- selectedApiVersion: string | null
1203
- handleApiVersionChange: (version: string) => void
1204
- isVersionLoading: boolean
1205
- notFoundSlug: string | null
1206
- }
1207
-
1208
- // Mode toggle tabs - switches between Docs, API Client, and Notes
1209
- function ModeToggleTabs({ hasEndpoint }: { hasEndpoint: boolean }) {
1210
- const { mode, switchToDocs, switchToApiClient, switchToNotes } = useModeContext()
1211
-
1212
- return (
1213
- <div className="flex items-center gap-1 p-1 rounded-lg bg-muted/50 shrink-0">
1214
- <Button
1215
- variant={mode === 'docs' ? 'secondary' : 'ghost'}
1216
- size="sm"
1217
- onClick={() => switchToDocs()}
1218
- className={cn(
1219
- "text-xs h-7 px-2 sm:px-3 gap-1.5",
1220
- mode === 'docs' ? 'bg-background shadow-sm' : 'text-muted-foreground hover:text-foreground'
1221
- )}
1222
- >
1223
- <Book className="h-3.5 w-3.5" weight="bold" />
1224
- <span className="hidden xs:inline">Docs</span>
1225
- </Button>
1226
- {hasEndpoint && (
1227
- <Button
1228
- variant={mode === 'api_client' ? 'secondary' : 'ghost'}
1229
- size="sm"
1230
- onClick={() => switchToApiClient()}
1231
- className={cn(
1232
- "text-xs h-7 px-2 sm:px-3 gap-1.5",
1233
- mode === 'api_client' ? 'bg-background shadow-sm' : 'text-muted-foreground hover:text-foreground'
1234
- )}
1235
- >
1236
- <TestTube className="h-3.5 w-3.5" weight="bold" />
1237
- <span className="hidden xs:inline">API Client</span>
1238
- </Button>
1239
- )}
1240
- <Button
1241
- variant={mode === 'notes' ? 'secondary' : 'ghost'}
1242
- size="sm"
1243
- onClick={() => switchToNotes()}
1244
- className={cn(
1245
- "text-xs h-7 px-2 sm:px-3 gap-1.5",
1246
- mode === 'notes' ? 'bg-background shadow-sm' : 'text-muted-foreground hover:text-foreground'
1247
- )}
1248
- >
1249
- <Code className="h-3.5 w-3.5" weight="bold" />
1250
- <span className="hidden xs:inline">Sandbox</span>
1251
- </Button>
1252
- </div>
1253
- )
1254
- }
1255
-
1256
- function DocsWithMode({
1257
- collection,
1258
- selectedRequest,
1259
- selectedDocSection,
1260
- selectedDocPage,
1261
- activeTab,
1262
- onTabChange,
1263
- contentRef,
1264
- showAuthModal,
1265
- setShowAuthModal,
1266
- handleSelectRequest,
1267
- handleSelectDocumentation,
1268
- handleSelectDocPage,
1269
- handleDebugRequest,
1270
- handleExplainRequest,
1271
- handleAgentNavigate,
1272
- handleAgentPrefill,
1273
- debugContext,
1274
- clearDebugContext,
1275
- explainContext,
1276
- clearExplainContext,
1277
- navigateAndHighlight,
1278
- selectedApiVersion,
1279
- handleApiVersionChange,
1280
- isVersionLoading,
1281
- notFoundSlug,
1282
- }: DocsWithModeProps) {
1283
- const { mode, switchToDocs } = useModeContext()
1284
- const { setTheme } = useTheme()
1285
-
1286
- // GraphQL state
1287
- const [graphqlOperations, setGraphqlOperations] = useState<GraphQLOperationItem[]>([])
1288
- const [graphqlCollection, setGraphqlCollection] = useState<BrainfishCollection | null>(null)
1289
- const [selectedGraphQLOperation, setSelectedGraphQLOperation] = useState<GraphQLOperationItem | null>(null)
1290
-
1291
- // Get API spec URL from environment or collection
1292
- const apiSpecUrl = process.env.NEXT_PUBLIC_OPENAPI_URL || collection.name || 'default'
1293
-
1294
- // Check if there are endpoints
1295
- const hasEndpoints = collection.folders.length > 0 || collection.requests.length > 0
1296
-
1297
- // Search functionality
1298
- const { isOpen: searchOpen, setIsOpen: setSearchOpen, openSearch, searchItems } = useSearch({
1299
- requests: collection.requests,
1300
- folders: collection.folders,
1301
- docGroups: collection.docGroups,
1302
- })
1303
-
1304
- // Handle search item selection - use URL hash navigation
1305
- const handleSearchSelect = useCallback((item: { id: string; type: string; href: string }) => {
1306
- if (item.type === 'endpoint') {
1307
- // Navigate to endpoint via hash - format: #api-reference/endpoint/{id}
1308
- window.location.hash = `api-reference/endpoint/${item.id}`
1309
- } else if (item.type === 'doc') {
1310
- // Navigate to doc page via hash - the href already has the correct format
1311
- // Extract tab and path from href (e.g., #guides/page/quickstart)
1312
- const hashPath = item.href.replace('#', '')
1313
- window.location.hash = hashPath
1314
- }
1315
- }, [])
1316
-
1317
- // Wrap agent navigate to switch to Docs mode and navigate to endpoint
1318
- const handleAgentNavigateWithModeSwitch = useCallback((endpointId: string) => {
1319
- // Just navigate - handleAgentNavigate already sets the tab via setActiveTab
1320
- handleAgentNavigate(endpointId)
1321
- }, [handleAgentNavigate])
1322
-
1323
- // Get the current content title for header
1324
- // Find the active tab's config
1325
- const activeTabConfig = collection.navigationTabs?.find(t => t.id === activeTab)
1326
- const activeTabType = activeTabConfig?.type || 'docs'
1327
-
1328
-
1329
- // Filter doc groups for sidebar based on active tab
1330
- // Group IDs are like "group-guides-getting-started", tab IDs are like "guides"
1331
- // Filter doc groups for sidebar based on active tab
1332
- // Group IDs are formatted as: group-{tab-id}-{group-name}
1333
- const filteredDocGroups = collection.docGroups?.filter(g => isGroupForTab(g.id, activeTab)) || []
1334
-
1335
- // Show endpoints in sidebar only when OpenAPI tab is active
1336
- const showEndpoints = activeTabType === 'openapi'
1337
-
1338
- // Show changelog when changelog tab is active
1339
- const showChangelog = activeTabType === 'changelog'
1340
-
1341
- // Show GraphQL playground when graphql tab is active
1342
- const showGraphQL = activeTabType === 'graphql'
1343
- const activeGraphQLSchemas = activeTabConfig?.graphqlSchemas || []
1344
-
1345
- // Get the first schema path for stable dependency
1346
- const schemaPath = activeGraphQLSchemas[0]?.schema
1347
- const schemaEndpoint = activeGraphQLSchemas[0]?.endpoint
1348
-
1349
- // Load GraphQL operations when graphql tab is active
1350
- useEffect(() => {
1351
- if (!showGraphQL || !schemaPath) {
1352
- setGraphqlOperations([])
1353
- setGraphqlCollection(null)
1354
- setSelectedGraphQLOperation(null) // Clear selection when leaving GraphQL tab
1355
- return
1356
- }
1357
-
1358
- // Load and parse GraphQL schema
1359
- const loadGraphQLSchema = async () => {
1360
- try {
1361
- const response = await fetch(`/api/schema?path=${encodeURIComponent(schemaPath)}`)
1362
- if (!response.ok) return
1363
-
1364
- const schemaContent = await response.text()
1365
- const operations = parseGraphQLSchema(schemaContent)
1366
- setGraphqlOperations(operations)
1367
-
1368
- // Convert to BrainfishCollection for sidebar
1369
- const gqlCollection = convertGraphQLToCollection(operations, schemaEndpoint || '')
1370
- setGraphqlCollection(gqlCollection)
1371
- } catch (err) {
1372
- console.error('Failed to load GraphQL schema:', err)
1373
- }
1374
- }
1375
-
1376
- loadGraphQLSchema()
1377
- }, [showGraphQL, schemaPath, schemaEndpoint])
1378
-
1379
- // Auto-select GraphQL operation from URL hash or default to first operation
1380
- useEffect(() => {
1381
- if (!showGraphQL || graphqlOperations.length === 0) {
1382
- return
1383
- }
1384
-
1385
- // Check if there's a selection in the URL hash first
1386
- const hash = window.location.hash.slice(1) // Remove #
1387
- if (hash) {
1388
- const parts = hash.split('/')
1389
- const hashType = parts[1]
1390
- const hashId = parts.slice(2).join('/')
1391
-
1392
- if (hashType === 'endpoint' && hashId) {
1393
- // Try to find and select the operation from the URL hash
1394
- const operation = graphqlOperations.find(op => op.id === hashId)
1395
- if (operation) {
1396
- setSelectedGraphQLOperation(operation)
1397
- return
1398
- }
1399
- }
1400
- }
1401
-
1402
- // If already have a selection, don't override it
1403
- if (selectedGraphQLOperation) {
1404
- return
1405
- }
1406
-
1407
- // Check if there are doc groups for this tab first
1408
- const hasGroups = hasDocGroupsForTab(collection?.docGroups, activeTab)
1409
- if (!hasGroups) {
1410
- // No doc groups - auto-select first GraphQL operation
1411
- const firstOperation = graphqlOperations[0]
1412
- setSelectedGraphQLOperation(firstOperation)
1413
- window.history.pushState(null, '', `#${activeTab}/endpoint/${firstOperation.id}`)
1414
- }
1415
- }, [showGraphQL, graphqlOperations, selectedGraphQLOperation, activeTab, collection?.docGroups])
1416
-
1417
- // Handle GraphQL operation selection from sidebar
1418
- const handleSelectGraphQLOperation = useCallback((request: BrainfishRESTRequest) => {
1419
- // Find the matching GraphQL operation
1420
- const operation = graphqlOperations.find(op => op.id === request.id)
1421
- if (operation) {
1422
- setSelectedGraphQLOperation(operation)
1423
- // Update URL hash
1424
- window.history.pushState(null, '', `#${activeTab}/endpoint/${request.id}`)
1425
- switchToDocs()
1426
- }
1427
- }, [graphqlOperations, activeTab, switchToDocs])
1428
-
1429
- // Inject custom CSS and color variables
1430
- useEffect(() => {
1431
- // Create or update style element for custom CSS
1432
- let styleEl = document.getElementById('docs-custom-css') as HTMLStyleElement | null
1433
- if (!styleEl) {
1434
- styleEl = document.createElement('style')
1435
- styleEl.id = 'docs-custom-css'
1436
- document.head.appendChild(styleEl)
1437
- }
1438
-
1439
- // Build CSS with color variables
1440
- let cssContent = ''
1441
-
1442
- // Add color variables if provided
1443
- // We set both --docs-primary (for custom CSS) and --primary (for Tailwind's bg-primary class)
1444
- if (collection.docsColors) {
1445
- const colors = collection.docsColors
1446
- cssContent += `:root {\n`
1447
- if (colors.primary) {
1448
- cssContent += ` --docs-primary: ${colors.primary};\n`
1449
- cssContent += ` --primary: ${colors.primary};\n`
1450
- }
1451
- if (colors.primaryLight) cssContent += ` --docs-primary-light: ${colors.primaryLight};\n`
1452
- if (colors.primaryDark) cssContent += ` --docs-primary-dark: ${colors.primaryDark};\n`
1453
- cssContent += `}\n`
1454
- // Also set for dark mode
1455
- cssContent += `.dark {\n`
1456
- if (colors.primary) cssContent += ` --primary: ${colors.primary};\n`
1457
- cssContent += `}\n\n`
1458
- }
1459
-
1460
- // Add custom CSS if provided
1461
- if (collection.customCss) {
1462
- cssContent += collection.customCss
1463
- }
1464
-
1465
- styleEl.textContent = cssContent
1466
-
1467
- return () => {
1468
- // Cleanup on unmount
1469
- const el = document.getElementById('docs-custom-css')
1470
- if (el) el.remove()
1471
- }
1472
- }, [collection.customCss, collection.docsColors])
1473
-
1474
- // Apply default theme from theme.json on initial load
1475
- // Use a ref to track if we've already applied the default theme
1476
- const hasAppliedDefaultTheme = useRef(false)
1477
- useEffect(() => {
1478
- if (collection?.defaultTheme && !hasAppliedDefaultTheme.current) {
1479
- setTheme(collection.defaultTheme)
1480
- hasAppliedDefaultTheme.current = true
1481
- }
1482
- }, [collection?.defaultTheme, setTheme])
1483
-
1484
- return (
1485
- <div className="docs-viewer-root flex flex-col h-screen overflow-hidden">
1486
- {/* Notice */}
1487
- {collection.notice && (
1488
- <Notice config={collection.notice} storageKey="docs-notice" />
1489
- )}
1490
-
1491
- {/* Search Dialog */}
1492
- <SearchDialog
1493
- open={searchOpen}
1494
- onOpenChange={setSearchOpen}
1495
- items={searchItems}
1496
- onSelect={handleSearchSelect}
1497
- />
1498
-
1499
- {/* Header with tabs */}
1500
- <DocsHeader
1501
- docGroups={collection.docGroups}
1502
- navigationTabs={collection.navigationTabs}
1503
- activeTab={activeTab}
1504
- onTabChange={onTabChange}
1505
- hasEndpoints={hasEndpoints}
1506
- docsName={collection.docsName}
1507
- docsLogo={collection.docsLogo}
1508
- onSearchClick={openSearch}
1509
- docsHeader={collection.docsHeader}
1510
- docsNavbar={collection.docsNavbar}
1511
- />
1512
-
1513
- <div className="docs-layout flex flex-1 overflow-hidden relative z-0 min-h-0">
1514
- {/* Left Sidebar - Hidden for changelog */}
1515
- {!showChangelog && (
1516
- <DocsSidebar
1517
- collection={{
1518
- ...collection,
1519
- // Show GraphQL operations when GraphQL tab is active, else show REST endpoints
1520
- folders: showGraphQL && graphqlCollection
1521
- ? graphqlCollection.folders
1522
- : (showEndpoints ? collection.folders : []),
1523
- requests: showGraphQL && graphqlCollection
1524
- ? graphqlCollection.requests
1525
- : (showEndpoints ? collection.requests : []),
1526
- // Only show filtered doc groups
1527
- docGroups: filteredDocGroups,
1528
- }}
1529
- selectedRequest={showGraphQL ? (selectedGraphQLOperation ? { id: selectedGraphQLOperation.id } as BrainfishRESTRequest : null) : selectedRequest}
1530
- selectedDocSection={selectedDocSection}
1531
- selectedDocPage={selectedDocPage}
1532
- activeTab={activeTab}
1533
- onSelectRequest={showGraphQL ? handleSelectGraphQLOperation : handleSelectRequest}
1534
- onSelectDocumentation={handleSelectDocumentation}
1535
- onSelectDocPage={handleSelectDocPage}
1536
- apiVersions={collection.apiVersions}
1537
- selectedApiVersion={selectedApiVersion}
1538
- onApiVersionChange={handleApiVersionChange}
1539
- isVersionLoading={isVersionLoading}
1540
- />
1541
- )}
1542
-
1543
- {/* Center - Toggles between API Client/Playground and Notes */}
1544
- <div className="docs-main flex-1 flex flex-col overflow-hidden" style={{ minWidth: 0, flexBasis: 0, flexGrow: 1 }}>
1545
- {/* Mode Toggle Header */}
1546
- <div className="docs-main-header flex items-center justify-end px-3 sm:px-4 h-[41px] border-b border-border bg-muted/30 flex-shrink-0">
1547
- <ModeToggleTabs hasEndpoint={!!selectedRequest} />
1548
- </div>
1549
-
1550
- {/* Content Area */}
1551
- {mode === 'docs' ? (
1552
- <DocsNavigationProvider
1553
- onNavigateToPage={handleSelectDocPage}
1554
- onSwitchTab={onTabChange}
1555
- activeTab={activeTab}
1556
- >
1557
- <div
1558
- ref={contentRef}
1559
- className={cn(
1560
- "docs-content-area flex-1 bg-background min-w-0",
1561
- (showChangelog || (showGraphQL && selectedGraphQLOperation)) ? "overflow-hidden flex flex-col" : "overflow-y-auto scroll-smooth"
1562
- )}
1563
- >
1564
- {showGraphQL && selectedGraphQLOperation ? (
1565
- <div className="flex-1 flex flex-col h-full">
1566
- <GraphQLPlayground
1567
- endpoint={activeGraphQLSchemas[0]?.endpoint || ''}
1568
- defaultQuery={selectedGraphQLOperation?.query}
1569
- operations={graphqlOperations}
1570
- selectedOperationId={selectedGraphQLOperation?.id}
1571
- hideExplorer={true}
1572
- headers={{}}
1573
- theme="dark"
1574
- />
1575
- </div>
1576
- ) : showChangelog ? (
1577
- <ChangelogPage
1578
- releases={collection.changelogReleases || []}
1579
- tabName={activeTabConfig?.tab}
1580
- />
1581
- ) : selectedRequest ? (
1582
- <RequestDetails request={selectedRequest} />
1583
- ) : selectedDocPage ? (
1584
- <DocPage slug={selectedDocPage} onSearch={openSearch} />
1585
- ) : notFoundSlug ? (
1586
- <NotFoundPage slug={notFoundSlug} onSearch={openSearch} />
1587
- ) : (
1588
- <div className="flex-1 flex items-center justify-center bg-background">
1589
- <div className="text-center text-muted-foreground">
1590
- <Book className="h-12 w-12 mx-auto mb-3 opacity-40" />
1591
- <p className="text-sm">Select an endpoint from the sidebar</p>
1592
- </div>
1593
- </div>
1594
- )}
1595
- </div>
1596
- </DocsNavigationProvider>
1597
- ) : mode === 'notes' ? (
1598
- <div className="flex-1 flex flex-col min-h-0 overflow-hidden">
1599
- <NotesMode apiSpecUrl={apiSpecUrl} apiName={collection.name} />
1600
- </div>
1601
- ) : selectedRequest ? (
1602
- <div className="flex-1 flex flex-col min-h-0 overflow-hidden">
1603
- <ApiPlayground
1604
- request={selectedRequest}
1605
- onDebugRequest={handleDebugRequest}
1606
- onExplainRequest={handleExplainRequest}
1607
- />
1608
- </div>
1609
- ) : (
1610
- <div className="flex-1 flex items-center justify-center bg-background">
1611
- <div className="text-center text-muted-foreground">
1612
- <TestTube className="h-12 w-12 mx-auto mb-3 opacity-40" />
1613
- <p className="text-sm">Select an endpoint from the sidebar to test</p>
1614
- </div>
1615
- </div>
1616
- )}
1617
- </div>
1618
-
1619
- {/* Right Sidebar - Agent Panel (drawer on mobile, always visible on desktop) */}
1620
- <RightSidebar
1621
- request={selectedRequest}
1622
- collection={collection}
1623
- apiSummary={collection.apiSummary}
1624
- onNavigateToEndpoint={handleAgentNavigateWithModeSwitch}
1625
- onPrefillParameters={handleAgentPrefill}
1626
- debugContext={debugContext}
1627
- onClearDebugContext={clearDebugContext}
1628
- explainContext={explainContext}
1629
- onClearExplainContext={clearExplainContext}
1630
- onOpenGlobalAuth={() => setShowAuthModal(true)}
1631
- onNavigateToAuthTab={() => {
1632
- navigateAndHighlight('auth', { type: 'auth' }, true)
1633
- }}
1634
- onNavigateToParamsTab={() => {
1635
- navigateAndHighlight('params', { type: 'param' }, true)
1636
- }}
1637
- onNavigateToBodyTab={() => {
1638
- navigateAndHighlight('body', { type: 'body' }, true)
1639
- }}
1640
- onNavigateToHeadersTab={() => {
1641
- navigateAndHighlight('headers', { type: 'header' }, true)
1642
- }}
1643
- onNavigateToDocSection={handleSelectDocumentation}
1644
- onNavigateToDocPage={handleSelectDocPage}
1645
- />
1646
- </div>
1647
-
1648
- {/* Global Auth Modal */}
1649
- <GlobalAuthModal
1650
- open={showAuthModal}
1651
- onClose={() => setShowAuthModal(false)}
1652
- />
1653
- </div>
1654
- )
1655
- }
1656
-
1657
- // Wrapper component with providers
1658
- export function Docs() {
1659
- return (
1660
- <AuthProvider>
1661
- <ModeProvider>
1662
- <PlaygroundProvider>
1663
- <PlaygroundNavigationProvider>
1664
- <DocsContent />
1665
- </PlaygroundNavigationProvider>
1666
- </PlaygroundProvider>
1667
- </ModeProvider>
1668
- </AuthProvider>
1669
- )
1670
- }