@brainfish-ai/devdoc 0.1.41 → 0.1.43

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 (400) hide show
  1. package/ai-agents/.claude/skills/bootstrap-docs/SKILL.md +710 -79
  2. package/ai-agents/.claude/skills/check-docs/SKILL.md +83 -8
  3. package/ai-agents/.claude/skills/create-doc/SKILL.md +267 -55
  4. package/ai-agents/.claude/skills/update-doc/SKILL.md +162 -63
  5. package/ai-agents/.cursor/rules/devdoc-bootstrap.mdc +145 -15
  6. package/ai-agents/.cursor/rules/devdoc-create.mdc +108 -57
  7. package/ai-agents/.cursor/rules/devdoc-update.mdc +93 -70
  8. package/ai-agents/.cursor/rules/devdoc.mdc +21 -0
  9. package/ai-agents/schemas/docs.schema.json +332 -0
  10. package/ai-agents/schemas/theme.schema.json +243 -0
  11. package/dist/cli/commands/create.js +4 -9
  12. package/dist/cli/commands/deploy.js +50 -25
  13. package/dist/cli/commands/dev.js +19 -10
  14. package/package.json +3 -2
  15. package/renderer/app/api/assets/[...path]/route.js +108 -0
  16. package/renderer/app/api/assets/route.js +114 -0
  17. package/renderer/app/api/assets/upload/route.js +163 -0
  18. package/renderer/app/api/auth-schemes/route.js +58 -0
  19. package/renderer/app/api/chat/route.js +759 -0
  20. package/renderer/app/api/codegen/route.js +52 -0
  21. package/renderer/app/api/collections/route.js +675 -0
  22. package/renderer/app/api/debug/route.js +47 -0
  23. package/renderer/app/api/deploy/route.js +199 -0
  24. package/renderer/app/api/device/route.js +36 -0
  25. package/renderer/app/api/docs/route.js +205 -0
  26. package/renderer/app/api/domains/add/route.js +121 -0
  27. package/renderer/app/api/domains/lookup/route.js +43 -0
  28. package/renderer/app/api/domains/remove/route.js +89 -0
  29. package/renderer/app/api/domains/status/route.js +140 -0
  30. package/renderer/app/api/domains/verify/route.js +168 -0
  31. package/renderer/app/api/keys/regenerate/route.js +71 -0
  32. package/renderer/app/api/local-assets/[...path]/route.js +108 -0
  33. package/renderer/app/api/openapi-spec/route.js +73 -0
  34. package/renderer/app/api/projects/[slug]/route.js +129 -0
  35. package/renderer/app/api/projects/[slug]/stats/route.js +80 -0
  36. package/renderer/app/api/projects/register/route.js +176 -0
  37. package/renderer/app/api/proxy/route.js +139 -0
  38. package/renderer/app/api/proxy-stream/route.js +156 -0
  39. package/renderer/app/api/redirects/route.js +35 -0
  40. package/renderer/app/api/schema/route.js +85 -0
  41. package/renderer/app/api/subdomains/check/route.js +158 -0
  42. package/renderer/app/api/suggestions/route.js +175 -0
  43. package/renderer/app/layout.js +47 -0
  44. package/renderer/app/llms-full.txt/route.js +257 -0
  45. package/renderer/app/llms.txt/route.js +219 -0
  46. package/renderer/app/page.js +12 -0
  47. package/renderer/app/robots.txt/route.js +66 -0
  48. package/renderer/app/sitemap.xml/route.js +145 -0
  49. package/renderer/components/docs/index.js +8 -0
  50. package/renderer/components/docs/mdx/accordion.js +113 -0
  51. package/renderer/components/docs/mdx/badge.js +72 -0
  52. package/renderer/components/docs/mdx/callouts.js +137 -0
  53. package/renderer/components/docs/mdx/cards.js +175 -0
  54. package/renderer/components/docs/mdx/changelog.js +100 -0
  55. package/renderer/components/docs/mdx/code-block.js +147 -0
  56. package/renderer/components/docs/mdx/code-group.js +287 -0
  57. package/renderer/components/docs/mdx/file-embeds.js +82 -0
  58. package/renderer/components/docs/mdx/frame.js +59 -0
  59. package/renderer/components/docs/mdx/highlight.js +90 -0
  60. package/renderer/components/docs/mdx/iframe.js +69 -0
  61. package/renderer/components/docs/mdx/image.js +135 -0
  62. package/renderer/components/docs/mdx/index.js +134 -0
  63. package/renderer/components/docs/mdx/landing.js +315 -0
  64. package/renderer/components/docs/mdx/mermaid.js +212 -0
  65. package/renderer/components/docs/mdx/param-field.js +112 -0
  66. package/renderer/components/docs/mdx/steps.js +74 -0
  67. package/renderer/components/docs/mdx/tabs.js +50 -0
  68. package/renderer/components/docs/mdx-renderer.js +77 -0
  69. package/renderer/components/docs/navigation/breadcrumbs.js +64 -0
  70. package/renderer/components/docs/navigation/index.js +6 -0
  71. package/renderer/components/docs/navigation/page-nav.js +57 -0
  72. package/renderer/components/docs/navigation/sidebar.js +375 -0
  73. package/renderer/components/docs/navigation/toc.js +89 -0
  74. package/renderer/components/docs/notice.js +77 -0
  75. package/renderer/components/docs-header.js +202 -0
  76. package/renderer/components/docs-viewer/agent/agent-chat.js +1930 -0
  77. package/renderer/components/docs-viewer/agent/cards/debug-context-card.js +107 -0
  78. package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.js +57 -0
  79. package/renderer/components/docs-viewer/agent/cards/index.js +45 -0
  80. package/renderer/components/docs-viewer/agent/cards/response-options-card.js +154 -0
  81. package/renderer/components/docs-viewer/agent/cards/types.js +22 -0
  82. package/renderer/components/docs-viewer/agent/chat-message.js +2 -0
  83. package/renderer/components/docs-viewer/agent/index.js +4 -0
  84. package/renderer/components/docs-viewer/agent/messages/assistant-message.js +108 -0
  85. package/renderer/components/docs-viewer/agent/messages/chat-message.js +34 -0
  86. package/renderer/components/docs-viewer/agent/messages/index.js +6 -0
  87. package/renderer/components/docs-viewer/agent/messages/tool-call-display.js +1065 -0
  88. package/renderer/components/docs-viewer/agent/messages/types.js +2 -0
  89. package/renderer/components/docs-viewer/agent/messages/typing-indicator.js +26 -0
  90. package/renderer/components/docs-viewer/agent/messages/user-message.js +37 -0
  91. package/renderer/components/docs-viewer/code-editor/{index.tsx → index.js} +1 -1
  92. package/renderer/components/docs-viewer/code-editor/notes-mode.js +1338 -0
  93. package/renderer/components/docs-viewer/content/changelog-page.js +297 -0
  94. package/renderer/components/docs-viewer/content/doc-page.js +264 -0
  95. package/renderer/components/docs-viewer/content/documentation-viewer.js +14 -0
  96. package/renderer/components/docs-viewer/content/index.js +29 -0
  97. package/renderer/components/docs-viewer/content/not-found-page.js +300 -0
  98. package/renderer/components/docs-viewer/content/request-details.js +528 -0
  99. package/renderer/components/docs-viewer/content/sections/auth.js +108 -0
  100. package/renderer/components/docs-viewer/content/sections/body.js +80 -0
  101. package/renderer/components/docs-viewer/content/sections/headers.js +64 -0
  102. package/renderer/components/docs-viewer/content/sections/overview.js +56 -0
  103. package/renderer/components/docs-viewer/content/sections/parameters.js +64 -0
  104. package/renderer/components/docs-viewer/content/sections/responses.js +91 -0
  105. package/renderer/components/docs-viewer/global-auth-modal.js +427 -0
  106. package/renderer/components/docs-viewer/index.js +1552 -0
  107. package/renderer/components/docs-viewer/playground/auth-editor.js +418 -0
  108. package/renderer/components/docs-viewer/playground/body-editor.js +240 -0
  109. package/renderer/components/docs-viewer/playground/code-editor.js +135 -0
  110. package/renderer/components/docs-viewer/playground/code-snippet.js +393 -0
  111. package/renderer/components/docs-viewer/playground/graphql-playground.js +734 -0
  112. package/renderer/components/docs-viewer/playground/index.js +682 -0
  113. package/renderer/components/docs-viewer/playground/key-value-editor.js +317 -0
  114. package/renderer/components/docs-viewer/playground/method-selector.js +65 -0
  115. package/renderer/components/docs-viewer/playground/request-builder.js +181 -0
  116. package/renderer/components/docs-viewer/playground/request-tabs.js +240 -0
  117. package/renderer/components/docs-viewer/playground/response-cards/idle-card.js +42 -0
  118. package/renderer/components/docs-viewer/playground/response-cards/index.js +72 -0
  119. package/renderer/components/docs-viewer/playground/response-cards/loading-card.js +24 -0
  120. package/renderer/components/docs-viewer/playground/response-cards/network-error-card.js +28 -0
  121. package/renderer/components/docs-viewer/playground/response-cards/response-body-card.js +308 -0
  122. package/renderer/components/docs-viewer/playground/response-cards/types.js +9 -0
  123. package/renderer/components/docs-viewer/playground/response-viewer.js +18 -0
  124. package/renderer/components/docs-viewer/search/index.js +2 -0
  125. package/renderer/components/docs-viewer/search/search-dialog.js +367 -0
  126. package/renderer/components/docs-viewer/search/use-search.js +89 -0
  127. package/renderer/components/docs-viewer/shared/markdown-renderer.js +423 -0
  128. package/renderer/components/docs-viewer/shared/method-badge.js +23 -0
  129. package/renderer/components/docs-viewer/shared/schema-viewer.js +321 -0
  130. package/renderer/components/docs-viewer/sidebar/collection-tree.js +222 -0
  131. package/renderer/components/docs-viewer/sidebar/endpoint-options.js +512 -0
  132. package/renderer/components/docs-viewer/sidebar/index.js +196 -0
  133. package/renderer/components/docs-viewer/sidebar/right-sidebar.js +163 -0
  134. package/renderer/components/docs-viewer/sidebar/sidebar-group.js +87 -0
  135. package/renderer/components/docs-viewer/sidebar/sidebar-item.js +172 -0
  136. package/renderer/components/docs-viewer/sidebar/sidebar-section.js +31 -0
  137. package/renderer/components/theme-provider.js +10 -0
  138. package/renderer/components/theme-toggle.js +106 -0
  139. package/renderer/components/ui/badge.js +29 -0
  140. package/renderer/components/ui/button.js +40 -0
  141. package/renderer/components/ui/dialog.js +50 -0
  142. package/renderer/components/ui/dropdown-menu.js +143 -0
  143. package/renderer/components/ui/input.js +12 -0
  144. package/renderer/components/ui/label.js +13 -0
  145. package/renderer/components/ui/navigation-menu.js +83 -0
  146. package/renderer/components/ui/select.js +116 -0
  147. package/renderer/components/ui/spinner.js +92 -0
  148. package/renderer/components/ui/tabs.js +34 -0
  149. package/renderer/components/ui/tooltip.js +43 -0
  150. package/renderer/hooks/use-code-copy.js +76 -0
  151. package/renderer/hooks/use-openapi-title.js +33 -0
  152. package/renderer/lib/api-docs/agent/index.js +4 -0
  153. package/renderer/lib/api-docs/agent/indexer.js +254 -0
  154. package/renderer/lib/api-docs/agent/spec-summary.js +227 -0
  155. package/renderer/lib/api-docs/agent/types.js +5 -0
  156. package/renderer/lib/api-docs/auth/auth-context.js +157 -0
  157. package/renderer/lib/api-docs/auth/auth-storage.js +66 -0
  158. package/renderer/lib/api-docs/auth/crypto.js +64 -0
  159. package/renderer/lib/api-docs/auth/index.js +3 -0
  160. package/renderer/lib/api-docs/code-editor/db.js +145 -0
  161. package/renderer/lib/api-docs/code-editor/hooks.js +254 -0
  162. package/renderer/lib/api-docs/code-editor/{index.ts → index.js} +3 -4
  163. package/renderer/lib/api-docs/code-editor/mode-context.js +154 -0
  164. package/renderer/lib/api-docs/code-editor/types.js +53 -0
  165. package/renderer/lib/api-docs/codegen/definitions.js +258 -0
  166. package/renderer/lib/api-docs/codegen/har.js +171 -0
  167. package/renderer/lib/api-docs/codegen/index.js +118 -0
  168. package/renderer/lib/api-docs/factories.js +136 -0
  169. package/renderer/lib/api-docs/{index.ts → index.js} +5 -10
  170. package/renderer/lib/api-docs/mobile-context.js +79 -0
  171. package/renderer/lib/api-docs/navigation-context.js +62 -0
  172. package/renderer/lib/api-docs/parsers/graphql/index.js +50 -0
  173. package/renderer/lib/api-docs/parsers/graphql/parser.js +350 -0
  174. package/renderer/lib/api-docs/parsers/graphql/transformer.js +215 -0
  175. package/renderer/lib/api-docs/parsers/graphql/types.js +46 -0
  176. package/renderer/lib/api-docs/parsers/openapi/dereferencer.js +43 -0
  177. package/renderer/lib/api-docs/parsers/openapi/extractors/auth.js +486 -0
  178. package/renderer/lib/api-docs/parsers/openapi/extractors/body.js +295 -0
  179. package/renderer/lib/api-docs/parsers/openapi/extractors/index.js +132 -0
  180. package/renderer/lib/api-docs/parsers/openapi/index.js +127 -0
  181. package/renderer/lib/api-docs/parsers/openapi/transformer.js +192 -0
  182. package/renderer/lib/api-docs/parsers/openapi/validator.js +24 -0
  183. package/renderer/lib/api-docs/playground/context.js +65 -0
  184. package/renderer/lib/api-docs/playground/navigation-context.js +74 -0
  185. package/renderer/lib/api-docs/playground/request-builder.js +163 -0
  186. package/renderer/lib/api-docs/playground/request-runner.js +224 -0
  187. package/renderer/lib/api-docs/playground/types.js +5 -0
  188. package/renderer/lib/api-docs/types.js +23 -0
  189. package/renderer/lib/api-docs/utils.js +212 -0
  190. package/renderer/lib/cache.js +157 -0
  191. package/renderer/lib/docs/config/domain-schema.js +161 -0
  192. package/renderer/lib/docs/config/index.js +5 -0
  193. package/renderer/lib/docs/config/loader.js +113 -0
  194. package/renderer/lib/docs/config/schema.js +269 -0
  195. package/renderer/lib/docs/index.js +8 -0
  196. package/renderer/lib/docs/mdx/compiler.js +128 -0
  197. package/renderer/lib/docs/mdx/frontmatter.js +73 -0
  198. package/renderer/lib/docs/mdx/index.js +8 -0
  199. package/renderer/lib/docs/navigation/generator.js +269 -0
  200. package/renderer/lib/docs/navigation/index.js +4 -0
  201. package/renderer/lib/docs/navigation/types.js +9 -0
  202. package/renderer/lib/docs-navigation-context.js +40 -0
  203. package/renderer/lib/multi-tenant/context.js +80 -0
  204. package/renderer/lib/storage/blob.js +767 -0
  205. package/renderer/lib/utils/icons.js +30 -0
  206. package/renderer/lib/utils.js +5 -0
  207. package/renderer/next.config.js +62 -0
  208. package/renderer/tsconfig.json +23 -5
  209. package/renderer/app/api/assets/[...path]/route.ts +0 -123
  210. package/renderer/app/api/assets/route.ts +0 -124
  211. package/renderer/app/api/assets/upload/route.ts +0 -177
  212. package/renderer/app/api/auth-schemes/route.ts +0 -77
  213. package/renderer/app/api/chat/route.ts +0 -858
  214. package/renderer/app/api/codegen/route.ts +0 -72
  215. package/renderer/app/api/collections/route.ts +0 -1002
  216. package/renderer/app/api/debug/route.ts +0 -53
  217. package/renderer/app/api/deploy/route.ts +0 -234
  218. package/renderer/app/api/device/route.ts +0 -42
  219. package/renderer/app/api/docs/route.ts +0 -201
  220. package/renderer/app/api/domains/add/route.ts +0 -132
  221. package/renderer/app/api/domains/lookup/route.ts +0 -43
  222. package/renderer/app/api/domains/remove/route.ts +0 -100
  223. package/renderer/app/api/domains/status/route.ts +0 -158
  224. package/renderer/app/api/domains/verify/route.ts +0 -181
  225. package/renderer/app/api/keys/regenerate/route.ts +0 -80
  226. package/renderer/app/api/local-assets/[...path]/route.ts +0 -122
  227. package/renderer/app/api/openapi-spec/route.ts +0 -151
  228. package/renderer/app/api/projects/[slug]/route.ts +0 -153
  229. package/renderer/app/api/projects/[slug]/stats/route.ts +0 -96
  230. package/renderer/app/api/projects/register/route.ts +0 -152
  231. package/renderer/app/api/proxy/route.ts +0 -149
  232. package/renderer/app/api/proxy-stream/route.ts +0 -168
  233. package/renderer/app/api/redirects/route.ts +0 -47
  234. package/renderer/app/api/schema/route.ts +0 -73
  235. package/renderer/app/api/subdomains/check/route.ts +0 -172
  236. package/renderer/app/api/suggestions/route.ts +0 -144
  237. package/renderer/app/layout.tsx +0 -54
  238. package/renderer/app/llms-full.txt/route.ts +0 -346
  239. package/renderer/app/llms.txt/route.ts +0 -279
  240. package/renderer/app/page.tsx +0 -14
  241. package/renderer/app/robots.txt/route.ts +0 -84
  242. package/renderer/app/sitemap.xml/route.ts +0 -199
  243. package/renderer/components/docs/index.ts +0 -12
  244. package/renderer/components/docs/mdx/accordion.tsx +0 -169
  245. package/renderer/components/docs/mdx/badge.tsx +0 -132
  246. package/renderer/components/docs/mdx/callouts.tsx +0 -154
  247. package/renderer/components/docs/mdx/cards.tsx +0 -241
  248. package/renderer/components/docs/mdx/changelog.tsx +0 -120
  249. package/renderer/components/docs/mdx/code-block.tsx +0 -186
  250. package/renderer/components/docs/mdx/code-group.tsx +0 -421
  251. package/renderer/components/docs/mdx/file-embeds.tsx +0 -105
  252. package/renderer/components/docs/mdx/frame.tsx +0 -112
  253. package/renderer/components/docs/mdx/highlight.tsx +0 -151
  254. package/renderer/components/docs/mdx/iframe.tsx +0 -134
  255. package/renderer/components/docs/mdx/image.tsx +0 -235
  256. package/renderer/components/docs/mdx/index.ts +0 -237
  257. package/renderer/components/docs/mdx/landing.tsx +0 -684
  258. package/renderer/components/docs/mdx/mermaid.tsx +0 -240
  259. package/renderer/components/docs/mdx/param-field.tsx +0 -200
  260. package/renderer/components/docs/mdx/steps.tsx +0 -113
  261. package/renderer/components/docs/mdx/tabs.tsx +0 -86
  262. package/renderer/components/docs/mdx-renderer.tsx +0 -100
  263. package/renderer/components/docs/navigation/breadcrumbs.tsx +0 -76
  264. package/renderer/components/docs/navigation/index.ts +0 -8
  265. package/renderer/components/docs/navigation/page-nav.tsx +0 -64
  266. package/renderer/components/docs/navigation/sidebar.tsx +0 -515
  267. package/renderer/components/docs/navigation/toc.tsx +0 -113
  268. package/renderer/components/docs/notice.tsx +0 -105
  269. package/renderer/components/docs-header.tsx +0 -278
  270. package/renderer/components/docs-viewer/agent/agent-chat.tsx +0 -2076
  271. package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +0 -90
  272. package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +0 -49
  273. package/renderer/components/docs-viewer/agent/cards/index.tsx +0 -50
  274. package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +0 -212
  275. package/renderer/components/docs-viewer/agent/cards/types.ts +0 -84
  276. package/renderer/components/docs-viewer/agent/chat-message.tsx +0 -17
  277. package/renderer/components/docs-viewer/agent/index.tsx +0 -6
  278. package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +0 -119
  279. package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +0 -46
  280. package/renderer/components/docs-viewer/agent/messages/index.ts +0 -17
  281. package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +0 -721
  282. package/renderer/components/docs-viewer/agent/messages/types.ts +0 -61
  283. package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +0 -24
  284. package/renderer/components/docs-viewer/agent/messages/user-message.tsx +0 -51
  285. package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +0 -1283
  286. package/renderer/components/docs-viewer/content/changelog-page.tsx +0 -331
  287. package/renderer/components/docs-viewer/content/doc-page.tsx +0 -367
  288. package/renderer/components/docs-viewer/content/documentation-viewer.tsx +0 -17
  289. package/renderer/components/docs-viewer/content/index.tsx +0 -29
  290. package/renderer/components/docs-viewer/content/not-found-page.tsx +0 -330
  291. package/renderer/components/docs-viewer/content/request-details.tsx +0 -330
  292. package/renderer/components/docs-viewer/content/sections/auth.tsx +0 -69
  293. package/renderer/components/docs-viewer/content/sections/body.tsx +0 -66
  294. package/renderer/components/docs-viewer/content/sections/headers.tsx +0 -43
  295. package/renderer/components/docs-viewer/content/sections/overview.tsx +0 -40
  296. package/renderer/components/docs-viewer/content/sections/parameters.tsx +0 -43
  297. package/renderer/components/docs-viewer/content/sections/responses.tsx +0 -87
  298. package/renderer/components/docs-viewer/global-auth-modal.tsx +0 -352
  299. package/renderer/components/docs-viewer/index.tsx +0 -1662
  300. package/renderer/components/docs-viewer/playground/auth-editor.tsx +0 -280
  301. package/renderer/components/docs-viewer/playground/body-editor.tsx +0 -221
  302. package/renderer/components/docs-viewer/playground/code-editor.tsx +0 -224
  303. package/renderer/components/docs-viewer/playground/code-snippet.tsx +0 -387
  304. package/renderer/components/docs-viewer/playground/graphql-playground.tsx +0 -745
  305. package/renderer/components/docs-viewer/playground/index.tsx +0 -671
  306. package/renderer/components/docs-viewer/playground/key-value-editor.tsx +0 -261
  307. package/renderer/components/docs-viewer/playground/method-selector.tsx +0 -60
  308. package/renderer/components/docs-viewer/playground/request-builder.tsx +0 -179
  309. package/renderer/components/docs-viewer/playground/request-tabs.tsx +0 -237
  310. package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +0 -21
  311. package/renderer/components/docs-viewer/playground/response-cards/index.tsx +0 -93
  312. package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +0 -16
  313. package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +0 -23
  314. package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +0 -268
  315. package/renderer/components/docs-viewer/playground/response-cards/types.ts +0 -82
  316. package/renderer/components/docs-viewer/playground/response-viewer.tsx +0 -43
  317. package/renderer/components/docs-viewer/search/index.ts +0 -2
  318. package/renderer/components/docs-viewer/search/search-dialog.tsx +0 -331
  319. package/renderer/components/docs-viewer/search/use-search.ts +0 -117
  320. package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +0 -431
  321. package/renderer/components/docs-viewer/shared/method-badge.tsx +0 -41
  322. package/renderer/components/docs-viewer/shared/schema-viewer.tsx +0 -349
  323. package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +0 -259
  324. package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +0 -316
  325. package/renderer/components/docs-viewer/sidebar/index.tsx +0 -282
  326. package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +0 -202
  327. package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +0 -118
  328. package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +0 -212
  329. package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +0 -38
  330. package/renderer/components/theme-provider.tsx +0 -11
  331. package/renderer/components/theme-toggle.tsx +0 -76
  332. package/renderer/components/ui/badge.tsx +0 -46
  333. package/renderer/components/ui/button.tsx +0 -59
  334. package/renderer/components/ui/dialog.tsx +0 -118
  335. package/renderer/components/ui/dropdown-menu.tsx +0 -257
  336. package/renderer/components/ui/input.tsx +0 -21
  337. package/renderer/components/ui/label.tsx +0 -24
  338. package/renderer/components/ui/navigation-menu.tsx +0 -168
  339. package/renderer/components/ui/select.tsx +0 -190
  340. package/renderer/components/ui/spinner.tsx +0 -114
  341. package/renderer/components/ui/tabs.tsx +0 -66
  342. package/renderer/components/ui/tooltip.tsx +0 -61
  343. package/renderer/hooks/use-code-copy.ts +0 -88
  344. package/renderer/hooks/use-openapi-title.ts +0 -44
  345. package/renderer/lib/api-docs/agent/index.ts +0 -6
  346. package/renderer/lib/api-docs/agent/indexer.ts +0 -323
  347. package/renderer/lib/api-docs/agent/spec-summary.ts +0 -335
  348. package/renderer/lib/api-docs/agent/types.ts +0 -116
  349. package/renderer/lib/api-docs/auth/auth-context.tsx +0 -225
  350. package/renderer/lib/api-docs/auth/auth-storage.ts +0 -87
  351. package/renderer/lib/api-docs/auth/crypto.ts +0 -89
  352. package/renderer/lib/api-docs/auth/index.ts +0 -4
  353. package/renderer/lib/api-docs/code-editor/db.ts +0 -164
  354. package/renderer/lib/api-docs/code-editor/hooks.ts +0 -266
  355. package/renderer/lib/api-docs/code-editor/mode-context.tsx +0 -207
  356. package/renderer/lib/api-docs/code-editor/types.ts +0 -105
  357. package/renderer/lib/api-docs/codegen/definitions.ts +0 -297
  358. package/renderer/lib/api-docs/codegen/har.ts +0 -251
  359. package/renderer/lib/api-docs/codegen/index.ts +0 -159
  360. package/renderer/lib/api-docs/factories.ts +0 -170
  361. package/renderer/lib/api-docs/mobile-context.tsx +0 -112
  362. package/renderer/lib/api-docs/navigation-context.tsx +0 -88
  363. package/renderer/lib/api-docs/parsers/graphql/README.md +0 -129
  364. package/renderer/lib/api-docs/parsers/graphql/index.ts +0 -91
  365. package/renderer/lib/api-docs/parsers/graphql/parser.ts +0 -491
  366. package/renderer/lib/api-docs/parsers/graphql/transformer.ts +0 -246
  367. package/renderer/lib/api-docs/parsers/graphql/types.ts +0 -283
  368. package/renderer/lib/api-docs/parsers/openapi/README.md +0 -32
  369. package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +0 -60
  370. package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +0 -574
  371. package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +0 -403
  372. package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +0 -232
  373. package/renderer/lib/api-docs/parsers/openapi/index.ts +0 -171
  374. package/renderer/lib/api-docs/parsers/openapi/transformer.ts +0 -278
  375. package/renderer/lib/api-docs/parsers/openapi/validator.ts +0 -31
  376. package/renderer/lib/api-docs/playground/context.tsx +0 -107
  377. package/renderer/lib/api-docs/playground/navigation-context.tsx +0 -124
  378. package/renderer/lib/api-docs/playground/request-builder.ts +0 -223
  379. package/renderer/lib/api-docs/playground/request-runner.ts +0 -282
  380. package/renderer/lib/api-docs/playground/types.ts +0 -35
  381. package/renderer/lib/api-docs/types.ts +0 -269
  382. package/renderer/lib/api-docs/utils.ts +0 -311
  383. package/renderer/lib/cache.ts +0 -193
  384. package/renderer/lib/docs/config/domain-schema.ts +0 -260
  385. package/renderer/lib/docs/config/index.ts +0 -43
  386. package/renderer/lib/docs/config/loader.ts +0 -142
  387. package/renderer/lib/docs/config/schema.ts +0 -308
  388. package/renderer/lib/docs/index.ts +0 -12
  389. package/renderer/lib/docs/mdx/compiler.ts +0 -176
  390. package/renderer/lib/docs/mdx/frontmatter.ts +0 -91
  391. package/renderer/lib/docs/mdx/index.ts +0 -26
  392. package/renderer/lib/docs/navigation/generator.ts +0 -348
  393. package/renderer/lib/docs/navigation/index.ts +0 -12
  394. package/renderer/lib/docs/navigation/types.ts +0 -123
  395. package/renderer/lib/docs-navigation-context.tsx +0 -80
  396. package/renderer/lib/multi-tenant/context.ts +0 -105
  397. package/renderer/lib/storage/blob.ts +0 -1083
  398. package/renderer/lib/utils/icons.ts +0 -48
  399. package/renderer/lib/utils.ts +0 -6
  400. package/renderer/next.config.ts +0 -76
@@ -1,1662 +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
- if (collection.docsColors) {
1444
- const colors = collection.docsColors
1445
- cssContent += `:root {\n`
1446
- if (colors.primary) cssContent += ` --docs-primary: ${colors.primary};\n`
1447
- if (colors.primaryLight) cssContent += ` --docs-primary-light: ${colors.primaryLight};\n`
1448
- if (colors.primaryDark) cssContent += ` --docs-primary-dark: ${colors.primaryDark};\n`
1449
- cssContent += `}\n\n`
1450
- }
1451
-
1452
- // Add custom CSS if provided
1453
- if (collection.customCss) {
1454
- cssContent += collection.customCss
1455
- }
1456
-
1457
- styleEl.textContent = cssContent
1458
-
1459
- return () => {
1460
- // Cleanup on unmount
1461
- const el = document.getElementById('docs-custom-css')
1462
- if (el) el.remove()
1463
- }
1464
- }, [collection.customCss, collection.docsColors])
1465
-
1466
- // Apply default theme from theme.json on initial load
1467
- // Use a ref to track if we've already applied the default theme
1468
- const hasAppliedDefaultTheme = useRef(false)
1469
- useEffect(() => {
1470
- if (collection?.defaultTheme && !hasAppliedDefaultTheme.current) {
1471
- setTheme(collection.defaultTheme)
1472
- hasAppliedDefaultTheme.current = true
1473
- }
1474
- }, [collection?.defaultTheme, setTheme])
1475
-
1476
- return (
1477
- <div className="docs-viewer-root flex flex-col h-screen overflow-hidden">
1478
- {/* Notice */}
1479
- {collection.notice && (
1480
- <Notice config={collection.notice} storageKey="docs-notice" />
1481
- )}
1482
-
1483
- {/* Search Dialog */}
1484
- <SearchDialog
1485
- open={searchOpen}
1486
- onOpenChange={setSearchOpen}
1487
- items={searchItems}
1488
- onSelect={handleSearchSelect}
1489
- />
1490
-
1491
- {/* Header with tabs */}
1492
- <DocsHeader
1493
- docGroups={collection.docGroups}
1494
- navigationTabs={collection.navigationTabs}
1495
- activeTab={activeTab}
1496
- onTabChange={onTabChange}
1497
- hasEndpoints={hasEndpoints}
1498
- docsName={collection.docsName}
1499
- docsLogo={collection.docsLogo}
1500
- onSearchClick={openSearch}
1501
- docsHeader={collection.docsHeader}
1502
- docsNavbar={collection.docsNavbar}
1503
- />
1504
-
1505
- <div className="docs-layout flex flex-1 overflow-hidden relative z-0 min-h-0">
1506
- {/* Left Sidebar - Hidden for changelog */}
1507
- {!showChangelog && (
1508
- <DocsSidebar
1509
- collection={{
1510
- ...collection,
1511
- // Show GraphQL operations when GraphQL tab is active, else show REST endpoints
1512
- folders: showGraphQL && graphqlCollection
1513
- ? graphqlCollection.folders
1514
- : (showEndpoints ? collection.folders : []),
1515
- requests: showGraphQL && graphqlCollection
1516
- ? graphqlCollection.requests
1517
- : (showEndpoints ? collection.requests : []),
1518
- // Only show filtered doc groups
1519
- docGroups: filteredDocGroups,
1520
- }}
1521
- selectedRequest={showGraphQL ? (selectedGraphQLOperation ? { id: selectedGraphQLOperation.id } as BrainfishRESTRequest : null) : selectedRequest}
1522
- selectedDocSection={selectedDocSection}
1523
- selectedDocPage={selectedDocPage}
1524
- activeTab={activeTab}
1525
- onSelectRequest={showGraphQL ? handleSelectGraphQLOperation : handleSelectRequest}
1526
- onSelectDocumentation={handleSelectDocumentation}
1527
- onSelectDocPage={handleSelectDocPage}
1528
- apiVersions={collection.apiVersions}
1529
- selectedApiVersion={selectedApiVersion}
1530
- onApiVersionChange={handleApiVersionChange}
1531
- isVersionLoading={isVersionLoading}
1532
- />
1533
- )}
1534
-
1535
- {/* Center - Toggles between API Client/Playground and Notes */}
1536
- <div className="docs-main flex-1 flex flex-col overflow-hidden" style={{ minWidth: 0, flexBasis: 0, flexGrow: 1 }}>
1537
- {/* Mode Toggle Header */}
1538
- <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">
1539
- <ModeToggleTabs hasEndpoint={!!selectedRequest} />
1540
- </div>
1541
-
1542
- {/* Content Area */}
1543
- {mode === 'docs' ? (
1544
- <DocsNavigationProvider
1545
- onNavigateToPage={handleSelectDocPage}
1546
- onSwitchTab={onTabChange}
1547
- activeTab={activeTab}
1548
- >
1549
- <div
1550
- ref={contentRef}
1551
- className={cn(
1552
- "docs-content-area flex-1 bg-background min-w-0",
1553
- (showChangelog || (showGraphQL && selectedGraphQLOperation)) ? "overflow-hidden flex flex-col" : "overflow-y-auto scroll-smooth"
1554
- )}
1555
- >
1556
- {showGraphQL && selectedGraphQLOperation ? (
1557
- <div className="flex-1 flex flex-col h-full">
1558
- <GraphQLPlayground
1559
- endpoint={activeGraphQLSchemas[0]?.endpoint || ''}
1560
- defaultQuery={selectedGraphQLOperation?.query}
1561
- operations={graphqlOperations}
1562
- selectedOperationId={selectedGraphQLOperation?.id}
1563
- hideExplorer={true}
1564
- headers={{}}
1565
- theme="dark"
1566
- />
1567
- </div>
1568
- ) : showChangelog ? (
1569
- <ChangelogPage
1570
- releases={collection.changelogReleases || []}
1571
- tabName={activeTabConfig?.tab}
1572
- />
1573
- ) : selectedRequest ? (
1574
- <RequestDetails request={selectedRequest} />
1575
- ) : selectedDocPage ? (
1576
- <DocPage slug={selectedDocPage} onSearch={openSearch} />
1577
- ) : notFoundSlug ? (
1578
- <NotFoundPage slug={notFoundSlug} onSearch={openSearch} />
1579
- ) : (
1580
- <div className="flex-1 flex items-center justify-center bg-background">
1581
- <div className="text-center text-muted-foreground">
1582
- <Book className="h-12 w-12 mx-auto mb-3 opacity-40" />
1583
- <p className="text-sm">Select an endpoint from the sidebar</p>
1584
- </div>
1585
- </div>
1586
- )}
1587
- </div>
1588
- </DocsNavigationProvider>
1589
- ) : mode === 'notes' ? (
1590
- <div className="flex-1 flex flex-col min-h-0 overflow-hidden">
1591
- <NotesMode apiSpecUrl={apiSpecUrl} apiName={collection.name} />
1592
- </div>
1593
- ) : selectedRequest ? (
1594
- <div className="flex-1 flex flex-col min-h-0 overflow-hidden">
1595
- <ApiPlayground
1596
- request={selectedRequest}
1597
- onDebugRequest={handleDebugRequest}
1598
- onExplainRequest={handleExplainRequest}
1599
- />
1600
- </div>
1601
- ) : (
1602
- <div className="flex-1 flex items-center justify-center bg-background">
1603
- <div className="text-center text-muted-foreground">
1604
- <TestTube className="h-12 w-12 mx-auto mb-3 opacity-40" />
1605
- <p className="text-sm">Select an endpoint from the sidebar to test</p>
1606
- </div>
1607
- </div>
1608
- )}
1609
- </div>
1610
-
1611
- {/* Right Sidebar - Agent Panel (drawer on mobile, always visible on desktop) */}
1612
- <RightSidebar
1613
- request={selectedRequest}
1614
- collection={collection}
1615
- apiSummary={collection.apiSummary}
1616
- onNavigateToEndpoint={handleAgentNavigateWithModeSwitch}
1617
- onPrefillParameters={handleAgentPrefill}
1618
- debugContext={debugContext}
1619
- onClearDebugContext={clearDebugContext}
1620
- explainContext={explainContext}
1621
- onClearExplainContext={clearExplainContext}
1622
- onOpenGlobalAuth={() => setShowAuthModal(true)}
1623
- onNavigateToAuthTab={() => {
1624
- navigateAndHighlight('auth', { type: 'auth' }, true)
1625
- }}
1626
- onNavigateToParamsTab={() => {
1627
- navigateAndHighlight('params', { type: 'param' }, true)
1628
- }}
1629
- onNavigateToBodyTab={() => {
1630
- navigateAndHighlight('body', { type: 'body' }, true)
1631
- }}
1632
- onNavigateToHeadersTab={() => {
1633
- navigateAndHighlight('headers', { type: 'header' }, true)
1634
- }}
1635
- onNavigateToDocSection={handleSelectDocumentation}
1636
- onNavigateToDocPage={handleSelectDocPage}
1637
- />
1638
- </div>
1639
-
1640
- {/* Global Auth Modal */}
1641
- <GlobalAuthModal
1642
- open={showAuthModal}
1643
- onClose={() => setShowAuthModal(false)}
1644
- />
1645
- </div>
1646
- )
1647
- }
1648
-
1649
- // Wrapper component with providers
1650
- export function Docs() {
1651
- return (
1652
- <AuthProvider>
1653
- <ModeProvider>
1654
- <PlaygroundProvider>
1655
- <PlaygroundNavigationProvider>
1656
- <DocsContent />
1657
- </PlaygroundNavigationProvider>
1658
- </PlaygroundProvider>
1659
- </ModeProvider>
1660
- </AuthProvider>
1661
- )
1662
- }