@brainfish-ai/devdoc 0.1.42 → 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 (388) hide show
  1. package/dist/cli/commands/dev.js +19 -10
  2. package/package.json +3 -2
  3. package/renderer/app/api/assets/[...path]/route.js +108 -0
  4. package/renderer/app/api/assets/route.js +114 -0
  5. package/renderer/app/api/assets/upload/route.js +163 -0
  6. package/renderer/app/api/auth-schemes/route.js +58 -0
  7. package/renderer/app/api/chat/route.js +759 -0
  8. package/renderer/app/api/codegen/route.js +52 -0
  9. package/renderer/app/api/collections/route.js +675 -0
  10. package/renderer/app/api/debug/route.js +47 -0
  11. package/renderer/app/api/deploy/route.js +199 -0
  12. package/renderer/app/api/device/route.js +36 -0
  13. package/renderer/app/api/docs/route.js +205 -0
  14. package/renderer/app/api/domains/add/route.js +121 -0
  15. package/renderer/app/api/domains/lookup/route.js +43 -0
  16. package/renderer/app/api/domains/remove/route.js +89 -0
  17. package/renderer/app/api/domains/status/route.js +140 -0
  18. package/renderer/app/api/domains/verify/route.js +168 -0
  19. package/renderer/app/api/keys/regenerate/route.js +71 -0
  20. package/renderer/app/api/local-assets/[...path]/route.js +108 -0
  21. package/renderer/app/api/openapi-spec/route.js +73 -0
  22. package/renderer/app/api/projects/[slug]/route.js +129 -0
  23. package/renderer/app/api/projects/[slug]/stats/route.js +80 -0
  24. package/renderer/app/api/projects/register/route.js +176 -0
  25. package/renderer/app/api/proxy/route.js +139 -0
  26. package/renderer/app/api/proxy-stream/route.js +156 -0
  27. package/renderer/app/api/redirects/route.js +35 -0
  28. package/renderer/app/api/schema/route.js +85 -0
  29. package/renderer/app/api/subdomains/check/route.js +158 -0
  30. package/renderer/app/api/suggestions/route.js +175 -0
  31. package/renderer/app/layout.js +47 -0
  32. package/renderer/app/llms-full.txt/route.js +257 -0
  33. package/renderer/app/llms.txt/route.js +219 -0
  34. package/renderer/app/page.js +12 -0
  35. package/renderer/app/robots.txt/route.js +66 -0
  36. package/renderer/app/sitemap.xml/route.js +145 -0
  37. package/renderer/components/docs/index.js +8 -0
  38. package/renderer/components/docs/mdx/accordion.js +113 -0
  39. package/renderer/components/docs/mdx/badge.js +72 -0
  40. package/renderer/components/docs/mdx/callouts.js +137 -0
  41. package/renderer/components/docs/mdx/cards.js +175 -0
  42. package/renderer/components/docs/mdx/changelog.js +100 -0
  43. package/renderer/components/docs/mdx/code-block.js +147 -0
  44. package/renderer/components/docs/mdx/code-group.js +287 -0
  45. package/renderer/components/docs/mdx/file-embeds.js +82 -0
  46. package/renderer/components/docs/mdx/frame.js +59 -0
  47. package/renderer/components/docs/mdx/highlight.js +90 -0
  48. package/renderer/components/docs/mdx/iframe.js +69 -0
  49. package/renderer/components/docs/mdx/image.js +135 -0
  50. package/renderer/components/docs/mdx/index.js +134 -0
  51. package/renderer/components/docs/mdx/landing.js +315 -0
  52. package/renderer/components/docs/mdx/mermaid.js +212 -0
  53. package/renderer/components/docs/mdx/param-field.js +112 -0
  54. package/renderer/components/docs/mdx/steps.js +74 -0
  55. package/renderer/components/docs/mdx/tabs.js +50 -0
  56. package/renderer/components/docs/mdx-renderer.js +77 -0
  57. package/renderer/components/docs/navigation/breadcrumbs.js +64 -0
  58. package/renderer/components/docs/navigation/index.js +6 -0
  59. package/renderer/components/docs/navigation/page-nav.js +57 -0
  60. package/renderer/components/docs/navigation/sidebar.js +375 -0
  61. package/renderer/components/docs/navigation/toc.js +89 -0
  62. package/renderer/components/docs/notice.js +77 -0
  63. package/renderer/components/docs-header.js +202 -0
  64. package/renderer/components/docs-viewer/agent/agent-chat.js +1930 -0
  65. package/renderer/components/docs-viewer/agent/cards/debug-context-card.js +107 -0
  66. package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.js +57 -0
  67. package/renderer/components/docs-viewer/agent/cards/index.js +45 -0
  68. package/renderer/components/docs-viewer/agent/cards/response-options-card.js +154 -0
  69. package/renderer/components/docs-viewer/agent/cards/types.js +22 -0
  70. package/renderer/components/docs-viewer/agent/chat-message.js +2 -0
  71. package/renderer/components/docs-viewer/agent/index.js +4 -0
  72. package/renderer/components/docs-viewer/agent/messages/assistant-message.js +108 -0
  73. package/renderer/components/docs-viewer/agent/messages/chat-message.js +34 -0
  74. package/renderer/components/docs-viewer/agent/messages/index.js +6 -0
  75. package/renderer/components/docs-viewer/agent/messages/tool-call-display.js +1065 -0
  76. package/renderer/components/docs-viewer/agent/messages/types.js +2 -0
  77. package/renderer/components/docs-viewer/agent/messages/typing-indicator.js +26 -0
  78. package/renderer/components/docs-viewer/agent/messages/user-message.js +37 -0
  79. package/renderer/components/docs-viewer/code-editor/{index.tsx → index.js} +1 -1
  80. package/renderer/components/docs-viewer/code-editor/notes-mode.js +1338 -0
  81. package/renderer/components/docs-viewer/content/changelog-page.js +297 -0
  82. package/renderer/components/docs-viewer/content/doc-page.js +264 -0
  83. package/renderer/components/docs-viewer/content/documentation-viewer.js +14 -0
  84. package/renderer/components/docs-viewer/content/index.js +29 -0
  85. package/renderer/components/docs-viewer/content/not-found-page.js +300 -0
  86. package/renderer/components/docs-viewer/content/request-details.js +528 -0
  87. package/renderer/components/docs-viewer/content/sections/auth.js +108 -0
  88. package/renderer/components/docs-viewer/content/sections/body.js +80 -0
  89. package/renderer/components/docs-viewer/content/sections/headers.js +64 -0
  90. package/renderer/components/docs-viewer/content/sections/overview.js +56 -0
  91. package/renderer/components/docs-viewer/content/sections/parameters.js +64 -0
  92. package/renderer/components/docs-viewer/content/sections/responses.js +91 -0
  93. package/renderer/components/docs-viewer/global-auth-modal.js +427 -0
  94. package/renderer/components/docs-viewer/index.js +1552 -0
  95. package/renderer/components/docs-viewer/playground/auth-editor.js +418 -0
  96. package/renderer/components/docs-viewer/playground/body-editor.js +240 -0
  97. package/renderer/components/docs-viewer/playground/code-editor.js +135 -0
  98. package/renderer/components/docs-viewer/playground/code-snippet.js +393 -0
  99. package/renderer/components/docs-viewer/playground/graphql-playground.js +734 -0
  100. package/renderer/components/docs-viewer/playground/index.js +682 -0
  101. package/renderer/components/docs-viewer/playground/key-value-editor.js +317 -0
  102. package/renderer/components/docs-viewer/playground/method-selector.js +65 -0
  103. package/renderer/components/docs-viewer/playground/request-builder.js +181 -0
  104. package/renderer/components/docs-viewer/playground/request-tabs.js +240 -0
  105. package/renderer/components/docs-viewer/playground/response-cards/idle-card.js +42 -0
  106. package/renderer/components/docs-viewer/playground/response-cards/index.js +72 -0
  107. package/renderer/components/docs-viewer/playground/response-cards/loading-card.js +24 -0
  108. package/renderer/components/docs-viewer/playground/response-cards/network-error-card.js +28 -0
  109. package/renderer/components/docs-viewer/playground/response-cards/response-body-card.js +308 -0
  110. package/renderer/components/docs-viewer/playground/response-cards/types.js +9 -0
  111. package/renderer/components/docs-viewer/playground/response-viewer.js +18 -0
  112. package/renderer/components/docs-viewer/search/index.js +2 -0
  113. package/renderer/components/docs-viewer/search/search-dialog.js +367 -0
  114. package/renderer/components/docs-viewer/search/use-search.js +89 -0
  115. package/renderer/components/docs-viewer/shared/markdown-renderer.js +423 -0
  116. package/renderer/components/docs-viewer/shared/method-badge.js +23 -0
  117. package/renderer/components/docs-viewer/shared/schema-viewer.js +321 -0
  118. package/renderer/components/docs-viewer/sidebar/collection-tree.js +222 -0
  119. package/renderer/components/docs-viewer/sidebar/endpoint-options.js +512 -0
  120. package/renderer/components/docs-viewer/sidebar/index.js +196 -0
  121. package/renderer/components/docs-viewer/sidebar/right-sidebar.js +163 -0
  122. package/renderer/components/docs-viewer/sidebar/sidebar-group.js +87 -0
  123. package/renderer/components/docs-viewer/sidebar/sidebar-item.js +172 -0
  124. package/renderer/components/docs-viewer/sidebar/sidebar-section.js +31 -0
  125. package/renderer/components/theme-provider.js +10 -0
  126. package/renderer/components/theme-toggle.js +106 -0
  127. package/renderer/components/ui/badge.js +29 -0
  128. package/renderer/components/ui/button.js +40 -0
  129. package/renderer/components/ui/dialog.js +50 -0
  130. package/renderer/components/ui/dropdown-menu.js +143 -0
  131. package/renderer/components/ui/input.js +12 -0
  132. package/renderer/components/ui/label.js +13 -0
  133. package/renderer/components/ui/navigation-menu.js +83 -0
  134. package/renderer/components/ui/select.js +116 -0
  135. package/renderer/components/ui/spinner.js +92 -0
  136. package/renderer/components/ui/tabs.js +34 -0
  137. package/renderer/components/ui/tooltip.js +43 -0
  138. package/renderer/hooks/use-code-copy.js +76 -0
  139. package/renderer/hooks/use-openapi-title.js +33 -0
  140. package/renderer/lib/api-docs/agent/index.js +4 -0
  141. package/renderer/lib/api-docs/agent/indexer.js +254 -0
  142. package/renderer/lib/api-docs/agent/spec-summary.js +227 -0
  143. package/renderer/lib/api-docs/agent/types.js +5 -0
  144. package/renderer/lib/api-docs/auth/auth-context.js +157 -0
  145. package/renderer/lib/api-docs/auth/auth-storage.js +66 -0
  146. package/renderer/lib/api-docs/auth/crypto.js +64 -0
  147. package/renderer/lib/api-docs/auth/index.js +3 -0
  148. package/renderer/lib/api-docs/code-editor/db.js +145 -0
  149. package/renderer/lib/api-docs/code-editor/hooks.js +254 -0
  150. package/renderer/lib/api-docs/code-editor/{index.ts → index.js} +3 -4
  151. package/renderer/lib/api-docs/code-editor/mode-context.js +154 -0
  152. package/renderer/lib/api-docs/code-editor/types.js +53 -0
  153. package/renderer/lib/api-docs/codegen/definitions.js +258 -0
  154. package/renderer/lib/api-docs/codegen/har.js +171 -0
  155. package/renderer/lib/api-docs/codegen/index.js +118 -0
  156. package/renderer/lib/api-docs/factories.js +136 -0
  157. package/renderer/lib/api-docs/{index.ts → index.js} +5 -10
  158. package/renderer/lib/api-docs/mobile-context.js +79 -0
  159. package/renderer/lib/api-docs/navigation-context.js +62 -0
  160. package/renderer/lib/api-docs/parsers/graphql/index.js +50 -0
  161. package/renderer/lib/api-docs/parsers/graphql/parser.js +350 -0
  162. package/renderer/lib/api-docs/parsers/graphql/transformer.js +215 -0
  163. package/renderer/lib/api-docs/parsers/graphql/types.js +46 -0
  164. package/renderer/lib/api-docs/parsers/openapi/dereferencer.js +43 -0
  165. package/renderer/lib/api-docs/parsers/openapi/extractors/auth.js +486 -0
  166. package/renderer/lib/api-docs/parsers/openapi/extractors/body.js +295 -0
  167. package/renderer/lib/api-docs/parsers/openapi/extractors/index.js +132 -0
  168. package/renderer/lib/api-docs/parsers/openapi/index.js +127 -0
  169. package/renderer/lib/api-docs/parsers/openapi/transformer.js +192 -0
  170. package/renderer/lib/api-docs/parsers/openapi/validator.js +24 -0
  171. package/renderer/lib/api-docs/playground/context.js +65 -0
  172. package/renderer/lib/api-docs/playground/navigation-context.js +74 -0
  173. package/renderer/lib/api-docs/playground/request-builder.js +163 -0
  174. package/renderer/lib/api-docs/playground/request-runner.js +224 -0
  175. package/renderer/lib/api-docs/playground/types.js +5 -0
  176. package/renderer/lib/api-docs/types.js +23 -0
  177. package/renderer/lib/api-docs/utils.js +212 -0
  178. package/renderer/lib/cache.js +157 -0
  179. package/renderer/lib/docs/config/domain-schema.js +161 -0
  180. package/renderer/lib/docs/config/index.js +5 -0
  181. package/renderer/lib/docs/config/loader.js +113 -0
  182. package/renderer/lib/docs/config/schema.js +269 -0
  183. package/renderer/lib/docs/index.js +8 -0
  184. package/renderer/lib/docs/mdx/compiler.js +128 -0
  185. package/renderer/lib/docs/mdx/frontmatter.js +73 -0
  186. package/renderer/lib/docs/mdx/index.js +8 -0
  187. package/renderer/lib/docs/navigation/generator.js +269 -0
  188. package/renderer/lib/docs/navigation/index.js +4 -0
  189. package/renderer/lib/docs/navigation/types.js +9 -0
  190. package/renderer/lib/docs-navigation-context.js +40 -0
  191. package/renderer/lib/multi-tenant/context.js +80 -0
  192. package/renderer/lib/storage/blob.js +767 -0
  193. package/renderer/lib/utils/icons.js +30 -0
  194. package/renderer/lib/utils.js +5 -0
  195. package/renderer/next.config.js +62 -0
  196. package/renderer/tsconfig.json +23 -5
  197. package/renderer/app/api/assets/[...path]/route.ts +0 -123
  198. package/renderer/app/api/assets/route.ts +0 -124
  199. package/renderer/app/api/assets/upload/route.ts +0 -177
  200. package/renderer/app/api/auth-schemes/route.ts +0 -77
  201. package/renderer/app/api/chat/route.ts +0 -858
  202. package/renderer/app/api/codegen/route.ts +0 -72
  203. package/renderer/app/api/collections/route.ts +0 -1002
  204. package/renderer/app/api/debug/route.ts +0 -53
  205. package/renderer/app/api/deploy/route.ts +0 -234
  206. package/renderer/app/api/device/route.ts +0 -42
  207. package/renderer/app/api/docs/route.ts +0 -201
  208. package/renderer/app/api/domains/add/route.ts +0 -132
  209. package/renderer/app/api/domains/lookup/route.ts +0 -43
  210. package/renderer/app/api/domains/remove/route.ts +0 -100
  211. package/renderer/app/api/domains/status/route.ts +0 -158
  212. package/renderer/app/api/domains/verify/route.ts +0 -181
  213. package/renderer/app/api/keys/regenerate/route.ts +0 -80
  214. package/renderer/app/api/local-assets/[...path]/route.ts +0 -122
  215. package/renderer/app/api/openapi-spec/route.ts +0 -151
  216. package/renderer/app/api/projects/[slug]/route.ts +0 -153
  217. package/renderer/app/api/projects/[slug]/stats/route.ts +0 -96
  218. package/renderer/app/api/projects/register/route.ts +0 -152
  219. package/renderer/app/api/proxy/route.ts +0 -149
  220. package/renderer/app/api/proxy-stream/route.ts +0 -168
  221. package/renderer/app/api/redirects/route.ts +0 -47
  222. package/renderer/app/api/schema/route.ts +0 -73
  223. package/renderer/app/api/subdomains/check/route.ts +0 -172
  224. package/renderer/app/api/suggestions/route.ts +0 -144
  225. package/renderer/app/layout.tsx +0 -54
  226. package/renderer/app/llms-full.txt/route.ts +0 -346
  227. package/renderer/app/llms.txt/route.ts +0 -279
  228. package/renderer/app/page.tsx +0 -14
  229. package/renderer/app/robots.txt/route.ts +0 -84
  230. package/renderer/app/sitemap.xml/route.ts +0 -199
  231. package/renderer/components/docs/index.ts +0 -12
  232. package/renderer/components/docs/mdx/accordion.tsx +0 -169
  233. package/renderer/components/docs/mdx/badge.tsx +0 -132
  234. package/renderer/components/docs/mdx/callouts.tsx +0 -154
  235. package/renderer/components/docs/mdx/cards.tsx +0 -241
  236. package/renderer/components/docs/mdx/changelog.tsx +0 -120
  237. package/renderer/components/docs/mdx/code-block.tsx +0 -186
  238. package/renderer/components/docs/mdx/code-group.tsx +0 -421
  239. package/renderer/components/docs/mdx/file-embeds.tsx +0 -105
  240. package/renderer/components/docs/mdx/frame.tsx +0 -112
  241. package/renderer/components/docs/mdx/highlight.tsx +0 -151
  242. package/renderer/components/docs/mdx/iframe.tsx +0 -134
  243. package/renderer/components/docs/mdx/image.tsx +0 -235
  244. package/renderer/components/docs/mdx/index.ts +0 -237
  245. package/renderer/components/docs/mdx/landing.tsx +0 -684
  246. package/renderer/components/docs/mdx/mermaid.tsx +0 -240
  247. package/renderer/components/docs/mdx/param-field.tsx +0 -200
  248. package/renderer/components/docs/mdx/steps.tsx +0 -113
  249. package/renderer/components/docs/mdx/tabs.tsx +0 -86
  250. package/renderer/components/docs/mdx-renderer.tsx +0 -100
  251. package/renderer/components/docs/navigation/breadcrumbs.tsx +0 -76
  252. package/renderer/components/docs/navigation/index.ts +0 -8
  253. package/renderer/components/docs/navigation/page-nav.tsx +0 -64
  254. package/renderer/components/docs/navigation/sidebar.tsx +0 -515
  255. package/renderer/components/docs/navigation/toc.tsx +0 -113
  256. package/renderer/components/docs/notice.tsx +0 -105
  257. package/renderer/components/docs-header.tsx +0 -278
  258. package/renderer/components/docs-viewer/agent/agent-chat.tsx +0 -2076
  259. package/renderer/components/docs-viewer/agent/cards/debug-context-card.tsx +0 -90
  260. package/renderer/components/docs-viewer/agent/cards/endpoint-context-card.tsx +0 -49
  261. package/renderer/components/docs-viewer/agent/cards/index.tsx +0 -50
  262. package/renderer/components/docs-viewer/agent/cards/response-options-card.tsx +0 -212
  263. package/renderer/components/docs-viewer/agent/cards/types.ts +0 -84
  264. package/renderer/components/docs-viewer/agent/chat-message.tsx +0 -17
  265. package/renderer/components/docs-viewer/agent/index.tsx +0 -6
  266. package/renderer/components/docs-viewer/agent/messages/assistant-message.tsx +0 -119
  267. package/renderer/components/docs-viewer/agent/messages/chat-message.tsx +0 -46
  268. package/renderer/components/docs-viewer/agent/messages/index.ts +0 -17
  269. package/renderer/components/docs-viewer/agent/messages/tool-call-display.tsx +0 -721
  270. package/renderer/components/docs-viewer/agent/messages/types.ts +0 -61
  271. package/renderer/components/docs-viewer/agent/messages/typing-indicator.tsx +0 -24
  272. package/renderer/components/docs-viewer/agent/messages/user-message.tsx +0 -51
  273. package/renderer/components/docs-viewer/code-editor/notes-mode.tsx +0 -1283
  274. package/renderer/components/docs-viewer/content/changelog-page.tsx +0 -331
  275. package/renderer/components/docs-viewer/content/doc-page.tsx +0 -367
  276. package/renderer/components/docs-viewer/content/documentation-viewer.tsx +0 -17
  277. package/renderer/components/docs-viewer/content/index.tsx +0 -29
  278. package/renderer/components/docs-viewer/content/not-found-page.tsx +0 -330
  279. package/renderer/components/docs-viewer/content/request-details.tsx +0 -330
  280. package/renderer/components/docs-viewer/content/sections/auth.tsx +0 -69
  281. package/renderer/components/docs-viewer/content/sections/body.tsx +0 -66
  282. package/renderer/components/docs-viewer/content/sections/headers.tsx +0 -43
  283. package/renderer/components/docs-viewer/content/sections/overview.tsx +0 -40
  284. package/renderer/components/docs-viewer/content/sections/parameters.tsx +0 -43
  285. package/renderer/components/docs-viewer/content/sections/responses.tsx +0 -87
  286. package/renderer/components/docs-viewer/global-auth-modal.tsx +0 -352
  287. package/renderer/components/docs-viewer/index.tsx +0 -1670
  288. package/renderer/components/docs-viewer/playground/auth-editor.tsx +0 -280
  289. package/renderer/components/docs-viewer/playground/body-editor.tsx +0 -221
  290. package/renderer/components/docs-viewer/playground/code-editor.tsx +0 -224
  291. package/renderer/components/docs-viewer/playground/code-snippet.tsx +0 -387
  292. package/renderer/components/docs-viewer/playground/graphql-playground.tsx +0 -745
  293. package/renderer/components/docs-viewer/playground/index.tsx +0 -671
  294. package/renderer/components/docs-viewer/playground/key-value-editor.tsx +0 -261
  295. package/renderer/components/docs-viewer/playground/method-selector.tsx +0 -60
  296. package/renderer/components/docs-viewer/playground/request-builder.tsx +0 -179
  297. package/renderer/components/docs-viewer/playground/request-tabs.tsx +0 -237
  298. package/renderer/components/docs-viewer/playground/response-cards/idle-card.tsx +0 -21
  299. package/renderer/components/docs-viewer/playground/response-cards/index.tsx +0 -93
  300. package/renderer/components/docs-viewer/playground/response-cards/loading-card.tsx +0 -16
  301. package/renderer/components/docs-viewer/playground/response-cards/network-error-card.tsx +0 -23
  302. package/renderer/components/docs-viewer/playground/response-cards/response-body-card.tsx +0 -268
  303. package/renderer/components/docs-viewer/playground/response-cards/types.ts +0 -82
  304. package/renderer/components/docs-viewer/playground/response-viewer.tsx +0 -43
  305. package/renderer/components/docs-viewer/search/index.ts +0 -2
  306. package/renderer/components/docs-viewer/search/search-dialog.tsx +0 -331
  307. package/renderer/components/docs-viewer/search/use-search.ts +0 -117
  308. package/renderer/components/docs-viewer/shared/markdown-renderer.tsx +0 -431
  309. package/renderer/components/docs-viewer/shared/method-badge.tsx +0 -41
  310. package/renderer/components/docs-viewer/shared/schema-viewer.tsx +0 -349
  311. package/renderer/components/docs-viewer/sidebar/collection-tree.tsx +0 -259
  312. package/renderer/components/docs-viewer/sidebar/endpoint-options.tsx +0 -316
  313. package/renderer/components/docs-viewer/sidebar/index.tsx +0 -282
  314. package/renderer/components/docs-viewer/sidebar/right-sidebar.tsx +0 -202
  315. package/renderer/components/docs-viewer/sidebar/sidebar-group.tsx +0 -118
  316. package/renderer/components/docs-viewer/sidebar/sidebar-item.tsx +0 -212
  317. package/renderer/components/docs-viewer/sidebar/sidebar-section.tsx +0 -38
  318. package/renderer/components/theme-provider.tsx +0 -11
  319. package/renderer/components/theme-toggle.tsx +0 -76
  320. package/renderer/components/ui/badge.tsx +0 -46
  321. package/renderer/components/ui/button.tsx +0 -59
  322. package/renderer/components/ui/dialog.tsx +0 -118
  323. package/renderer/components/ui/dropdown-menu.tsx +0 -257
  324. package/renderer/components/ui/input.tsx +0 -21
  325. package/renderer/components/ui/label.tsx +0 -24
  326. package/renderer/components/ui/navigation-menu.tsx +0 -168
  327. package/renderer/components/ui/select.tsx +0 -190
  328. package/renderer/components/ui/spinner.tsx +0 -114
  329. package/renderer/components/ui/tabs.tsx +0 -66
  330. package/renderer/components/ui/tooltip.tsx +0 -61
  331. package/renderer/hooks/use-code-copy.ts +0 -88
  332. package/renderer/hooks/use-openapi-title.ts +0 -44
  333. package/renderer/lib/api-docs/agent/index.ts +0 -6
  334. package/renderer/lib/api-docs/agent/indexer.ts +0 -323
  335. package/renderer/lib/api-docs/agent/spec-summary.ts +0 -335
  336. package/renderer/lib/api-docs/agent/types.ts +0 -116
  337. package/renderer/lib/api-docs/auth/auth-context.tsx +0 -225
  338. package/renderer/lib/api-docs/auth/auth-storage.ts +0 -87
  339. package/renderer/lib/api-docs/auth/crypto.ts +0 -89
  340. package/renderer/lib/api-docs/auth/index.ts +0 -4
  341. package/renderer/lib/api-docs/code-editor/db.ts +0 -164
  342. package/renderer/lib/api-docs/code-editor/hooks.ts +0 -266
  343. package/renderer/lib/api-docs/code-editor/mode-context.tsx +0 -207
  344. package/renderer/lib/api-docs/code-editor/types.ts +0 -105
  345. package/renderer/lib/api-docs/codegen/definitions.ts +0 -297
  346. package/renderer/lib/api-docs/codegen/har.ts +0 -251
  347. package/renderer/lib/api-docs/codegen/index.ts +0 -159
  348. package/renderer/lib/api-docs/factories.ts +0 -170
  349. package/renderer/lib/api-docs/mobile-context.tsx +0 -112
  350. package/renderer/lib/api-docs/navigation-context.tsx +0 -88
  351. package/renderer/lib/api-docs/parsers/graphql/README.md +0 -129
  352. package/renderer/lib/api-docs/parsers/graphql/index.ts +0 -91
  353. package/renderer/lib/api-docs/parsers/graphql/parser.ts +0 -491
  354. package/renderer/lib/api-docs/parsers/graphql/transformer.ts +0 -246
  355. package/renderer/lib/api-docs/parsers/graphql/types.ts +0 -283
  356. package/renderer/lib/api-docs/parsers/openapi/README.md +0 -32
  357. package/renderer/lib/api-docs/parsers/openapi/dereferencer.ts +0 -60
  358. package/renderer/lib/api-docs/parsers/openapi/extractors/auth.ts +0 -574
  359. package/renderer/lib/api-docs/parsers/openapi/extractors/body.ts +0 -403
  360. package/renderer/lib/api-docs/parsers/openapi/extractors/index.ts +0 -232
  361. package/renderer/lib/api-docs/parsers/openapi/index.ts +0 -171
  362. package/renderer/lib/api-docs/parsers/openapi/transformer.ts +0 -278
  363. package/renderer/lib/api-docs/parsers/openapi/validator.ts +0 -31
  364. package/renderer/lib/api-docs/playground/context.tsx +0 -107
  365. package/renderer/lib/api-docs/playground/navigation-context.tsx +0 -124
  366. package/renderer/lib/api-docs/playground/request-builder.ts +0 -223
  367. package/renderer/lib/api-docs/playground/request-runner.ts +0 -282
  368. package/renderer/lib/api-docs/playground/types.ts +0 -35
  369. package/renderer/lib/api-docs/types.ts +0 -269
  370. package/renderer/lib/api-docs/utils.ts +0 -311
  371. package/renderer/lib/cache.ts +0 -193
  372. package/renderer/lib/docs/config/domain-schema.ts +0 -260
  373. package/renderer/lib/docs/config/index.ts +0 -43
  374. package/renderer/lib/docs/config/loader.ts +0 -142
  375. package/renderer/lib/docs/config/schema.ts +0 -308
  376. package/renderer/lib/docs/index.ts +0 -12
  377. package/renderer/lib/docs/mdx/compiler.ts +0 -176
  378. package/renderer/lib/docs/mdx/frontmatter.ts +0 -91
  379. package/renderer/lib/docs/mdx/index.ts +0 -26
  380. package/renderer/lib/docs/navigation/generator.ts +0 -348
  381. package/renderer/lib/docs/navigation/index.ts +0 -12
  382. package/renderer/lib/docs/navigation/types.ts +0 -123
  383. package/renderer/lib/docs-navigation-context.tsx +0 -80
  384. package/renderer/lib/multi-tenant/context.ts +0 -105
  385. package/renderer/lib/storage/blob.ts +0 -1083
  386. package/renderer/lib/utils/icons.ts +0 -48
  387. package/renderer/lib/utils.ts +0 -6
  388. package/renderer/next.config.ts +0 -76
@@ -0,0 +1,1930 @@
1
+ 'use client';
2
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
3
+ import { useEffect, useRef, useCallback, useState, useMemo } from 'react';
4
+ import { useChat } from '@ai-sdk/react';
5
+ import { DefaultChatTransport, lastAssistantMessageIsCompleteWithToolCalls } from 'ai';
6
+ import { ArrowUp, Square, Image as ImageIcon, X } from '@phosphor-icons/react';
7
+ import { Button } from '@/components/ui/button';
8
+ import { encodeCardMessage, detectErrorType } from './cards';
9
+ import { buildEndpointIndex, searchEndpoints, findRequestById, formatRequestForAI } from '@/lib/api-docs/agent/indexer';
10
+ import { ChatMessage, TypingIndicator } from './chat-message';
11
+ import { cn } from '@/lib/utils';
12
+ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
13
+ import { useModeContext, useNotes, useWorkspace } from '@/lib/api-docs/code-editor';
14
+ import { useCurrentRequestPayload, useSendTrigger } from '@/lib/api-docs/playground/context';
15
+ /**
16
+ * Custom transport that intercepts streaming events for write_file tool
17
+ * Wraps DefaultChatTransport and intercepts the stream to extract write_file content
18
+ */ function createStreamingTransport(config, callbacks) {
19
+ const transport = new DefaultChatTransport(config);
20
+ // Track active write_file tool calls
21
+ const activeWriteFiles = new Map();
22
+ const toolInputBuffers = new Map();
23
+ // Override the fetch to intercept the stream
24
+ const originalFetch = transport['fetch'] || globalThis.fetch;
25
+ transport['fetch'] = async (input, init)=>{
26
+ const response = await originalFetch(input, init);
27
+ if (!response.body) return response;
28
+ // Create a transform stream to intercept and process the SSE events
29
+ const reader = response.body.getReader();
30
+ const decoder = new TextDecoder();
31
+ let buffer = '';
32
+ const processedStream = new ReadableStream({
33
+ async start (controller) {
34
+ try {
35
+ while(true){
36
+ const { done, value } = await reader.read();
37
+ if (done) {
38
+ // Stream ended - close all active write_files
39
+ for (const [toolCallId] of activeWriteFiles){
40
+ callbacks.onWriteFileEnd(toolCallId);
41
+ }
42
+ activeWriteFiles.clear();
43
+ toolInputBuffers.clear();
44
+ controller.close();
45
+ break;
46
+ }
47
+ // Pass through the original data
48
+ controller.enqueue(value);
49
+ // Parse the SSE events for write_file content
50
+ buffer += decoder.decode(value, {
51
+ stream: true
52
+ });
53
+ const lines = buffer.split('\n');
54
+ buffer = lines.pop() || ''; // Keep incomplete line in buffer
55
+ for (const line of lines){
56
+ if (line.startsWith('data: ')) {
57
+ const data = line.slice(6);
58
+ if (data === '[DONE]') continue;
59
+ try {
60
+ const event = JSON.parse(data);
61
+ // Handle tool-input-start for write_file
62
+ if (event.type === 'tool-input-start' && event.toolName === 'write_file') {
63
+ toolInputBuffers.set(event.toolCallId, '');
64
+ }
65
+ // Handle tool-input-delta for write_file
66
+ if (event.type === 'tool-input-delta' && toolInputBuffers.has(event.toolCallId)) {
67
+ const currentBuffer = toolInputBuffers.get(event.toolCallId) || '';
68
+ const newBuffer = currentBuffer + (event.inputTextDelta || '');
69
+ toolInputBuffers.set(event.toolCallId, newBuffer);
70
+ // Try to parse partial JSON to extract content
71
+ try {
72
+ // Look for "content": " and extract what comes after
73
+ const contentMatch = newBuffer.match(/"content":\s*"((?:[^"\\]|\\.)*)/);
74
+ if (contentMatch) {
75
+ const escapedContent = contentMatch[1];
76
+ // Unescape the content
77
+ const content = escapedContent.replace(/\\n/g, '\n').replace(/\\t/g, '\t').replace(/\\r/g, '\r').replace(/\\"/g, '"').replace(/\\\\/g, '\\');
78
+ const activeWrite = activeWriteFiles.get(event.toolCallId);
79
+ // Also try to extract path
80
+ const pathMatch = newBuffer.match(/"path":\s*"([^"]+)"/);
81
+ const path = pathMatch?.[1] || '';
82
+ if (!activeWrite && path) {
83
+ // First time seeing content with path - start streaming
84
+ activeWriteFiles.set(event.toolCallId, {
85
+ path,
86
+ lastContent: content
87
+ });
88
+ callbacks.onWriteFileStart(event.toolCallId, path);
89
+ callbacks.onWriteFileDelta(event.toolCallId, path, content);
90
+ } else if (activeWrite && content.length > activeWrite.lastContent.length) {
91
+ // Content grew - send new content
92
+ activeWrite.lastContent = content;
93
+ callbacks.onWriteFileDelta(event.toolCallId, activeWrite.path, content);
94
+ }
95
+ }
96
+ } catch {
97
+ // JSON not complete yet, continue buffering
98
+ }
99
+ }
100
+ // Handle tool-input-available (complete)
101
+ if (event.type === 'tool-input-available' && event.toolName === 'write_file') {
102
+ const activeWrite = activeWriteFiles.get(event.toolCallId);
103
+ if (activeWrite) {
104
+ callbacks.onWriteFileEnd(event.toolCallId);
105
+ activeWriteFiles.delete(event.toolCallId);
106
+ }
107
+ toolInputBuffers.delete(event.toolCallId);
108
+ }
109
+ } catch {
110
+ // Invalid JSON, skip
111
+ }
112
+ }
113
+ }
114
+ }
115
+ } catch (err) {
116
+ controller.error(err);
117
+ }
118
+ }
119
+ });
120
+ // Return new response with the processed stream
121
+ return new Response(processedStream, {
122
+ headers: response.headers,
123
+ status: response.status,
124
+ statusText: response.statusText
125
+ });
126
+ };
127
+ return transport;
128
+ }
129
+ // Client-side cache for suggestions
130
+ const SUGGESTIONS_CACHE_KEY = 'brainfish-suggestions-cache';
131
+ function getSuggestionsFromCache(key) {
132
+ try {
133
+ const cached = localStorage.getItem(SUGGESTIONS_CACHE_KEY);
134
+ if (cached) {
135
+ const data = JSON.parse(cached);
136
+ if (data[key] && Date.now() - data[key].timestamp < 1000 * 60 * 60) {
137
+ return data[key].suggestions;
138
+ }
139
+ }
140
+ } catch {
141
+ // Ignore cache errors
142
+ }
143
+ return null;
144
+ }
145
+ function saveSuggestionsToCache(key, suggestions) {
146
+ try {
147
+ const cached = localStorage.getItem(SUGGESTIONS_CACHE_KEY);
148
+ const data = cached ? JSON.parse(cached) : {};
149
+ data[key] = {
150
+ suggestions,
151
+ timestamp: Date.now()
152
+ };
153
+ localStorage.setItem(SUGGESTIONS_CACHE_KEY, JSON.stringify(data));
154
+ } catch {
155
+ // Ignore cache errors
156
+ }
157
+ }
158
+ // Storage key for chat history
159
+ const CHAT_STORAGE_KEY = 'brainfish-agent-chat';
160
+ export function AgentChat({ collection, currentEndpoint, onNavigate, onPrefill, apiSummary, debugContext, onDebugContextConsumed, explainContext, onExplainContextConsumed, onOpenGlobalAuth, onNavigateToAuthTab, onNavigateToParamsTab, onNavigateToBodyTab, onNavigateToHeadersTab, onNavigateToDocSection, onNavigateToDocPage, onHasMessagesChange }) {
161
+ const messagesEndRef = useRef(null);
162
+ const textareaRef = useRef(null);
163
+ const processedDebugRef = useRef(null);
164
+ // Local input state (controlled by us, not useChat)
165
+ const [inputValue, setInputValue] = useState('');
166
+ // Image upload state
167
+ const [selectedImages, setSelectedImages] = useState([]);
168
+ const fileInputRef = useRef(null);
169
+ // Mode context for notes, API client, and docs
170
+ const { switchToNotes, switchToApiClient, switchToDocs, setActiveFilePath, setStreamingContent, triggerNotesRefresh } = useModeContext();
171
+ // Get workspace for notes
172
+ const apiSpecUrl = process.env.NEXT_PUBLIC_OPENAPI_URL || collection.name || 'default';
173
+ const { workspace } = useWorkspace(apiSpecUrl, collection.name);
174
+ const { notes, createNote, updateNote, deleteNote, deleteAllNotes, refresh: refreshNotes } = useNotes(workspace?.id || null);
175
+ // Suggestions state
176
+ const [suggestions, setSuggestions] = useState([]);
177
+ const [loadingSuggestions, setLoadingSuggestions] = useState(false);
178
+ const [showSuggestions, setShowSuggestions] = useState(true);
179
+ // Current request payload from playground
180
+ const { currentRequestPayload } = useCurrentRequestPayload();
181
+ // Send trigger - to invoke playground's send button
182
+ const { requestSend } = useSendTrigger();
183
+ // Build endpoint index for search
184
+ const endpointIndex = useMemo(()=>buildEndpointIndex(collection), [
185
+ collection
186
+ ]);
187
+ // Handler to open a file from chat (when user clicks on file name in tool display)
188
+ const handleOpenFile = useCallback((path)=>{
189
+ switchToNotes(path); // Pass path directly to avoid URL reset
190
+ }, [
191
+ switchToNotes
192
+ ]);
193
+ // Track last streamed content to compute deltas
194
+ const lastStreamedContent = useRef(new Map());
195
+ // Build documentation index for search (including nested pages)
196
+ const docIndex = useMemo(()=>{
197
+ if (!collection.docGroups) return [];
198
+ const pages = [];
199
+ // Helper to recursively add pages
200
+ const addPages = (pageList, groupTitle, groupId)=>{
201
+ for (const page of pageList){
202
+ pages.push({
203
+ id: page.id,
204
+ slug: page.slug,
205
+ title: page.title,
206
+ description: page.description || '',
207
+ group: groupTitle,
208
+ groupId: groupId
209
+ });
210
+ // Also add children if present
211
+ if (page.children && page.children.length > 0) {
212
+ addPages(page.children, page.title || groupTitle, groupId);
213
+ }
214
+ }
215
+ };
216
+ for (const group of collection.docGroups){
217
+ addPages(group.pages, group.title, group.id);
218
+ }
219
+ return pages;
220
+ }, [
221
+ collection
222
+ ]);
223
+ // Build changelog index for search
224
+ const changelogIndex = useMemo(()=>{
225
+ if (!collection.changelogReleases) return [];
226
+ return collection.changelogReleases.map((release)=>({
227
+ version: release.version,
228
+ date: release.date,
229
+ title: release.title,
230
+ slug: release.slug
231
+ }));
232
+ }, [
233
+ collection
234
+ ]);
235
+ // Create streaming transport with callbacks
236
+ const transport = useMemo(()=>{
237
+ return createStreamingTransport({
238
+ api: '/api/chat',
239
+ body: {
240
+ endpointIndex,
241
+ docIndex,
242
+ changelogIndex,
243
+ currentEndpointId: currentEndpoint?.id,
244
+ apiSummary: apiSummary || undefined
245
+ }
246
+ }, {
247
+ onWriteFileStart: (toolCallId, path)=>{
248
+ lastStreamedContent.current.set(toolCallId, '');
249
+ setStreamingContent({
250
+ path,
251
+ content: '',
252
+ isStreaming: true
253
+ });
254
+ // Navigate to the file being written
255
+ setActiveFilePath(path);
256
+ },
257
+ onWriteFileDelta: (toolCallId, path, fullContent)=>{
258
+ // Update streaming content with full content (already includes all previous)
259
+ setStreamingContent({
260
+ path,
261
+ content: fullContent,
262
+ isStreaming: true
263
+ });
264
+ },
265
+ onWriteFileEnd: (toolCallId)=>{
266
+ lastStreamedContent.current.delete(toolCallId);
267
+ // Don't clear streaming content immediately - let the final save do it
268
+ }
269
+ });
270
+ }, [
271
+ endpointIndex,
272
+ docIndex,
273
+ changelogIndex,
274
+ currentEndpoint?.id,
275
+ apiSummary,
276
+ setStreamingContent,
277
+ setActiveFilePath
278
+ ]);
279
+ // Chat hook with custom streaming transport
280
+ const { messages, status, sendMessage, addToolOutput, setMessages, stop } = useChat({
281
+ id: 'agent-chat',
282
+ transport,
283
+ // Automatically submit when all tool calls are complete
284
+ sendAutomaticallyWhen: lastAssistantMessageIsCompleteWithToolCalls,
285
+ // Handle client-side tool execution
286
+ async onToolCall ({ toolCall }) {
287
+ if ('dynamic' in toolCall && toolCall.dynamic) {
288
+ return;
289
+ }
290
+ const { toolName, toolCallId, input: toolInput } = toolCall;
291
+ try {
292
+ // === UNIFIED SEARCH TOOL ===
293
+ // Searches across docs, endpoints, changelog, and future content types
294
+ if (toolName === 'search') {
295
+ const { query, type = 'all' } = toolInput;
296
+ const results = [];
297
+ // Tokenize query
298
+ const queryTokens = query.toLowerCase().split(/\s+/).filter((t)=>t.length > 1);
299
+ const queryLower = query.toLowerCase();
300
+ // Search changelog if type is 'all' or 'changelog'
301
+ const changelogReleases = collection.changelogReleases || [];
302
+ if ((type === 'all' || type === 'changelog') && changelogReleases.length > 0) {
303
+ // Check for keywords that indicate changelog intent
304
+ const changelogKeywords = [
305
+ 'release',
306
+ 'changelog',
307
+ 'version',
308
+ 'update',
309
+ 'latest',
310
+ 'new',
311
+ 'what\'s new',
312
+ 'history'
313
+ ];
314
+ const isChangelogQuery = changelogKeywords.some((kw)=>queryLower.includes(kw));
315
+ for (const release of changelogReleases){
316
+ let score = 0;
317
+ const versionLower = release.version.toLowerCase();
318
+ const titleLower = release.title.toLowerCase();
319
+ const dateLower = release.date.toLowerCase();
320
+ // Boost score for changelog-related queries
321
+ if (isChangelogQuery) score += 50;
322
+ if (versionLower.includes(queryLower)) score += 100;
323
+ if (titleLower.includes(queryLower)) score += 80;
324
+ if (queryLower.includes('latest') && release === changelogReleases[0]) score += 150;
325
+ for (const token of queryTokens){
326
+ if (versionLower.includes(token)) score += 30;
327
+ if (titleLower.includes(token)) score += 25;
328
+ if (dateLower.includes(token)) score += 20;
329
+ }
330
+ if (score > 0) {
331
+ // Extract just the version from the slug (e.g., "changelog/v1.2.0" -> "v1.2.0")
332
+ const versionId = release.slug.replace(/^changelog\//, '');
333
+ results.push({
334
+ type: 'changelog',
335
+ id: versionId,
336
+ title: `${release.version} - ${release.title}`,
337
+ description: `Released ${release.date}`,
338
+ date: release.date,
339
+ group: 'Changelog',
340
+ score
341
+ });
342
+ }
343
+ }
344
+ }
345
+ // Search docs if type is 'all' or 'docs'
346
+ if ((type === 'all' || type === 'docs') && docIndex && docIndex.length > 0) {
347
+ for (const doc of docIndex){
348
+ let score = 0;
349
+ const titleLower = doc.title.toLowerCase();
350
+ const descLower = doc.description.toLowerCase();
351
+ const slugLower = doc.slug.toLowerCase();
352
+ if (titleLower.includes(queryLower)) score += 100;
353
+ if (slugLower.includes(queryLower)) score += 80;
354
+ if (descLower.includes(queryLower)) score += 60;
355
+ for (const token of queryTokens){
356
+ if (titleLower.includes(token)) score += 20;
357
+ if (slugLower.includes(token)) score += 25;
358
+ if (descLower.includes(token)) score += 15;
359
+ }
360
+ if (score > 0) {
361
+ results.push({
362
+ type: 'doc',
363
+ id: doc.slug,
364
+ title: doc.title,
365
+ description: doc.description,
366
+ group: doc.group,
367
+ score
368
+ });
369
+ }
370
+ }
371
+ }
372
+ // Search endpoints if type is 'all' or 'endpoints'
373
+ if ((type === 'all' || type === 'endpoints') && endpointIndex && endpointIndex.length > 0) {
374
+ for (const ep of endpointIndex){
375
+ let score = 0;
376
+ const nameLower = ep.name.toLowerCase();
377
+ const pathLower = ep.path.toLowerCase();
378
+ const descLower = (ep.description || '').toLowerCase();
379
+ if (nameLower.includes(queryLower)) score += 100;
380
+ if (pathLower.includes(queryLower)) score += 80;
381
+ if (descLower.includes(queryLower)) score += 60;
382
+ for (const token of queryTokens){
383
+ if (nameLower.includes(token)) score += 20;
384
+ if (pathLower.includes(token)) score += 25;
385
+ if (descLower.includes(token)) score += 15;
386
+ if (ep.method.toLowerCase() === token) score += 30;
387
+ }
388
+ if (score > 0) {
389
+ results.push({
390
+ type: 'endpoint',
391
+ id: ep.id,
392
+ title: ep.name,
393
+ description: ep.description || undefined,
394
+ method: ep.method,
395
+ path: ep.path,
396
+ score
397
+ });
398
+ }
399
+ }
400
+ }
401
+ // Sort by score and limit
402
+ const sortedResults = results.sort((a, b)=>b.score - a.score).slice(0, 10)// eslint-disable-next-line @typescript-eslint/no-unused-vars
403
+ .map(({ score, ...rest })=>rest);
404
+ addToolOutput({
405
+ tool: 'search',
406
+ toolCallId,
407
+ output: {
408
+ results: sortedResults,
409
+ message: sortedResults.length > 0 ? `Found ${sortedResults.length} result(s)` : 'No results found. Try different keywords.'
410
+ }
411
+ });
412
+ }
413
+ // === UNIFIED NAVIGATE TOOL ===
414
+ // Navigates to docs, endpoints, changelog, sections, and future content types
415
+ if (toolName === 'navigate') {
416
+ const { type, id, title } = toolInput;
417
+ if (type === 'doc') {
418
+ switchToDocs();
419
+ if (onNavigateToDocPage) {
420
+ onNavigateToDocPage(id);
421
+ }
422
+ addToolOutput({
423
+ tool: 'navigate',
424
+ toolCallId,
425
+ output: {
426
+ success: true,
427
+ type: 'doc',
428
+ id,
429
+ title,
430
+ message: `Opened ${title || id}`
431
+ }
432
+ });
433
+ } else if (type === 'changelog') {
434
+ // Navigate to changelog tab and scroll to specific release
435
+ // handleSelectDocPage handles the scrolling automatically
436
+ switchToDocs();
437
+ if (onNavigateToDocPage) {
438
+ onNavigateToDocPage(`changelog/${id}`);
439
+ }
440
+ addToolOutput({
441
+ tool: 'navigate',
442
+ toolCallId,
443
+ output: {
444
+ success: true,
445
+ type: 'changelog',
446
+ id,
447
+ title,
448
+ message: `Opened changelog ${title || id}`
449
+ }
450
+ });
451
+ } else if (type === 'endpoint') {
452
+ switchToDocs();
453
+ onNavigate(id);
454
+ const endpoint = endpointIndex.find((e)=>e.id === id);
455
+ addToolOutput({
456
+ tool: 'navigate',
457
+ toolCallId,
458
+ output: {
459
+ success: true,
460
+ type: 'endpoint',
461
+ id,
462
+ title: endpoint?.name || title,
463
+ method: endpoint?.method,
464
+ message: `Opened ${endpoint?.name || title || id}`
465
+ }
466
+ });
467
+ } else if (type === 'section') {
468
+ // Scroll to section on current page with retry for newly rendered content
469
+ const scrollToSection = ()=>{
470
+ const headings = document.querySelectorAll('.docs-content h1[id], .docs-content h2[id], .docs-content h3[id], .docs-content h4[id], .docs-prose h1[id], .docs-prose h2[id], .docs-prose h3[id], .docs-prose h4[id], .docs-page h1[id], .docs-page h2[id], .docs-page h3[id], .docs-page h4[id]');
471
+ for (const heading of headings){
472
+ const text = heading.textContent?.toLowerCase() || '';
473
+ const headingId = heading.getAttribute('id') || '';
474
+ if (text.includes(id.toLowerCase()) || headingId.includes(id.toLowerCase())) {
475
+ heading.scrollIntoView({
476
+ behavior: 'smooth',
477
+ block: 'start'
478
+ });
479
+ return true;
480
+ }
481
+ }
482
+ return false;
483
+ };
484
+ // Try immediately, then retry with delays if not found
485
+ let found = scrollToSection();
486
+ if (!found) {
487
+ // Retry after a short delay (page might still be rendering)
488
+ setTimeout(()=>{
489
+ found = scrollToSection();
490
+ if (!found) {
491
+ // Final retry after longer delay
492
+ setTimeout(()=>{
493
+ scrollToSection();
494
+ }, 300);
495
+ }
496
+ }, 100);
497
+ }
498
+ addToolOutput({
499
+ tool: 'navigate',
500
+ toolCallId,
501
+ output: {
502
+ success: true,
503
+ type: 'section',
504
+ id,
505
+ message: `Scrolled to ${id}`
506
+ }
507
+ });
508
+ }
509
+ }
510
+ // === LEGACY TOOL HANDLERS (for backwards compatibility) ===
511
+ // These can be removed once the unified tools are fully adopted
512
+ if (toolName === 'search_docs') {
513
+ // Redirect to unified search
514
+ const { query } = toolInput;
515
+ const results = docIndex.filter((doc)=>doc.title.toLowerCase().includes(query.toLowerCase()) || doc.slug.toLowerCase().includes(query.toLowerCase())).slice(0, 10);
516
+ addToolOutput({
517
+ tool: 'search_docs',
518
+ toolCallId,
519
+ output: {
520
+ results,
521
+ message: `Found ${results.length} result(s)`
522
+ }
523
+ });
524
+ }
525
+ if (toolName === 'search_endpoints') {
526
+ const { query, method } = toolInput;
527
+ const results = searchEndpoints(endpointIndex, query, method);
528
+ addToolOutput({
529
+ tool: 'search_endpoints',
530
+ toolCallId,
531
+ output: results
532
+ });
533
+ }
534
+ if (toolName === 'navigate_to_doc_page') {
535
+ const { slug, title } = toolInput;
536
+ switchToDocs();
537
+ if (onNavigateToDocPage) {
538
+ onNavigateToDocPage(slug);
539
+ }
540
+ addToolOutput({
541
+ tool: 'navigate_to_doc_page',
542
+ toolCallId,
543
+ output: {
544
+ success: true,
545
+ slug,
546
+ title,
547
+ message: `Navigated to "${title}"`
548
+ }
549
+ });
550
+ }
551
+ if (toolName === 'navigate_to_endpoint') {
552
+ const { endpointId } = toolInput;
553
+ switchToDocs();
554
+ onNavigate(endpointId);
555
+ const endpoint = endpointIndex.find((e)=>e.id === endpointId);
556
+ addToolOutput({
557
+ tool: 'navigate_to_endpoint',
558
+ toolCallId,
559
+ output: {
560
+ success: true,
561
+ endpoint
562
+ }
563
+ });
564
+ }
565
+ if (toolName === 'navigate_to_doc_section') {
566
+ const { sectionId, sectionName } = toolInput;
567
+ switchToDocs();
568
+ if (onNavigateToDocSection) {
569
+ onNavigateToDocSection(sectionId);
570
+ }
571
+ addToolOutput({
572
+ tool: 'navigate_to_doc_section',
573
+ toolCallId,
574
+ output: {
575
+ success: true,
576
+ sectionId,
577
+ sectionName
578
+ }
579
+ });
580
+ }
581
+ // Handle scroll_to_section tool - scroll within current page
582
+ if (toolName === 'scroll_to_section') {
583
+ const { query } = toolInput;
584
+ // Find matching heading in the current page
585
+ const findAndScrollToSection = ()=>{
586
+ const queryLower = query.toLowerCase();
587
+ const headings = document.querySelectorAll('.docs-content h1[id], .docs-content h2[id], .docs-content h3[id], .docs-content h4[id], .docs-content h5[id], .docs-content h6[id], .docs-prose h1[id], .docs-prose h2[id], .docs-prose h3[id], .docs-prose h4[id], .docs-prose h5[id], .docs-prose h6[id], .docs-page h1[id], .docs-page h2[id], .docs-page h3[id], .docs-page h4[id], .docs-page h5[id], .docs-page h6[id]');
588
+ let bestMatch = null;
589
+ for (const heading of headings){
590
+ const text = heading.textContent?.toLowerCase() || '';
591
+ const id = heading.getAttribute('id') || '';
592
+ let score = 0;
593
+ // Exact match
594
+ if (text === queryLower || id === queryLower) {
595
+ score = 100;
596
+ } else if (text.startsWith(queryLower) || id.startsWith(queryLower)) {
597
+ score = 80;
598
+ } else if (text.includes(queryLower) || id.includes(queryLower)) {
599
+ score = 60;
600
+ } else {
601
+ const queryWords = queryLower.split(/\s+/);
602
+ const textWords = text.split(/\s+/);
603
+ const matchingWords = queryWords.filter((qw)=>textWords.some((tw)=>tw.includes(qw) || qw.includes(tw)));
604
+ if (matchingWords.length > 0) {
605
+ score = 30 + matchingWords.length / queryWords.length * 30;
606
+ }
607
+ }
608
+ if (score > 0 && (!bestMatch || score > bestMatch.score)) {
609
+ bestMatch = {
610
+ element: heading,
611
+ score,
612
+ text: heading.textContent || '',
613
+ id
614
+ };
615
+ }
616
+ }
617
+ if (bestMatch) {
618
+ // Scroll to the section
619
+ bestMatch.element.scrollIntoView({
620
+ behavior: 'smooth',
621
+ block: 'start'
622
+ });
623
+ return {
624
+ found: true,
625
+ heading: bestMatch.text,
626
+ id: bestMatch.id
627
+ };
628
+ }
629
+ return {
630
+ found: false
631
+ };
632
+ };
633
+ // Try immediately
634
+ let result = findAndScrollToSection();
635
+ // If not found, retry with delays (page might still be rendering)
636
+ if (!result.found) {
637
+ setTimeout(()=>{
638
+ result = findAndScrollToSection();
639
+ if (!result.found) {
640
+ setTimeout(()=>{
641
+ findAndScrollToSection();
642
+ }, 300);
643
+ }
644
+ }, 100);
645
+ }
646
+ addToolOutput({
647
+ tool: 'scroll_to_section',
648
+ toolCallId,
649
+ output: result.found ? {
650
+ success: true,
651
+ heading: result.heading,
652
+ id: result.id,
653
+ message: `Scrolled to "${result.heading}"`
654
+ } : {
655
+ success: true,
656
+ heading: query,
657
+ id: query.toLowerCase().replace(/\s+/g, '-'),
658
+ message: `Scrolling to "${query}"`
659
+ }
660
+ });
661
+ }
662
+ if (toolName === 'prefill_parameters') {
663
+ const prefillData = toolInput;
664
+ onPrefill(prefillData);
665
+ addToolOutput({
666
+ tool: 'prefill_parameters',
667
+ toolCallId,
668
+ output: {
669
+ success: true,
670
+ ...prefillData
671
+ }
672
+ });
673
+ }
674
+ if (toolName === 'get_endpoint_details') {
675
+ const { endpointId } = toolInput;
676
+ const fullRequest = findRequestById(collection, endpointId);
677
+ if (fullRequest) {
678
+ const formattedDetails = formatRequestForAI(fullRequest);
679
+ addToolOutput({
680
+ tool: 'get_endpoint_details',
681
+ toolCallId,
682
+ output: formattedDetails
683
+ });
684
+ } else {
685
+ addToolOutput({
686
+ tool: 'get_endpoint_details',
687
+ toolCallId,
688
+ output: {
689
+ error: 'Endpoint not found'
690
+ }
691
+ });
692
+ }
693
+ }
694
+ // Handle get_current_request tool - returns current playground request payload
695
+ if (toolName === 'get_current_request') {
696
+ if (currentRequestPayload) {
697
+ addToolOutput({
698
+ tool: 'get_current_request',
699
+ toolCallId,
700
+ output: {
701
+ success: true,
702
+ request: {
703
+ method: currentRequestPayload.method,
704
+ url: currentRequestPayload.endpoint,
705
+ queryParams: currentRequestPayload.params.filter((p)=>p.active),
706
+ headers: currentRequestPayload.headers.filter((h)=>h.active),
707
+ body: currentRequestPayload.body,
708
+ contentType: currentRequestPayload.bodyContentType
709
+ }
710
+ }
711
+ });
712
+ } else {
713
+ addToolOutput({
714
+ tool: 'get_current_request',
715
+ toolCallId,
716
+ output: {
717
+ success: false,
718
+ error: 'No request payload available. Please select an endpoint first.'
719
+ }
720
+ });
721
+ }
722
+ }
723
+ // Handle switch_to_notes tool
724
+ if (toolName === 'switch_to_notes') {
725
+ switchToNotes();
726
+ addToolOutput({
727
+ tool: 'switch_to_notes',
728
+ toolCallId,
729
+ output: {
730
+ success: true,
731
+ message: 'Switched to Notes workspace'
732
+ }
733
+ });
734
+ }
735
+ // Handle switch_to_docs tool
736
+ if (toolName === 'switch_to_docs') {
737
+ switchToDocs();
738
+ addToolOutput({
739
+ tool: 'switch_to_docs',
740
+ toolCallId,
741
+ output: {
742
+ success: true,
743
+ message: 'Switched to Docs'
744
+ }
745
+ });
746
+ }
747
+ // Handle switch_to_api_client tool
748
+ if (toolName === 'switch_to_api_client') {
749
+ switchToApiClient();
750
+ addToolOutput({
751
+ tool: 'switch_to_api_client',
752
+ toolCallId,
753
+ output: {
754
+ success: true,
755
+ message: 'Switched to API Client'
756
+ }
757
+ });
758
+ }
759
+ // Handle list_notes tool (lists existing files to check for duplicates)
760
+ if (toolName === 'list_notes') {
761
+ const { filter } = toolInput;
762
+ // Switch to notes workspace
763
+ switchToNotes();
764
+ // Filter notes if a filter is provided
765
+ let filteredNotes = notes.filter((n)=>!n.path.endsWith('.folder')) // Exclude folder placeholders
766
+ ;
767
+ if (filter) {
768
+ const lowerFilter = filter.toLowerCase();
769
+ filteredNotes = filteredNotes.filter((n)=>n.path.toLowerCase().includes(lowerFilter));
770
+ }
771
+ // Return list of file paths with basic info
772
+ const notesList = filteredNotes.map((n)=>({
773
+ path: n.path,
774
+ updatedAt: n.updatedAt
775
+ }));
776
+ addToolOutput({
777
+ tool: 'list_notes',
778
+ toolCallId,
779
+ output: {
780
+ success: true,
781
+ count: notesList.length,
782
+ files: notesList,
783
+ message: notesList.length > 0 ? `Found ${notesList.length} file(s)${filter ? ` matching "${filter}"` : ''}` : `No files found${filter ? ` matching "${filter}"` : ' in workspace'}`
784
+ }
785
+ });
786
+ }
787
+ // Handle open_file tool (opens existing file)
788
+ if (toolName === 'open_file') {
789
+ const { path } = toolInput;
790
+ // Switch to notes mode with the file path
791
+ switchToNotes(path);
792
+ addToolOutput({
793
+ tool: 'open_file',
794
+ toolCallId,
795
+ output: {
796
+ success: true,
797
+ path,
798
+ message: `Opened ${path}`
799
+ }
800
+ });
801
+ }
802
+ // Handle create_folder tool (creates a folder for organizing files)
803
+ if (toolName === 'create_folder') {
804
+ const { path, description } = toolInput;
805
+ // Switch to notes workspace
806
+ switchToNotes();
807
+ // Folders are virtual in our system - files with paths like "folder/file.ext"
808
+ // automatically create the folder structure when files are added.
809
+ // We just acknowledge the intent - the folder will appear when files are created inside it.
810
+ addToolOutput({
811
+ tool: 'create_folder',
812
+ toolCallId,
813
+ output: {
814
+ success: true,
815
+ path,
816
+ description,
817
+ message: `Folder "${path}" will be created when files are added`
818
+ }
819
+ });
820
+ }
821
+ // Handle create_file tool (creates empty file)
822
+ if (toolName === 'create_file') {
823
+ const { path, description } = toolInput;
824
+ // Switch to notes workspace
825
+ switchToNotes();
826
+ if (workspace) {
827
+ try {
828
+ // Create truly empty file - content will be written by write_file
829
+ await createNote(path, '');
830
+ await refreshNotes();
831
+ // Trigger refresh in notes-mode UI
832
+ triggerNotesRefresh();
833
+ setActiveFilePath(path);
834
+ addToolOutput({
835
+ tool: 'create_file',
836
+ toolCallId,
837
+ output: {
838
+ success: true,
839
+ path,
840
+ description
841
+ }
842
+ });
843
+ } catch (err) {
844
+ console.error('[AgentChat] Failed to create file:', err);
845
+ addToolOutput({
846
+ tool: 'create_file',
847
+ toolCallId,
848
+ output: {
849
+ success: false,
850
+ error: err instanceof Error ? err.message : 'Failed to create file'
851
+ }
852
+ });
853
+ }
854
+ } else {
855
+ addToolOutput({
856
+ tool: 'create_file',
857
+ toolCallId,
858
+ output: {
859
+ success: false,
860
+ error: 'Workspace not available'
861
+ }
862
+ });
863
+ }
864
+ }
865
+ // Handle write_file tool (writes content to file)
866
+ // Note: Streaming already shows content in real-time via onWriteFileDelta
867
+ // This handler saves the final content to IndexedDB for persistence
868
+ if (toolName === 'write_file') {
869
+ const { path, content } = toolInput;
870
+ // Switch to notes workspace
871
+ switchToNotes();
872
+ if (workspace) {
873
+ try {
874
+ // Don't clear streaming content here - notes-mode will clear it after loading
875
+ // This prevents flash of empty content for mermaid previews
876
+ // Save the final content to IndexedDB for persistence
877
+ await updateNote(path, content);
878
+ await refreshNotes();
879
+ // Trigger refresh in notes-mode UI
880
+ triggerNotesRefresh();
881
+ // Force reload the file content from DB
882
+ setActiveFilePath(`${path}?t=${Date.now()}`);
883
+ addToolOutput({
884
+ tool: 'write_file',
885
+ toolCallId,
886
+ output: {
887
+ success: true,
888
+ path
889
+ }
890
+ });
891
+ } catch (err) {
892
+ console.error('[AgentChat] Failed to save file:', err);
893
+ setStreamingContent(null); // Clear on error too
894
+ addToolOutput({
895
+ tool: 'write_file',
896
+ toolCallId,
897
+ output: {
898
+ success: false,
899
+ error: err instanceof Error ? err.message : 'Failed to save file'
900
+ }
901
+ });
902
+ }
903
+ } else {
904
+ setStreamingContent(null);
905
+ addToolOutput({
906
+ tool: 'write_file',
907
+ toolCallId,
908
+ output: {
909
+ success: false,
910
+ error: 'Workspace not available'
911
+ }
912
+ });
913
+ }
914
+ }
915
+ // Handle delete_file tool (deletes a file from workspace)
916
+ if (toolName === 'delete_file') {
917
+ const { path, reason } = toolInput;
918
+ // Switch to notes workspace
919
+ switchToNotes();
920
+ if (workspace) {
921
+ try {
922
+ // Check if file exists
923
+ const fileExists = notes.some((n)=>n.path === path);
924
+ if (!fileExists) {
925
+ addToolOutput({
926
+ tool: 'delete_file',
927
+ toolCallId,
928
+ output: {
929
+ success: false,
930
+ error: `File not found: ${path}`
931
+ }
932
+ });
933
+ return;
934
+ }
935
+ // Delete the file
936
+ await deleteNote(path);
937
+ await refreshNotes();
938
+ // Trigger refresh in notes-mode UI
939
+ triggerNotesRefresh();
940
+ // Clear active file if it was the deleted one
941
+ setActiveFilePath(null);
942
+ addToolOutput({
943
+ tool: 'delete_file',
944
+ toolCallId,
945
+ output: {
946
+ success: true,
947
+ path,
948
+ reason,
949
+ message: `Deleted ${path}`
950
+ }
951
+ });
952
+ } catch (err) {
953
+ console.error('[AgentChat] Failed to delete file:', err);
954
+ addToolOutput({
955
+ tool: 'delete_file',
956
+ toolCallId,
957
+ output: {
958
+ success: false,
959
+ error: err instanceof Error ? err.message : 'Failed to delete file'
960
+ }
961
+ });
962
+ }
963
+ } else {
964
+ addToolOutput({
965
+ tool: 'delete_file',
966
+ toolCallId,
967
+ output: {
968
+ success: false,
969
+ error: 'Workspace not available'
970
+ }
971
+ });
972
+ }
973
+ }
974
+ // Handle delete_all_notes tool (deletes all files from workspace)
975
+ if (toolName === 'delete_all_notes') {
976
+ const { confirmed } = toolInput;
977
+ // Switch to notes workspace
978
+ switchToNotes();
979
+ if (!confirmed) {
980
+ addToolOutput({
981
+ tool: 'delete_all_notes',
982
+ toolCallId,
983
+ output: {
984
+ success: false,
985
+ error: 'Deletion not confirmed. Please confirm with the user first.'
986
+ }
987
+ });
988
+ return;
989
+ }
990
+ if (workspace) {
991
+ try {
992
+ // Refresh notes first to get accurate count (notes state might be stale)
993
+ await refreshNotes();
994
+ // Get fresh count from database directly
995
+ const { dbOperations } = await import('@/lib/api-docs/code-editor/db');
996
+ const currentNotes = await dbOperations.listNotes(workspace.id);
997
+ const visibleNotes = currentNotes.filter((n)=>!n.path.endsWith('.folder'));
998
+ const noteCount = visibleNotes.length;
999
+ // Get list of files for display
1000
+ const fileList = visibleNotes.map((n)=>n.path);
1001
+ if (noteCount === 0) {
1002
+ addToolOutput({
1003
+ tool: 'delete_all_notes',
1004
+ toolCallId,
1005
+ output: {
1006
+ success: true,
1007
+ deletedCount: 0,
1008
+ files: [],
1009
+ message: 'No notes to delete'
1010
+ }
1011
+ });
1012
+ return;
1013
+ }
1014
+ // Delete all notes
1015
+ await deleteAllNotes();
1016
+ await refreshNotes();
1017
+ // Trigger refresh in notes-mode UI
1018
+ triggerNotesRefresh();
1019
+ // Clear active file
1020
+ setActiveFilePath(null);
1021
+ addToolOutput({
1022
+ tool: 'delete_all_notes',
1023
+ toolCallId,
1024
+ output: {
1025
+ success: true,
1026
+ deletedCount: noteCount,
1027
+ files: fileList,
1028
+ message: `Deleted ${noteCount} note${noteCount !== 1 ? 's' : ''}`
1029
+ }
1030
+ });
1031
+ } catch (err) {
1032
+ console.error('[AgentChat] Failed to delete all notes:', err);
1033
+ addToolOutput({
1034
+ tool: 'delete_all_notes',
1035
+ toolCallId,
1036
+ output: {
1037
+ success: false,
1038
+ error: err instanceof Error ? err.message : 'Failed to delete notes'
1039
+ }
1040
+ });
1041
+ }
1042
+ } else {
1043
+ addToolOutput({
1044
+ tool: 'delete_all_notes',
1045
+ toolCallId,
1046
+ output: {
1047
+ success: false,
1048
+ error: 'Workspace not available'
1049
+ }
1050
+ });
1051
+ }
1052
+ }
1053
+ // Handle check_request_validity tool - validates if request is ready to send
1054
+ if (toolName === 'check_request_validity') {
1055
+ const { endpointId } = toolInput;
1056
+ const endpoint = findRequestById(collection, endpointId);
1057
+ if (!endpoint) {
1058
+ addToolOutput({
1059
+ tool: 'check_request_validity',
1060
+ toolCallId,
1061
+ output: {
1062
+ success: false,
1063
+ error: 'Endpoint not found'
1064
+ }
1065
+ });
1066
+ return;
1067
+ }
1068
+ // Get current request payload from context
1069
+ const payload = currentRequestPayload;
1070
+ // Analyze path parameters from the endpoint URL
1071
+ const pathParamMatches = endpoint.endpoint.match(/\{([^}]+)\}/g) || [];
1072
+ const requiredPathParams = pathParamMatches.map((p)=>p.slice(1, -1));
1073
+ // Check which path params have values
1074
+ const filledPathParams = requiredPathParams.filter((param)=>{
1075
+ // Check if the URL has been filled with actual values (not still containing {param})
1076
+ return payload?.endpoint && !payload.endpoint.includes(`{${param}}`);
1077
+ });
1078
+ const missingPathParams = requiredPathParams.filter((p)=>!filledPathParams.includes(p));
1079
+ // Analyze query parameters
1080
+ const requiredQueryParams = endpoint.params.filter((p)=>p.description?.toLowerCase().includes('required')).map((p)=>p.key);
1081
+ const filledQueryParams = (payload?.params || []).filter((p)=>p.active && p.value).map((p)=>p.key);
1082
+ const missingQueryParams = requiredQueryParams.filter((p)=>!filledQueryParams.includes(p));
1083
+ // Analyze body (for POST/PUT/PATCH)
1084
+ const needsBody = [
1085
+ 'POST',
1086
+ 'PUT',
1087
+ 'PATCH'
1088
+ ].includes(endpoint.method);
1089
+ const hasBody = !!(payload?.body && typeof payload.body === 'string' && payload.body.trim().length > 0);
1090
+ // Try to extract required fields from the body schema
1091
+ const bodyRequiredFields = [];
1092
+ if (endpoint.body.body && typeof endpoint.body.body === 'string') {
1093
+ try {
1094
+ const bodySchema = JSON.parse(endpoint.body.body);
1095
+ if (bodySchema.required && Array.isArray(bodySchema.required)) {
1096
+ bodyRequiredFields.push(...bodySchema.required);
1097
+ }
1098
+ } catch {
1099
+ // Not a schema, might be example body
1100
+ }
1101
+ }
1102
+ // Check missing body fields
1103
+ let missingBodyFields = [];
1104
+ if (hasBody && bodyRequiredFields.length > 0) {
1105
+ try {
1106
+ const currentBody = JSON.parse(payload?.body || '{}');
1107
+ missingBodyFields = bodyRequiredFields.filter((field)=>currentBody[field] === undefined || currentBody[field] === '');
1108
+ } catch {
1109
+ // Invalid JSON in body
1110
+ }
1111
+ }
1112
+ // Check authentication
1113
+ const needsAuth = endpoint.auth.authType !== 'none';
1114
+ const hasAuth = payload?.headers?.some((h)=>h.active && (h.key.toLowerCase() === 'authorization' || h.key.toLowerCase() === 'x-api-key')) || false;
1115
+ // Build validation result
1116
+ const isValid = missingPathParams.length === 0 && missingQueryParams.length === 0 && (!needsBody || hasBody) && (!needsAuth || hasAuth);
1117
+ // Build summary message
1118
+ const issues = [];
1119
+ if (missingPathParams.length > 0) {
1120
+ issues.push(`Missing path params: ${missingPathParams.join(', ')}`);
1121
+ }
1122
+ if (missingQueryParams.length > 0) {
1123
+ issues.push(`Missing required params: ${missingQueryParams.join(', ')}`);
1124
+ }
1125
+ if (needsBody && !hasBody) {
1126
+ issues.push('Request body is empty');
1127
+ }
1128
+ if (missingBodyFields.length > 0) {
1129
+ issues.push(`Missing body fields: ${missingBodyFields.join(', ')}`);
1130
+ }
1131
+ if (needsAuth && !hasAuth) {
1132
+ issues.push('Authentication not configured');
1133
+ }
1134
+ const summary = isValid ? 'Request is ready to send!' : `Issues found: ${issues.join('; ')}`;
1135
+ const result = {
1136
+ isValid,
1137
+ endpoint: {
1138
+ id: endpoint.id || endpointId,
1139
+ name: endpoint.name,
1140
+ method: endpoint.method,
1141
+ path: endpoint.endpoint
1142
+ },
1143
+ validation: {
1144
+ pathParams: {
1145
+ required: requiredPathParams,
1146
+ filled: filledPathParams,
1147
+ missing: missingPathParams
1148
+ },
1149
+ queryParams: {
1150
+ required: requiredQueryParams,
1151
+ filled: filledQueryParams,
1152
+ missing: missingQueryParams
1153
+ },
1154
+ body: {
1155
+ required: needsBody,
1156
+ hasContent: hasBody,
1157
+ requiredFields: bodyRequiredFields,
1158
+ missingFields: missingBodyFields
1159
+ },
1160
+ auth: {
1161
+ required: needsAuth,
1162
+ configured: hasAuth,
1163
+ type: needsAuth ? endpoint.auth.authType : null
1164
+ }
1165
+ },
1166
+ summary
1167
+ };
1168
+ addToolOutput({
1169
+ tool: 'check_request_validity',
1170
+ toolCallId,
1171
+ output: result
1172
+ });
1173
+ }
1174
+ // Handle send_request tool - triggers the playground's send button
1175
+ if (toolName === 'send_request') {
1176
+ const { confirmSend } = toolInput;
1177
+ if (!confirmSend) {
1178
+ addToolOutput({
1179
+ tool: 'send_request',
1180
+ toolCallId,
1181
+ output: {
1182
+ success: false,
1183
+ error: 'Request not confirmed'
1184
+ }
1185
+ });
1186
+ return;
1187
+ }
1188
+ if (!currentEndpoint) {
1189
+ addToolOutput({
1190
+ tool: 'send_request',
1191
+ toolCallId,
1192
+ output: {
1193
+ success: false,
1194
+ error: 'No endpoint selected'
1195
+ }
1196
+ });
1197
+ return;
1198
+ }
1199
+ // Trigger the playground's send button
1200
+ requestSend();
1201
+ addToolOutput({
1202
+ tool: 'send_request',
1203
+ toolCallId,
1204
+ output: {
1205
+ success: true,
1206
+ message: 'Request sent! Check the response viewer in the playground.'
1207
+ }
1208
+ });
1209
+ }
1210
+ } catch (err) {
1211
+ addToolOutput({
1212
+ tool: toolName,
1213
+ toolCallId,
1214
+ state: 'output-error',
1215
+ errorText: err instanceof Error ? err.message : 'Tool execution failed'
1216
+ });
1217
+ }
1218
+ }
1219
+ });
1220
+ // Derived loading state
1221
+ const isLoading = status === 'streaming' || status === 'submitted';
1222
+ // Load saved messages on mount
1223
+ useEffect(()=>{
1224
+ try {
1225
+ const saved = localStorage.getItem(CHAT_STORAGE_KEY);
1226
+ if (saved) {
1227
+ const parsed = JSON.parse(saved);
1228
+ if (Array.isArray(parsed) && parsed.length > 0) {
1229
+ setMessages(parsed);
1230
+ setShowSuggestions(false);
1231
+ }
1232
+ }
1233
+ } catch {
1234
+ // Ignore parse errors
1235
+ }
1236
+ }, [
1237
+ setMessages
1238
+ ]);
1239
+ // Save messages when they change
1240
+ useEffect(()=>{
1241
+ if (messages.length > 0) {
1242
+ try {
1243
+ localStorage.setItem(CHAT_STORAGE_KEY, JSON.stringify(messages));
1244
+ } catch {
1245
+ // Ignore storage errors
1246
+ }
1247
+ }
1248
+ }, [
1249
+ messages
1250
+ ]);
1251
+ // Notify parent about message count changes
1252
+ useEffect(()=>{
1253
+ onHasMessagesChange?.(messages.length > 0);
1254
+ }, [
1255
+ messages.length,
1256
+ onHasMessagesChange
1257
+ ]);
1258
+ // Auto-scroll to bottom
1259
+ useEffect(()=>{
1260
+ messagesEndRef.current?.scrollIntoView({
1261
+ behavior: 'smooth'
1262
+ });
1263
+ }, [
1264
+ messages
1265
+ ]);
1266
+ // Cache key for suggestions
1267
+ const suggestionsCacheKey = currentEndpoint?.id ? `endpoint:${currentEndpoint.id}` : `general:${endpointIndex.length}`;
1268
+ // Load dynamic suggestions from API
1269
+ useEffect(()=>{
1270
+ // Check cache first
1271
+ const cached = getSuggestionsFromCache(suggestionsCacheKey);
1272
+ if (cached) {
1273
+ setSuggestions(cached);
1274
+ return;
1275
+ }
1276
+ // Fetch from API
1277
+ let cancelled = false;
1278
+ setLoadingSuggestions(true);
1279
+ fetch('/api/suggestions', {
1280
+ method: 'POST',
1281
+ headers: {
1282
+ 'Content-Type': 'application/json'
1283
+ },
1284
+ body: JSON.stringify({
1285
+ endpointIndex,
1286
+ currentEndpointId: currentEndpoint?.id
1287
+ })
1288
+ }).then((res)=>res.json()).then((data)=>{
1289
+ if (cancelled) return;
1290
+ if (data.suggestions) {
1291
+ setSuggestions(data.suggestions);
1292
+ saveSuggestionsToCache(suggestionsCacheKey, data.suggestions);
1293
+ }
1294
+ }).catch((err)=>{
1295
+ if (cancelled) return;
1296
+ console.warn('[AgentChat] Failed to fetch suggestions:', err);
1297
+ // Fallback suggestions
1298
+ const fallback = currentEndpoint ? [
1299
+ {
1300
+ title: 'What does this',
1301
+ label: 'endpoint do?',
1302
+ prompt: `What does ${currentEndpoint.name} do?`
1303
+ },
1304
+ {
1305
+ title: 'What parameters',
1306
+ label: 'are required?',
1307
+ prompt: 'What parameters are required?'
1308
+ },
1309
+ {
1310
+ title: 'Python example',
1311
+ label: 'Show code',
1312
+ prompt: 'Show me a Python example'
1313
+ }
1314
+ ] : [
1315
+ {
1316
+ title: 'Find an endpoint',
1317
+ label: 'to create resources',
1318
+ prompt: 'Find endpoints for creating resources'
1319
+ },
1320
+ {
1321
+ title: 'How do I',
1322
+ label: 'authenticate?',
1323
+ prompt: 'How do I authenticate?'
1324
+ },
1325
+ {
1326
+ title: 'Overview',
1327
+ label: 'What can I do?',
1328
+ prompt: 'What can I do with this API?'
1329
+ }
1330
+ ];
1331
+ setSuggestions(fallback);
1332
+ }).finally(()=>{
1333
+ if (!cancelled) setLoadingSuggestions(false);
1334
+ });
1335
+ return ()=>{
1336
+ cancelled = true;
1337
+ };
1338
+ // eslint-disable-next-line react-hooks/exhaustive-deps
1339
+ }, [
1340
+ suggestionsCacheKey
1341
+ ]);
1342
+ // Track pending debug error type for showing options after AI response
1343
+ const [pendingErrorType, setPendingErrorType] = useState(null);
1344
+ const prevStatus = useRef(status);
1345
+ // Handle debug context from response viewer - show context card and send prompt
1346
+ useEffect(()=>{
1347
+ if (!debugContext || status === 'streaming' || status === 'submitted') {
1348
+ if (!debugContext) processedDebugRef.current = null;
1349
+ return;
1350
+ }
1351
+ const contextKey = JSON.stringify(debugContext);
1352
+ if (processedDebugRef.current === contextKey) return;
1353
+ processedDebugRef.current = contextKey;
1354
+ // Detect and store error type for later (to show options after AI responds)
1355
+ const errorType = detectErrorType(debugContext.status, debugContext.responseBody);
1356
+ setPendingErrorType(errorType);
1357
+ // Create a concise debug prompt
1358
+ const prompt = `Debug this ${debugContext.status} error and suggest a fix.`;
1359
+ // Encode the debug context as a card message
1360
+ const contextCardMessage = encodeCardMessage({
1361
+ type: 'debug_context',
1362
+ context: debugContext
1363
+ });
1364
+ // Add the debug context card as a user message
1365
+ const debugContextMessage = {
1366
+ id: `debug-${Date.now()}`,
1367
+ role: 'user',
1368
+ parts: [
1369
+ {
1370
+ type: 'text',
1371
+ text: contextCardMessage
1372
+ }
1373
+ ]
1374
+ };
1375
+ // Add context card, then send the actual prompt
1376
+ setMessages((prev)=>[
1377
+ ...prev,
1378
+ debugContextMessage
1379
+ ]);
1380
+ setShowSuggestions(false);
1381
+ // Small delay to let the card render, then send prompt
1382
+ setTimeout(()=>{
1383
+ sendMessage({
1384
+ role: 'user',
1385
+ parts: [
1386
+ {
1387
+ type: 'text',
1388
+ text: prompt
1389
+ }
1390
+ ]
1391
+ });
1392
+ }, 100);
1393
+ onDebugContextConsumed?.();
1394
+ }, [
1395
+ debugContext,
1396
+ status,
1397
+ sendMessage,
1398
+ setMessages,
1399
+ onDebugContextConsumed
1400
+ ]);
1401
+ // Track processed explain context to avoid duplicates
1402
+ const processedExplainRef = useRef(null);
1403
+ // Handle explain context from response viewer - send explain prompt
1404
+ useEffect(()=>{
1405
+ if (!explainContext || status === 'streaming' || status === 'submitted') {
1406
+ if (!explainContext) processedExplainRef.current = null;
1407
+ return;
1408
+ }
1409
+ const contextKey = JSON.stringify(explainContext);
1410
+ if (processedExplainRef.current === contextKey) return;
1411
+ processedExplainRef.current = contextKey;
1412
+ // Create a concise explain prompt
1413
+ const prompt = `Explain this ${explainContext.status} response. What does it mean and what are the key fields?`;
1414
+ // Encode the context as a card message (reuse debug_context type)
1415
+ const contextCardMessage = encodeCardMessage({
1416
+ type: 'debug_context',
1417
+ context: {
1418
+ ...explainContext,
1419
+ errorMessage: undefined
1420
+ }
1421
+ });
1422
+ // Add the context card as a user message
1423
+ const explainContextMessage = {
1424
+ id: `explain-${Date.now()}`,
1425
+ role: 'user',
1426
+ parts: [
1427
+ {
1428
+ type: 'text',
1429
+ text: contextCardMessage
1430
+ }
1431
+ ]
1432
+ };
1433
+ // Add context card, then send the actual prompt
1434
+ setMessages((prev)=>[
1435
+ ...prev,
1436
+ explainContextMessage
1437
+ ]);
1438
+ setShowSuggestions(false);
1439
+ // Small delay to let the card render, then send prompt
1440
+ setTimeout(()=>{
1441
+ sendMessage({
1442
+ role: 'user',
1443
+ parts: [
1444
+ {
1445
+ type: 'text',
1446
+ text: prompt
1447
+ }
1448
+ ]
1449
+ });
1450
+ }, 100);
1451
+ onExplainContextConsumed?.();
1452
+ }, [
1453
+ explainContext,
1454
+ status,
1455
+ sendMessage,
1456
+ setMessages,
1457
+ onExplainContextConsumed
1458
+ ]);
1459
+ // Add response options card after AI finishes responding to debug request
1460
+ useEffect(()=>{
1461
+ // Check if status changed from streaming/submitted to ready
1462
+ const wasProcessing = prevStatus.current === 'streaming' || prevStatus.current === 'submitted';
1463
+ const isNowReady = status === 'ready';
1464
+ if (wasProcessing && isNowReady && pendingErrorType) {
1465
+ // Encode the response options as a card message
1466
+ const optionsCardMessage = encodeCardMessage({
1467
+ type: 'response_options',
1468
+ errorType: pendingErrorType
1469
+ });
1470
+ // Add the response options card after AI response
1471
+ const responseOptionsMessage = {
1472
+ id: `options-${Date.now()}`,
1473
+ role: 'assistant',
1474
+ parts: [
1475
+ {
1476
+ type: 'text',
1477
+ text: optionsCardMessage
1478
+ }
1479
+ ]
1480
+ };
1481
+ setMessages((prev)=>[
1482
+ ...prev,
1483
+ responseOptionsMessage
1484
+ ]);
1485
+ // Clear pending error type
1486
+ setPendingErrorType(null);
1487
+ }
1488
+ // Update previous status
1489
+ prevStatus.current = status;
1490
+ }, [
1491
+ status,
1492
+ pendingErrorType,
1493
+ setMessages
1494
+ ]);
1495
+ // Handle image selection
1496
+ const handleImageSelect = useCallback(async (e)=>{
1497
+ const files = Array.from(e.target.files || []);
1498
+ if (files.length === 0) return;
1499
+ // Limit to 4 images total
1500
+ const availableSlots = 4 - selectedImages.length;
1501
+ const filesToProcess = files.slice(0, availableSlots);
1502
+ const newImages = await Promise.all(filesToProcess.map(async (file)=>{
1503
+ // Create preview URL
1504
+ const preview = URL.createObjectURL(file);
1505
+ // Convert to base64
1506
+ const base64 = await new Promise((resolve)=>{
1507
+ const reader = new FileReader();
1508
+ reader.onloadend = ()=>{
1509
+ const result = reader.result;
1510
+ // Extract base64 data without the data URL prefix
1511
+ resolve(result);
1512
+ };
1513
+ reader.readAsDataURL(file);
1514
+ });
1515
+ return {
1516
+ file,
1517
+ preview,
1518
+ base64
1519
+ };
1520
+ }));
1521
+ setSelectedImages((prev)=>[
1522
+ ...prev,
1523
+ ...newImages
1524
+ ]);
1525
+ // Reset file input
1526
+ if (fileInputRef.current) {
1527
+ fileInputRef.current.value = '';
1528
+ }
1529
+ }, [
1530
+ selectedImages.length
1531
+ ]);
1532
+ // Remove selected image
1533
+ const handleRemoveImage = useCallback((index)=>{
1534
+ setSelectedImages((prev)=>{
1535
+ const newImages = [
1536
+ ...prev
1537
+ ];
1538
+ // Revoke the preview URL to free memory
1539
+ URL.revokeObjectURL(newImages[index].preview);
1540
+ newImages.splice(index, 1);
1541
+ return newImages;
1542
+ });
1543
+ }, []);
1544
+ // Auto-resize textarea
1545
+ const handleInputChange = useCallback((e)=>{
1546
+ setInputValue(e.target.value);
1547
+ const textarea = e.target;
1548
+ textarea.style.height = 'auto';
1549
+ textarea.style.height = `${Math.min(textarea.scrollHeight, 128)}px`;
1550
+ }, []);
1551
+ // Handle form submit
1552
+ const handleSubmit = useCallback((e)=>{
1553
+ e.preventDefault();
1554
+ if (!inputValue.trim() && selectedImages.length === 0 || isLoading) return;
1555
+ const trimmedInput = inputValue.trim();
1556
+ setInputValue('');
1557
+ if (textareaRef.current) {
1558
+ textareaRef.current.style.height = 'auto';
1559
+ }
1560
+ // Build message parts with images and text
1561
+ const parts = [];
1562
+ // Add images first
1563
+ selectedImages.forEach((img)=>{
1564
+ parts.push({
1565
+ type: 'image',
1566
+ image: img.base64
1567
+ });
1568
+ });
1569
+ // Add text if present
1570
+ if (trimmedInput) {
1571
+ parts.push({
1572
+ type: 'text',
1573
+ text: trimmedInput
1574
+ });
1575
+ } else if (selectedImages.length > 0) {
1576
+ // If only images, add a default prompt
1577
+ parts.push({
1578
+ type: 'text',
1579
+ text: 'What can you tell me about this image?'
1580
+ });
1581
+ }
1582
+ // Clear selected images
1583
+ selectedImages.forEach((img)=>URL.revokeObjectURL(img.preview));
1584
+ setSelectedImages([]);
1585
+ setShowSuggestions(false);
1586
+ sendMessage({
1587
+ role: 'user',
1588
+ // Cast to allow image parts which our API handles correctly
1589
+ parts: parts
1590
+ });
1591
+ }, [
1592
+ inputValue,
1593
+ isLoading,
1594
+ sendMessage,
1595
+ selectedImages
1596
+ ]);
1597
+ // Handle suggestion click
1598
+ const handleSuggestionClick = useCallback((prompt)=>{
1599
+ setShowSuggestions(false);
1600
+ sendMessage({
1601
+ role: 'user',
1602
+ parts: [
1603
+ {
1604
+ type: 'text',
1605
+ text: prompt
1606
+ }
1607
+ ]
1608
+ });
1609
+ }, [
1610
+ sendMessage
1611
+ ]);
1612
+ // Handle delete single message
1613
+ const handleDeleteMessage = useCallback((messageId)=>{
1614
+ setMessages((prevMessages)=>{
1615
+ const newMessages = prevMessages.filter((msg)=>msg.id !== messageId);
1616
+ // Update localStorage with the new messages
1617
+ if (newMessages.length === 0) {
1618
+ localStorage.removeItem(CHAT_STORAGE_KEY);
1619
+ setShowSuggestions(true);
1620
+ } else {
1621
+ localStorage.setItem(CHAT_STORAGE_KEY, JSON.stringify(newMessages));
1622
+ }
1623
+ return newMessages;
1624
+ });
1625
+ }, [
1626
+ setMessages
1627
+ ]);
1628
+ // Handle keyboard shortcuts
1629
+ const handleKeyDown = useCallback((e)=>{
1630
+ if (e.key === 'Enter' && !e.shiftKey) {
1631
+ e.preventDefault();
1632
+ handleSubmit(e);
1633
+ }
1634
+ }, [
1635
+ handleSubmit
1636
+ ]);
1637
+ // Card actions for error handling
1638
+ const cardActions = useMemo(()=>({
1639
+ onOpenGlobalAuth,
1640
+ onNavigateToAuthTab,
1641
+ onNavigateToParamsTab,
1642
+ onNavigateToBodyTab,
1643
+ onNavigateToHeadersTab
1644
+ }), [
1645
+ onOpenGlobalAuth,
1646
+ onNavigateToAuthTab,
1647
+ onNavigateToParamsTab,
1648
+ onNavigateToBodyTab,
1649
+ onNavigateToHeadersTab
1650
+ ]);
1651
+ // Convert AI SDK messages to our format for rendering
1652
+ const renderableMessages = messages.map((msg)=>({
1653
+ id: msg.id,
1654
+ role: msg.role,
1655
+ content: getMessageContent(msg),
1656
+ images: getMessageImages(msg),
1657
+ toolInvocations: getToolInvocations(msg)
1658
+ }));
1659
+ return /*#__PURE__*/ _jsxs("div", {
1660
+ className: "flex flex-col h-full",
1661
+ children: [
1662
+ /*#__PURE__*/ _jsx("div", {
1663
+ className: "flex-1 overflow-y-auto flex flex-col",
1664
+ children: /*#__PURE__*/ _jsxs("div", {
1665
+ className: cn("max-w-2xl mx-auto px-4 py-4 w-full", messages.length === 0 && showSuggestions ? "flex-1 flex flex-col" : ""),
1666
+ children: [
1667
+ messages.length === 0 && showSuggestions && /*#__PURE__*/ _jsxs("div", {
1668
+ className: "flex w-full grow flex-col",
1669
+ children: [
1670
+ /*#__PURE__*/ _jsx("div", {
1671
+ className: "flex w-full grow flex-col items-center justify-center",
1672
+ children: /*#__PURE__*/ _jsxs("div", {
1673
+ className: "flex flex-col items-center text-center px-4",
1674
+ children: [
1675
+ /*#__PURE__*/ _jsx("h1", {
1676
+ className: "font-semibold text-2xl animate-in fade-in slide-in-from-bottom-1 duration-200",
1677
+ children: currentEndpoint ? currentEndpoint.name : 'Hello there!'
1678
+ }),
1679
+ /*#__PURE__*/ _jsx("p", {
1680
+ className: "text-muted-foreground text-lg animate-in fade-in slide-in-from-bottom-1 duration-200 delay-75",
1681
+ children: currentEndpoint ? 'Ask me anything about this endpoint' : 'How can I help you today?'
1682
+ })
1683
+ ]
1684
+ })
1685
+ }),
1686
+ /*#__PURE__*/ _jsx("div", {
1687
+ className: "grid w-full grid-cols-1 gap-2 pb-4 mt-8",
1688
+ children: loadingSuggestions ? // Loading skeletons
1689
+ /*#__PURE__*/ _jsx(_Fragment, {
1690
+ children: [
1691
+ 1,
1692
+ 2,
1693
+ 3,
1694
+ 4
1695
+ ].map((i)=>/*#__PURE__*/ _jsx("div", {
1696
+ className: "h-[52px] w-full rounded-2xl border bg-muted/30 animate-pulse",
1697
+ style: {
1698
+ animationDelay: `${i * 100}ms`
1699
+ }
1700
+ }, i))
1701
+ }) : suggestions.map((suggestion, index)=>/*#__PURE__*/ _jsx("div", {
1702
+ className: "animate-in fade-in slide-in-from-bottom-2 duration-200 fill-mode-both",
1703
+ style: {
1704
+ animationDelay: `${100 + index * 50}ms`
1705
+ },
1706
+ children: /*#__PURE__*/ _jsxs("button", {
1707
+ onClick: ()=>handleSuggestionClick(suggestion.prompt),
1708
+ className: "h-auto w-full flex flex-wrap items-start justify-start gap-1 rounded-2xl border px-4 py-3 text-left text-sm transition-colors hover:bg-muted",
1709
+ children: [
1710
+ /*#__PURE__*/ _jsx("span", {
1711
+ className: "font-medium",
1712
+ children: suggestion.title
1713
+ }),
1714
+ /*#__PURE__*/ _jsx("span", {
1715
+ className: "text-muted-foreground",
1716
+ children: suggestion.label
1717
+ })
1718
+ ]
1719
+ })
1720
+ }, suggestion.prompt))
1721
+ })
1722
+ ]
1723
+ }),
1724
+ renderableMessages.map((message, i)=>/*#__PURE__*/ _jsx(ChatMessage, {
1725
+ role: message.role,
1726
+ content: message.content,
1727
+ messageId: message.id,
1728
+ images: message.images,
1729
+ toolInvocations: message.toolInvocations,
1730
+ isStreaming: isLoading && i === renderableMessages.length - 1,
1731
+ onDeleteMessage: handleDeleteMessage,
1732
+ onNavigate: onNavigate,
1733
+ onNavigateToDocPage: onNavigateToDocPage,
1734
+ onOpenFile: handleOpenFile,
1735
+ cardActions: cardActions
1736
+ }, message.id)),
1737
+ status === 'submitted' && /*#__PURE__*/ _jsx(TypingIndicator, {}),
1738
+ /*#__PURE__*/ _jsx("div", {
1739
+ ref: messagesEndRef
1740
+ })
1741
+ ]
1742
+ })
1743
+ }),
1744
+ /*#__PURE__*/ _jsx("div", {
1745
+ className: "border-t border-border p-4",
1746
+ children: /*#__PURE__*/ _jsxs("form", {
1747
+ onSubmit: handleSubmit,
1748
+ className: "max-w-2xl mx-auto",
1749
+ children: [
1750
+ /*#__PURE__*/ _jsx("input", {
1751
+ ref: fileInputRef,
1752
+ type: "file",
1753
+ accept: "image/*",
1754
+ multiple: true,
1755
+ onChange: handleImageSelect,
1756
+ className: "hidden",
1757
+ "aria-hidden": "true"
1758
+ }),
1759
+ /*#__PURE__*/ _jsxs("div", {
1760
+ className: "relative rounded-2xl border border-border bg-background shadow-sm focus-within:ring-2 focus-within:ring-ring",
1761
+ children: [
1762
+ selectedImages.length > 0 && /*#__PURE__*/ _jsx("div", {
1763
+ className: "flex flex-wrap gap-2 p-3 pb-0",
1764
+ children: selectedImages.map((img, index)=>/*#__PURE__*/ _jsxs("div", {
1765
+ className: "relative group",
1766
+ children: [
1767
+ /*#__PURE__*/ _jsx("img", {
1768
+ src: img.preview,
1769
+ alt: `Selected image ${index + 1}`,
1770
+ className: "h-16 w-16 object-cover rounded-lg border border-border"
1771
+ }),
1772
+ /*#__PURE__*/ _jsx(Button, {
1773
+ type: "button",
1774
+ size: "icon",
1775
+ variant: "secondary",
1776
+ onClick: ()=>handleRemoveImage(index),
1777
+ className: "absolute -top-1.5 -right-1.5 h-4 w-4 min-w-0 rounded-full opacity-0 group-hover:opacity-100 transition-opacity shadow-sm p-2",
1778
+ "aria-label": `Remove image ${index + 1}`,
1779
+ children: /*#__PURE__*/ _jsx(X, {
1780
+ className: "h-2.5 w-2.5 p-1",
1781
+ weight: "bold"
1782
+ })
1783
+ })
1784
+ ]
1785
+ }, index))
1786
+ }),
1787
+ /*#__PURE__*/ _jsx("textarea", {
1788
+ ref: textareaRef,
1789
+ value: inputValue,
1790
+ onChange: handleInputChange,
1791
+ onKeyDown: handleKeyDown,
1792
+ placeholder: "Send a message...",
1793
+ className: cn("w-full resize-none bg-transparent px-4 pt-3 pb-12 text-sm", "placeholder:text-muted-foreground/70 outline-none", "min-h-[56px] max-h-32"),
1794
+ rows: 1,
1795
+ disabled: isLoading,
1796
+ "aria-label": "Message input"
1797
+ }),
1798
+ /*#__PURE__*/ _jsxs("div", {
1799
+ className: "absolute bottom-2 left-2 right-2 flex items-center justify-between",
1800
+ children: [
1801
+ /*#__PURE__*/ _jsx("div", {
1802
+ children: /*#__PURE__*/ _jsxs(Tooltip, {
1803
+ children: [
1804
+ /*#__PURE__*/ _jsx(TooltipTrigger, {
1805
+ asChild: true,
1806
+ children: /*#__PURE__*/ _jsx(Button, {
1807
+ type: "button",
1808
+ size: "icon",
1809
+ variant: "ghost",
1810
+ className: cn("h-8 w-8 rounded-full text-muted-foreground hover:text-foreground", selectedImages.length >= 4 && "opacity-50 cursor-not-allowed"),
1811
+ onClick: ()=>fileInputRef.current?.click(),
1812
+ disabled: isLoading || selectedImages.length >= 4,
1813
+ "aria-label": "Upload image",
1814
+ children: /*#__PURE__*/ _jsx(ImageIcon, {
1815
+ className: "h-4 w-4",
1816
+ weight: "bold"
1817
+ })
1818
+ })
1819
+ }),
1820
+ /*#__PURE__*/ _jsx(TooltipContent, {
1821
+ side: "top",
1822
+ children: selectedImages.length >= 4 ? 'Maximum 4 images' : 'Upload image'
1823
+ })
1824
+ ]
1825
+ })
1826
+ }),
1827
+ isLoading ? /*#__PURE__*/ _jsxs(Tooltip, {
1828
+ children: [
1829
+ /*#__PURE__*/ _jsx(TooltipTrigger, {
1830
+ asChild: true,
1831
+ children: /*#__PURE__*/ _jsx(Button, {
1832
+ type: "button",
1833
+ onClick: ()=>stop(),
1834
+ size: "icon",
1835
+ variant: "default",
1836
+ className: "h-8 w-8 rounded-full",
1837
+ "aria-label": "Stop generating",
1838
+ children: /*#__PURE__*/ _jsx(Square, {
1839
+ className: "h-3 w-3",
1840
+ weight: "fill"
1841
+ })
1842
+ })
1843
+ }),
1844
+ /*#__PURE__*/ _jsx(TooltipContent, {
1845
+ side: "top",
1846
+ children: "Stop generating"
1847
+ })
1848
+ ]
1849
+ }) : /*#__PURE__*/ _jsxs(Tooltip, {
1850
+ children: [
1851
+ /*#__PURE__*/ _jsx(TooltipTrigger, {
1852
+ asChild: true,
1853
+ children: /*#__PURE__*/ _jsx(Button, {
1854
+ type: "submit",
1855
+ disabled: !inputValue.trim(),
1856
+ size: "icon",
1857
+ variant: "default",
1858
+ className: "h-8 w-8 rounded-full",
1859
+ "aria-label": "Send message",
1860
+ children: /*#__PURE__*/ _jsx(ArrowUp, {
1861
+ className: "h-4 w-4",
1862
+ weight: "bold"
1863
+ })
1864
+ })
1865
+ }),
1866
+ /*#__PURE__*/ _jsx(TooltipContent, {
1867
+ side: "top",
1868
+ children: "Send message"
1869
+ })
1870
+ ]
1871
+ })
1872
+ ]
1873
+ })
1874
+ ]
1875
+ })
1876
+ ]
1877
+ })
1878
+ })
1879
+ ]
1880
+ });
1881
+ }
1882
+ // Helper to extract text content from a message
1883
+ function getMessageContent(msg) {
1884
+ if (!msg.parts) return '';
1885
+ const textParts = msg.parts.filter((part)=>part.type === 'text' && typeof part.text === 'string');
1886
+ let content = textParts.map((p)=>p.text).join('');
1887
+ // Remove any trailing JSON blocks that the model might have included
1888
+ content = content.replace(/\n*```json[\s\S]*?```\n*/g, '');
1889
+ content = content.replace(/\n*\{[\s\S]*?"id":\s*"[^"]+",[\s\S]*?"name":\s*"[^"]+"[\s\S]*?\}\s*$/g, '');
1890
+ return content.trim();
1891
+ }
1892
+ // Helper to extract images from a message
1893
+ function getMessageImages(msg) {
1894
+ if (!msg.parts) return [];
1895
+ return msg.parts.filter((part)=>part.type === 'image' && typeof part.image === 'string').map((part)=>({
1896
+ image: part.image
1897
+ }));
1898
+ }
1899
+ // Helper to extract tool invocations from a message
1900
+ function getToolInvocations(msg) {
1901
+ if (!msg.parts) return [];
1902
+ // Use a Map to deduplicate by toolCallId (keep latest version)
1903
+ const toolCallMap = new Map();
1904
+ msg.parts.filter((part)=>{
1905
+ return typeof part === 'object' && part !== null && 'type' in part && typeof part.type === 'string' && part.type.startsWith('tool-') && part.type !== 'tool-call' && 'toolCallId' in part;
1906
+ }).forEach((part)=>{
1907
+ const toolName = part.toolName || part.type.replace('tool-', '');
1908
+ // Only update if we don't have this tool call yet, or if the new one has more info
1909
+ const existing = toolCallMap.get(part.toolCallId);
1910
+ const newState = getToolState(part);
1911
+ // Prefer 'result' state over 'pending'
1912
+ if (!existing || newState === 'result' || newState === 'error' && existing.state === 'pending') {
1913
+ toolCallMap.set(part.toolCallId, {
1914
+ toolCallId: part.toolCallId,
1915
+ toolName,
1916
+ args: part.input || part.args || {},
1917
+ result: part.output ?? part.result,
1918
+ state: newState
1919
+ });
1920
+ }
1921
+ });
1922
+ return Array.from(toolCallMap.values());
1923
+ }
1924
+ // Helper to determine tool state from part
1925
+ function getToolState(part) {
1926
+ if (part.state === 'output-error') return 'error';
1927
+ if (part.state === 'output-available') return 'result';
1928
+ if (part.state === 'input-streaming' || part.state === 'input-available') return 'pending';
1929
+ return 'result';
1930
+ }