@brainfish-ai/devdoc 0.1.42 → 0.1.44

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