@amir83nasr/10router 0.1.11 → 0.1.14

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 (396) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-path-routes-manifest.json +5 -6
  3. package/.next/build-manifest.json +2 -2
  4. package/.next/prerender-manifest.json +22 -50
  5. package/.next/routes-manifest.json +0 -6
  6. package/.next/server/app/(dashboard)/dashboard/cli-tools/[toolId]/page.js +2 -2
  7. package/.next/server/app/(dashboard)/dashboard/cli-tools/[toolId]/page_client-reference-manifest.js +1 -1
  8. package/.next/server/app/(dashboard)/dashboard/cli-tools/page.js +1 -1
  9. package/.next/server/app/(dashboard)/dashboard/cli-tools/page_client-reference-manifest.js +1 -1
  10. package/.next/server/app/(dashboard)/dashboard/combos/page.js +1 -1
  11. package/.next/server/app/(dashboard)/dashboard/combos/page_client-reference-manifest.js +1 -1
  12. package/.next/server/app/(dashboard)/dashboard/console-log/page.js +1 -1
  13. package/.next/server/app/(dashboard)/dashboard/console-log/page_client-reference-manifest.js +1 -1
  14. package/.next/server/app/(dashboard)/dashboard/endpoint/page.js +1 -1
  15. package/.next/server/app/(dashboard)/dashboard/endpoint/page_client-reference-manifest.js +1 -1
  16. package/.next/server/app/(dashboard)/dashboard/media-providers/systemone/[id]/page.js +2 -2
  17. package/.next/server/app/(dashboard)/dashboard/media-providers/systemone/[id]/page_client-reference-manifest.js +1 -1
  18. package/.next/server/app/(dashboard)/dashboard/media-providers/systemone/page.js +1 -1
  19. package/.next/server/app/(dashboard)/dashboard/media-providers/systemone/page_client-reference-manifest.js +1 -1
  20. package/.next/server/app/(dashboard)/dashboard/page.js +1 -1
  21. package/.next/server/app/(dashboard)/dashboard/page_client-reference-manifest.js +1 -1
  22. package/.next/server/app/(dashboard)/dashboard/profile/page.js +1 -1
  23. package/.next/server/app/(dashboard)/dashboard/profile/page_client-reference-manifest.js +1 -1
  24. package/.next/server/app/(dashboard)/dashboard/providers/[id]/page.js +2 -2
  25. package/.next/server/app/(dashboard)/dashboard/providers/[id]/page_client-reference-manifest.js +1 -1
  26. package/.next/server/app/(dashboard)/dashboard/providers/new/page.js +1 -1
  27. package/.next/server/app/(dashboard)/dashboard/providers/new/page_client-reference-manifest.js +1 -1
  28. package/.next/server/app/(dashboard)/dashboard/providers/page.js +1 -1
  29. package/.next/server/app/(dashboard)/dashboard/providers/page_client-reference-manifest.js +1 -1
  30. package/.next/server/app/(dashboard)/dashboard/proxy-pools/page.js +1 -1
  31. package/.next/server/app/(dashboard)/dashboard/proxy-pools/page_client-reference-manifest.js +1 -1
  32. package/.next/server/app/(dashboard)/dashboard/pxpipe/page.js +1 -1
  33. package/.next/server/app/(dashboard)/dashboard/pxpipe/page_client-reference-manifest.js +1 -1
  34. package/.next/server/app/(dashboard)/dashboard/quota/page.js +1 -1
  35. package/.next/server/app/(dashboard)/dashboard/quota/page_client-reference-manifest.js +1 -1
  36. package/.next/server/app/(dashboard)/dashboard/token-saver/page.js +1 -1
  37. package/.next/server/app/(dashboard)/dashboard/token-saver/page_client-reference-manifest.js +1 -1
  38. package/.next/server/app/(dashboard)/dashboard/usage/page.js +1 -1
  39. package/.next/server/app/(dashboard)/dashboard/usage/page_client-reference-manifest.js +1 -1
  40. package/.next/server/app/_global-error/page.js +2 -2
  41. package/.next/server/app/_global-error/page_client-reference-manifest.js +1 -1
  42. package/.next/server/app/_global-error.html +1 -1
  43. package/.next/server/app/_global-error.rsc +1 -1
  44. package/.next/server/app/_global-error.segments/_full.segment.rsc +1 -1
  45. package/.next/server/app/_global-error.segments/_global-error/__PAGE__.segment.rsc +1 -1
  46. package/.next/server/app/_global-error.segments/_tree.segment.rsc +1 -1
  47. package/.next/server/app/_not-found/page.js +1 -1
  48. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  49. package/.next/server/app/_not-found.html +1 -1
  50. package/.next/server/app/_not-found.rsc +6 -6
  51. package/.next/server/app/_not-found.segments/_full.segment.rsc +6 -6
  52. package/.next/server/app/_not-found.segments/_not-found/__PAGE__.segment.rsc +6 -6
  53. package/.next/server/app/_not-found.segments/_tree.segment.rsc +2 -2
  54. package/.next/server/app/api/auth/login/route.js +1 -1
  55. package/.next/server/app/api/auth/logout/route.js +1 -1
  56. package/.next/server/app/api/auth/oidc/callback/route.js +1 -1
  57. package/.next/server/app/api/auth/oidc/start/route.js +1 -1
  58. package/.next/server/app/api/auth/oidc/test/route.js +1 -1
  59. package/.next/server/app/api/auth/reset-password/route.js +1 -1
  60. package/.next/server/app/api/auth/saml/acs/route.js +1 -1
  61. package/.next/server/app/api/auth/saml/metadata/route.js +2 -2
  62. package/.next/server/app/api/auth/saml/start/route.js +2 -2
  63. package/.next/server/app/api/auth/saml/test/route.js +1 -1
  64. package/.next/server/app/api/auth/status/route.js +1 -1
  65. package/.next/server/app/api/changelog/route.js +1 -1
  66. package/.next/server/app/api/cli-tools/all-statuses/route.js +1 -1
  67. package/.next/server/app/api/cli-tools/claude-settings/route.js +1 -1
  68. package/.next/server/app/api/cli-tools/codex-settings/route.js +1 -1
  69. package/.next/server/app/api/cli-tools/cowork-mcp-registry/route.js +1 -1
  70. package/.next/server/app/api/cli-tools/cowork-mcp-tools/route.js +2 -2
  71. package/.next/server/app/api/cli-tools/cowork-settings/route.js +1 -1
  72. package/.next/server/app/api/cli-tools/dsh-settings/route.js +1 -1
  73. package/.next/server/app/api/cli-tools/opencode-settings/route.js +1 -1
  74. package/.next/server/app/api/combos/[id]/route.js +1 -1
  75. package/.next/server/app/api/combos/route.js +1 -1
  76. package/.next/server/app/api/headroom/extras/route.js +1 -1
  77. package/.next/server/app/api/headroom/proxy/[...path]/route.js +1 -1
  78. package/.next/server/app/api/headroom/restart/route.js +1 -1
  79. package/.next/server/app/api/headroom/start/route.js +1 -1
  80. package/.next/server/app/api/headroom/status/route.js +1 -1
  81. package/.next/server/app/api/headroom/stop/route.js +1 -1
  82. package/.next/server/app/api/health/route.js +1 -1
  83. package/.next/server/app/api/init/route.js +1 -1
  84. package/.next/server/app/api/keys/[id]/route.js +1 -1
  85. package/.next/server/app/api/keys/route.js +1 -1
  86. package/.next/server/app/api/locale/route.js +1 -1
  87. package/.next/server/app/api/mcp/[plugin]/message/route.js +1 -1
  88. package/.next/server/app/api/mcp/[plugin]/sse/route.js +1 -1
  89. package/.next/server/app/api/media-providers/tts/deepgram/voices/route.js +1 -1
  90. package/.next/server/app/api/media-providers/tts/elevenlabs/voices/route.js +1 -1
  91. package/.next/server/app/api/media-providers/tts/inworld/voices/route.js +1 -1
  92. package/.next/server/app/api/media-providers/tts/minimax/voices/route.js +1 -1
  93. package/.next/server/app/api/media-providers/tts/voices/route.js +1 -1
  94. package/.next/server/app/api/models/alias/route.js +1 -1
  95. package/.next/server/app/api/models/availability/route.js +1 -1
  96. package/.next/server/app/api/models/catalog-sync/route.js +1 -1
  97. package/.next/server/app/api/models/custom/route.js +1 -1
  98. package/.next/server/app/api/models/disabled/route.js +1 -1
  99. package/.next/server/app/api/models/route.js +1 -1
  100. package/.next/server/app/api/models/test/route.js +1 -1
  101. package/.next/server/app/api/oauth/[provider]/[action]/route.js +1 -1
  102. package/.next/server/app/api/oauth/codex/bulk-import/route.js +1 -1
  103. package/.next/server/app/api/oauth/codex/import-token/route.js +1 -1
  104. package/.next/server/app/api/oauth/cursor/auto-import/route.js +2 -2
  105. package/.next/server/app/api/oauth/cursor/import/route.js +1 -1
  106. package/.next/server/app/api/oauth/gitlab/pat/route.js +1 -1
  107. package/.next/server/app/api/oauth/grok-cli/bulk-import/route.js +1 -1
  108. package/.next/server/app/api/oauth/iflow/cookie/route.js +1 -1
  109. package/.next/server/app/api/oauth/xiaomi-mimo/api-key/route.js +1 -1
  110. package/.next/server/app/api/oauth/xiaomi-mimo/auto-import/route.js +2 -2
  111. package/.next/server/app/api/pricing/route.js +1 -1
  112. package/.next/server/app/api/provider-nodes/[id]/route.js +1 -1
  113. package/.next/server/app/api/provider-nodes/route.js +1 -1
  114. package/.next/server/app/api/provider-nodes/validate/route.js +1 -1
  115. package/.next/server/app/api/providers/[id]/models/route.js +1 -1
  116. package/.next/server/app/api/providers/[id]/route.js +1 -1
  117. package/.next/server/app/api/providers/[id]/test/route.js +1 -1
  118. package/.next/server/app/api/providers/[id]/test-models/route.js +1 -1
  119. package/.next/server/app/api/providers/client/route.js +1 -1
  120. package/.next/server/app/api/providers/kilo/free-models/route.js +1 -1
  121. package/.next/server/app/api/providers/route.js +1 -1
  122. package/.next/server/app/api/providers/suggested-models/route.js +1 -1
  123. package/.next/server/app/api/providers/test-batch/route.js +1 -1
  124. package/.next/server/app/api/providers/validate/route.js +1 -1
  125. package/.next/server/app/api/proxy-pools/[id]/route.js +1 -1
  126. package/.next/server/app/api/proxy-pools/[id]/test/route.js +1 -1
  127. package/.next/server/app/api/proxy-pools/cloudflare-deploy/route.js +2 -2
  128. package/.next/server/app/api/proxy-pools/deno-deploy/route.js +2 -2
  129. package/.next/server/app/api/proxy-pools/route.js +1 -1
  130. package/.next/server/app/api/proxy-pools/vercel-deploy/route.js +2 -2
  131. package/.next/server/app/api/pxpipe/health/route.js +2 -2
  132. package/.next/server/app/api/pxpipe/install/route.js +1 -1
  133. package/.next/server/app/api/pxpipe/logs/route.js +1 -1
  134. package/.next/server/app/api/pxpipe/restart/route.js +1 -1
  135. package/.next/server/app/api/pxpipe/start/route.js +1 -1
  136. package/.next/server/app/api/pxpipe/stats/route.js +1 -1
  137. package/.next/server/app/api/pxpipe/status/route.js +1 -1
  138. package/.next/server/app/api/pxpipe/stop/route.js +1 -1
  139. package/.next/server/app/api/settings/database/route.js +1 -1
  140. package/.next/server/app/api/settings/proxy-test/route.js +1 -1
  141. package/.next/server/app/api/settings/require-login/route.js +1 -1
  142. package/.next/server/app/api/settings/route.js +1 -1
  143. package/.next/server/app/api/shutdown/route.js +1 -1
  144. package/.next/server/app/api/tags/route.js +1 -1
  145. package/.next/server/app/api/translator/console-logs/route.js +1 -1
  146. package/.next/server/app/api/translator/console-logs/stream/route.js +1 -1
  147. package/.next/server/app/api/translator/load/route.js +1 -1
  148. package/.next/server/app/api/translator/save/route.js +1 -1
  149. package/.next/server/app/api/translator/send/route.js +1 -1
  150. package/.next/server/app/api/translator/translate/route.js +1 -1
  151. package/.next/server/app/api/tunnel/disable/route.js +1 -1
  152. package/.next/server/app/api/tunnel/enable/route.js +1 -1
  153. package/.next/server/app/api/tunnel/status/route.js +1 -1
  154. package/.next/server/app/api/tunnel/tailscale-check/route.js +1 -1
  155. package/.next/server/app/api/tunnel/tailscale-disable/route.js +1 -1
  156. package/.next/server/app/api/tunnel/tailscale-enable/route.js +1 -1
  157. package/.next/server/app/api/tunnel/tailscale-install/route.js +2 -2
  158. package/.next/server/app/api/usage/[connectionId]/codex-reset-credits/route.js +1 -1
  159. package/.next/server/app/api/usage/[connectionId]/route.js +1 -1
  160. package/.next/server/app/api/usage/chart/route.js +1 -1
  161. package/.next/server/app/api/usage/health/route.js +1 -1
  162. package/.next/server/app/api/usage/history/route.js +1 -1
  163. package/.next/server/app/api/usage/logs/route.js +1 -1
  164. package/.next/server/app/api/usage/providers/route.js +1 -1
  165. package/.next/server/app/api/usage/request-details/route.js +1 -1
  166. package/.next/server/app/api/usage/request-logs/route.js +1 -1
  167. package/.next/server/app/api/usage/stats/route.js +1 -1
  168. package/.next/server/app/api/usage/stream/route.js +2 -2
  169. package/.next/server/app/api/v1/api/chat/route.js +1 -1
  170. package/.next/server/app/api/v1/audio/speech/route.js +1 -1
  171. package/.next/server/app/api/v1/audio/transcriptions/route.js +1 -1
  172. package/.next/server/app/api/v1/audio/voices/route.js +1 -1
  173. package/.next/server/app/api/v1/chat/completions/route.js +1 -1
  174. package/.next/server/app/api/v1/embeddings/route.js +1 -1
  175. package/.next/server/app/api/v1/images/generations/route.js +2 -2
  176. package/.next/server/app/api/v1/messages/count_tokens/route.js +1 -1
  177. package/.next/server/app/api/v1/messages/route.js +1 -1
  178. package/.next/server/app/api/v1/models/[...model]/route.js +1 -1
  179. package/.next/server/app/api/v1/models/info/route.js +1 -1
  180. package/.next/server/app/api/v1/models/route.js +1 -1
  181. package/.next/server/app/api/v1/responses/compact/route.js +1 -1
  182. package/.next/server/app/api/v1/responses/route.js +1 -1
  183. package/.next/server/app/api/v1/route.js +1 -1
  184. package/.next/server/app/api/v1/search/route.js +1 -1
  185. package/.next/server/app/api/v1/systemone/route.js +1 -1
  186. package/.next/server/app/api/v1/videos/[id]/route.js +1 -1
  187. package/.next/server/app/api/v1/videos/edits/route.js +1 -1
  188. package/.next/server/app/api/v1/videos/extensions/route.js +1 -1
  189. package/.next/server/app/api/v1/videos/generations/route.js +1 -1
  190. package/.next/server/app/api/v1/web/fetch/route.js +1 -1
  191. package/.next/server/app/api/v1beta/models/[...path]/route.js +1 -1
  192. package/.next/server/app/api/v1beta/models/route.js +1 -1
  193. package/.next/server/app/api/version/route.js +1 -1
  194. package/.next/server/app/api/version/shutdown/route.js +1 -1
  195. package/.next/server/app/api/version/update/route.js +1 -1
  196. package/.next/server/app/callback/page.js +1 -1
  197. package/.next/server/app/callback/page_client-reference-manifest.js +1 -1
  198. package/.next/server/app/callback.html +1 -1
  199. package/.next/server/app/callback.rsc +7 -7
  200. package/.next/server/app/callback.segments/_full.segment.rsc +7 -7
  201. package/.next/server/app/callback.segments/_tree.segment.rsc +2 -2
  202. package/.next/server/app/callback.segments/callback/__PAGE__.segment.rsc +7 -7
  203. package/.next/server/app/dashboard/cli-tools.html +1 -1
  204. package/.next/server/app/dashboard/cli-tools.rsc +9 -9
  205. package/.next/server/app/dashboard/cli-tools.segments/!KGRhc2hib2FyZCk/dashboard/cli-tools/__PAGE__.segment.rsc +8 -8
  206. package/.next/server/app/dashboard/cli-tools.segments/_full.segment.rsc +9 -9
  207. package/.next/server/app/dashboard/cli-tools.segments/_tree.segment.rsc +2 -2
  208. package/.next/server/app/dashboard/combos.html +1 -1
  209. package/.next/server/app/dashboard/combos.rsc +8 -8
  210. package/.next/server/app/dashboard/combos.segments/!KGRhc2hib2FyZCk/dashboard/combos/__PAGE__.segment.rsc +8 -8
  211. package/.next/server/app/dashboard/combos.segments/_full.segment.rsc +8 -8
  212. package/.next/server/app/dashboard/combos.segments/_tree.segment.rsc +2 -2
  213. package/.next/server/app/dashboard/endpoint.html +1 -1
  214. package/.next/server/app/dashboard/endpoint.rsc +9 -9
  215. package/.next/server/app/dashboard/endpoint.segments/!KGRhc2hib2FyZCk/dashboard/endpoint/__PAGE__.segment.rsc +8 -8
  216. package/.next/server/app/dashboard/endpoint.segments/_full.segment.rsc +9 -9
  217. package/.next/server/app/dashboard/endpoint.segments/_tree.segment.rsc +2 -2
  218. package/.next/server/app/dashboard/media-providers/systemone.html +1 -1
  219. package/.next/server/app/dashboard/media-providers/systemone.rsc +8 -8
  220. package/.next/server/app/dashboard/media-providers/systemone.segments/!KGRhc2hib2FyZCk/dashboard/media-providers/systemone/__PAGE__.segment.rsc +8 -8
  221. package/.next/server/app/dashboard/media-providers/systemone.segments/_full.segment.rsc +8 -8
  222. package/.next/server/app/dashboard/media-providers/systemone.segments/_tree.segment.rsc +2 -2
  223. package/.next/server/app/dashboard/profile.html +1 -1
  224. package/.next/server/app/dashboard/profile.rsc +8 -8
  225. package/.next/server/app/dashboard/profile.segments/!KGRhc2hib2FyZCk/dashboard/profile/__PAGE__.segment.rsc +8 -8
  226. package/.next/server/app/dashboard/profile.segments/_full.segment.rsc +8 -8
  227. package/.next/server/app/dashboard/profile.segments/_tree.segment.rsc +2 -2
  228. package/.next/server/app/dashboard/providers/new.html +1 -1
  229. package/.next/server/app/dashboard/providers/new.rsc +8 -8
  230. package/.next/server/app/dashboard/providers/new.segments/!KGRhc2hib2FyZCk/dashboard/providers/new/__PAGE__.segment.rsc +8 -8
  231. package/.next/server/app/dashboard/providers/new.segments/_full.segment.rsc +8 -8
  232. package/.next/server/app/dashboard/providers/new.segments/_tree.segment.rsc +2 -2
  233. package/.next/server/app/dashboard/providers.html +1 -1
  234. package/.next/server/app/dashboard/providers.rsc +8 -8
  235. package/.next/server/app/dashboard/providers.segments/!KGRhc2hib2FyZCk/dashboard/providers/__PAGE__.segment.rsc +8 -8
  236. package/.next/server/app/dashboard/providers.segments/_full.segment.rsc +8 -8
  237. package/.next/server/app/dashboard/providers.segments/_tree.segment.rsc +2 -2
  238. package/.next/server/app/dashboard/proxy-pools.html +1 -1
  239. package/.next/server/app/dashboard/proxy-pools.rsc +8 -8
  240. package/.next/server/app/dashboard/proxy-pools.segments/!KGRhc2hib2FyZCk/dashboard/proxy-pools/__PAGE__.segment.rsc +8 -8
  241. package/.next/server/app/dashboard/proxy-pools.segments/_full.segment.rsc +8 -8
  242. package/.next/server/app/dashboard/proxy-pools.segments/_tree.segment.rsc +2 -2
  243. package/.next/server/app/dashboard/pxpipe.html +1 -1
  244. package/.next/server/app/dashboard/pxpipe.rsc +7 -7
  245. package/.next/server/app/dashboard/pxpipe.segments/!KGRhc2hib2FyZCk/dashboard/pxpipe/__PAGE__.segment.rsc +7 -7
  246. package/.next/server/app/dashboard/pxpipe.segments/_full.segment.rsc +7 -7
  247. package/.next/server/app/dashboard/pxpipe.segments/_tree.segment.rsc +2 -2
  248. package/.next/server/app/dashboard/quota.html +2 -2
  249. package/.next/server/app/dashboard/quota.rsc +9 -9
  250. package/.next/server/app/dashboard/quota.segments/!KGRhc2hib2FyZCk/dashboard/quota/__PAGE__.segment.rsc +9 -9
  251. package/.next/server/app/dashboard/quota.segments/_full.segment.rsc +9 -9
  252. package/.next/server/app/dashboard/quota.segments/_tree.segment.rsc +2 -2
  253. package/.next/server/app/dashboard/settings/pricing/page.js +1 -1
  254. package/.next/server/app/dashboard/settings/pricing/page_client-reference-manifest.js +1 -1
  255. package/.next/server/app/dashboard/settings/pricing.html +1 -1
  256. package/.next/server/app/dashboard/settings/pricing.rsc +7 -7
  257. package/.next/server/app/dashboard/settings/pricing.segments/_full.segment.rsc +7 -7
  258. package/.next/server/app/dashboard/settings/pricing.segments/_tree.segment.rsc +2 -2
  259. package/.next/server/app/dashboard/settings/pricing.segments/dashboard/settings/pricing/__PAGE__.segment.rsc +7 -7
  260. package/.next/server/app/dashboard/token-saver.html +1 -1
  261. package/.next/server/app/dashboard/token-saver.rsc +8 -8
  262. package/.next/server/app/dashboard/token-saver.segments/!KGRhc2hib2FyZCk/dashboard/token-saver/__PAGE__.segment.rsc +8 -8
  263. package/.next/server/app/dashboard/token-saver.segments/_full.segment.rsc +8 -8
  264. package/.next/server/app/dashboard/token-saver.segments/_tree.segment.rsc +2 -2
  265. package/.next/server/app/dashboard/usage.html +2 -2
  266. package/.next/server/app/dashboard/usage.rsc +8 -8
  267. package/.next/server/app/dashboard/usage.segments/!KGRhc2hib2FyZCk/dashboard/usage/__PAGE__.segment.rsc +8 -8
  268. package/.next/server/app/dashboard/usage.segments/_full.segment.rsc +8 -8
  269. package/.next/server/app/dashboard/usage.segments/_tree.segment.rsc +2 -2
  270. package/.next/server/app/dashboard.html +1 -1
  271. package/.next/server/app/dashboard.rsc +9 -9
  272. package/.next/server/app/dashboard.segments/!KGRhc2hib2FyZCk/dashboard/__PAGE__.segment.rsc +8 -8
  273. package/.next/server/app/dashboard.segments/_full.segment.rsc +9 -9
  274. package/.next/server/app/dashboard.segments/_tree.segment.rsc +2 -2
  275. package/.next/server/app/index.html +1 -1
  276. package/.next/server/app/index.rsc +6 -6
  277. package/.next/server/app/index.segments/__PAGE__.segment.rsc +6 -6
  278. package/.next/server/app/index.segments/_full.segment.rsc +6 -6
  279. package/.next/server/app/index.segments/_tree.segment.rsc +2 -2
  280. package/.next/server/app/landing/page.js +1 -1
  281. package/.next/server/app/landing/page_client-reference-manifest.js +1 -1
  282. package/.next/server/app/landing.html +1 -1
  283. package/.next/server/app/landing.rsc +7 -7
  284. package/.next/server/app/landing.segments/_full.segment.rsc +7 -7
  285. package/.next/server/app/landing.segments/_tree.segment.rsc +2 -2
  286. package/.next/server/app/landing.segments/landing/__PAGE__.segment.rsc +7 -7
  287. package/.next/server/app/login/page.js +1 -1
  288. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  289. package/.next/server/app/login.html +1 -1
  290. package/.next/server/app/login.rsc +6 -6
  291. package/.next/server/app/login.segments/_full.segment.rsc +6 -6
  292. package/.next/server/app/login.segments/_tree.segment.rsc +2 -2
  293. package/.next/server/app/login.segments/login/__PAGE__.segment.rsc +6 -6
  294. package/.next/server/app/manifest.webmanifest/route.js +2 -2
  295. package/.next/server/app/page.js +1 -1
  296. package/.next/server/app/page_client-reference-manifest.js +1 -1
  297. package/.next/server/app-paths-manifest.json +5 -6
  298. package/.next/server/chunks/1011.js +1 -1
  299. package/.next/server/chunks/1194.js +1 -1
  300. package/.next/server/chunks/1289.js +1 -1
  301. package/.next/server/chunks/1633.js +1 -1
  302. package/.next/server/chunks/2282.js +7 -7
  303. package/.next/server/chunks/2339.js +1 -1
  304. package/.next/server/chunks/2706.js +1 -1
  305. package/.next/server/chunks/2801.js +1 -1
  306. package/.next/server/chunks/3425.js +1 -1
  307. package/.next/server/chunks/4129.js +1 -1
  308. package/.next/server/chunks/6056.js +1 -1
  309. package/.next/server/chunks/7816.js +1 -1
  310. package/.next/server/chunks/9429.js +1 -1
  311. package/.next/server/middleware-build-manifest.js +1 -1
  312. package/.next/server/pages/404.html +1 -1
  313. package/.next/server/pages/500.html +1 -1
  314. package/.next/server/prefetch-hints.json +0 -24
  315. package/.next/server/server-reference-manifest.js +1 -1
  316. package/.next/server/server-reference-manifest.json +1 -1
  317. package/.next/static/chunks/1278-1bef1ffb32ea7f41.js +1 -0
  318. package/.next/static/chunks/1660-25564899e7912146.js +1 -0
  319. package/.next/static/chunks/{1699-13ee6864a8a31a96.js → 1699-49be310981e2759e.js} +2 -2
  320. package/.next/static/chunks/{3577-cd98d40e3099e1b0.js → 3577-1f02993330d7fba7.js} +1 -1
  321. package/.next/static/chunks/5148-7d67073becfaf162.js +1 -0
  322. package/.next/static/chunks/{5862-f98c2f3d480b80d2.js → 5862-6f3a3a6d4c00100d.js} +1 -1
  323. package/.next/static/chunks/6428-5a5f71440049a4e6.js +1 -0
  324. package/.next/static/chunks/7128-78b41798c1177008.js +1 -0
  325. package/.next/static/chunks/7835-06b436358f3395f6.js +1 -0
  326. package/.next/static/chunks/9150-32b4247b1af5e206.js +1 -0
  327. package/.next/static/chunks/{9ce8c186-3c7d7dbc5d428052.js → 9ce8c186-6cd204e9c27ecee1.js} +1 -1
  328. package/.next/static/chunks/app/(dashboard)/dashboard/cli-tools/[toolId]/{page-e8bceccb95695755.js → page-c913b130003c1672.js} +3 -3
  329. package/.next/static/chunks/app/(dashboard)/dashboard/cli-tools/{page-adee408d9de02423.js → page-3bab0c661f222ea3.js} +1 -1
  330. package/.next/static/chunks/app/(dashboard)/dashboard/combos/{page-d4c1e29e8d038f09.js → page-76cc2c386af12738.js} +1 -1
  331. package/.next/static/chunks/app/(dashboard)/dashboard/console-log/page-58a29f3cb409597a.js +1 -0
  332. package/.next/static/chunks/app/(dashboard)/dashboard/endpoint/page-d5f693c646ae044b.js +1 -0
  333. package/.next/static/chunks/app/(dashboard)/dashboard/media-providers/systemone/[id]/{page-b751d9603cb82e62.js → page-7340dad2b87640cf.js} +1 -1
  334. package/.next/static/chunks/app/(dashboard)/dashboard/media-providers/systemone/{page-49b3b4867d06cbe0.js → page-32e3e07a8f54ec11.js} +1 -1
  335. package/.next/static/chunks/app/(dashboard)/dashboard/page-a7f59da8c0db5cf1.js +1 -0
  336. package/.next/static/chunks/app/(dashboard)/dashboard/profile/{page-65b2c7989dbe2663.js → page-7123127fd841cfa5.js} +1 -1
  337. package/.next/static/chunks/app/(dashboard)/dashboard/providers/[id]/{page-d5d480e209fdc55a.js → page-fab6f4b4cccc1a6f.js} +1 -1
  338. package/.next/static/chunks/app/(dashboard)/dashboard/providers/new/{page-9b68243ab9555702.js → page-819e98f53bf2f4f7.js} +1 -1
  339. package/.next/static/chunks/app/(dashboard)/dashboard/providers/{page-2396571281d02804.js → page-9054f870a5e0f5c6.js} +1 -1
  340. package/.next/static/chunks/app/(dashboard)/dashboard/proxy-pools/{page-cfd6bcb7e55d7d18.js → page-3e4657b108582404.js} +1 -1
  341. package/.next/static/chunks/app/(dashboard)/dashboard/quota/{page-fef6edb7aafd8bd0.js → page-61780f0a2b494680.js} +1 -1
  342. package/.next/static/chunks/app/(dashboard)/dashboard/token-saver/{page-bcc37fc2b4089f2f.js → page-4e366abb76514be7.js} +1 -1
  343. package/.next/static/chunks/app/(dashboard)/dashboard/usage/{page-697f46461825d7fc.js → page-2d71fa2936b16f45.js} +1 -1
  344. package/.next/static/chunks/app/(dashboard)/{layout-5039502fc12707c9.js → layout-91f41c68685ed739.js} +1 -1
  345. package/.next/static/chunks/app/callback/page-8b6862b2e58a129e.js +1 -0
  346. package/.next/static/chunks/app/dashboard/settings/pricing/page-c1dfa7608ff710d1.js +1 -0
  347. package/.next/static/chunks/app/landing/page-a76ba2642bd02bf5.js +1 -0
  348. package/.next/static/chunks/app/layout-cc98f6c7473b86a3.js +1 -0
  349. package/.next/static/css/efaf98e3b295f727.css +1 -0
  350. package/.next/static/wBARQPScPhD6SLn7WQvI9/_buildManifest.js +1 -0
  351. package/CHANGELOG.md +19 -0
  352. package/open-sse/providers/registry/opencode-go.js +4 -0
  353. package/open-sse/providers/registry/opencode-zen.js +4 -0
  354. package/open-sse/providers/registry/opencode.js +3 -0
  355. package/open-sse/translator/concerns/thinkingUnified.js +16 -1
  356. package/open-sse/translator/concerns/toolCall.js +2 -1
  357. package/open-sse/translator/formats/openai.js +10 -2
  358. package/open-sse/translator/request/claude-to-openai.js +9 -5
  359. package/open-sse/translator/response/openai-to-claude.js +66 -32
  360. package/open-sse/translator/schema/defaults.js +6 -0
  361. package/open-sse/translator/schema/index.js +1 -1
  362. package/package.json +1 -1
  363. package/src/app/(dashboard)/dashboard/cli-tools/components/cards/ClaudeToolCard.tsx +10 -0
  364. package/src/app/api/cli-tools/claude-settings/route.js +10 -0
  365. package/src/app/globals.css +21 -1
  366. package/src/shared/components/layouts/DashboardLayout.tsx +2 -8
  367. package/src/shared/constants/cliTools.js +5 -0
  368. package/.next/server/app/(dashboard)/dashboard/basic-chat/page.js +0 -1
  369. package/.next/server/app/(dashboard)/dashboard/basic-chat/page.js.nft.json +0 -1
  370. package/.next/server/app/(dashboard)/dashboard/basic-chat/page_client-reference-manifest.js +0 -1
  371. package/.next/server/app/dashboard/basic-chat.html +0 -1
  372. package/.next/server/app/dashboard/basic-chat.meta +0 -36
  373. package/.next/server/app/dashboard/basic-chat.rsc +0 -26
  374. package/.next/server/app/dashboard/basic-chat.segments/!KGRhc2hib2FyZCk/dashboard/basic-chat/__PAGE__.segment.rsc +0 -31
  375. package/.next/server/app/dashboard/basic-chat.segments/_full.segment.rsc +0 -26
  376. package/.next/server/app/dashboard/basic-chat.segments/_tree.segment.rsc +0 -6
  377. package/.next/static/Xxof6vr5MvX0-6oReU23d/_buildManifest.js +0 -1
  378. package/.next/static/chunks/1278-f11c7d84c52342fc.js +0 -1
  379. package/.next/static/chunks/1471-10f225f62f7469cc.js +0 -1
  380. package/.next/static/chunks/159-ec207a220d7d5c16.js +0 -1
  381. package/.next/static/chunks/5113-5c4f5181388aab72.js +0 -1
  382. package/.next/static/chunks/5778-9d97ce04f2ac118c.js +0 -1
  383. package/.next/static/chunks/6920-e2b533c1eafa9521.js +0 -1
  384. package/.next/static/chunks/7835-c1ef8f5cdaaad483.js +0 -1
  385. package/.next/static/chunks/app/(dashboard)/dashboard/basic-chat/page-ce1364b2b9589785.js +0 -1
  386. package/.next/static/chunks/app/(dashboard)/dashboard/console-log/page-3cc58622294c8c8a.js +0 -1
  387. package/.next/static/chunks/app/(dashboard)/dashboard/endpoint/page-67516b120adbc4d7.js +0 -1
  388. package/.next/static/chunks/app/(dashboard)/dashboard/page-315c9f52c472a700.js +0 -1
  389. package/.next/static/chunks/app/callback/page-f714c2306a1e9004.js +0 -1
  390. package/.next/static/chunks/app/dashboard/settings/pricing/page-86197107690a0580.js +0 -1
  391. package/.next/static/chunks/app/landing/page-7a83d1be493ddc8c.js +0 -1
  392. package/.next/static/chunks/app/layout-dc929483cad9f77f.js +0 -1
  393. package/.next/static/css/c66cbd684164cacf.css +0 -1
  394. package/src/app/(dashboard)/dashboard/basic-chat/BasicChatPageClient.tsx +0 -1140
  395. package/src/app/(dashboard)/dashboard/basic-chat/page.tsx +0 -5
  396. /package/.next/static/{Xxof6vr5MvX0-6oReU23d → wBARQPScPhD6SLn7WQvI9}/_ssgManifest.js +0 -0
@@ -1,1140 +0,0 @@
1
- "use client";
2
- import Icon from "@/shared/components/Icon";
3
-
4
- import { useEffect, useMemo, useRef, useState } from "react";
5
- import { Badge } from "@/components/ui/badge";
6
- import { Button } from "@/components/ui/button";
7
- import { getModelsByProviderId } from "@/shared/constants/models";
8
- import {
9
- isAnthropicCompatibleProvider,
10
- isOpenAICompatibleProvider,
11
- } from "@/shared/constants/providers";
12
-
13
- const STORAGE_KEYS = {
14
- sessions: "basic-chat.sessions",
15
- activeSessionId: "basic-chat.activeSessionId",
16
- activeProviderId: "basic-chat.activeProviderId",
17
- draft: "basic-chat.draft",
18
- };
19
-
20
- function createId() {
21
- if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID();
22
- return `chat_${Date.now()}_${Math.random().toString(16).slice(2)}`;
23
- }
24
-
25
- function safeParse(value, fallback) {
26
- try {
27
- return JSON.parse(value);
28
- } catch {
29
- return fallback;
30
- }
31
- }
32
-
33
- function textValue(value) {
34
- if (typeof value === "string") return value;
35
- if (value == null) return "";
36
- if (Array.isArray(value)) return value.map(textValue).filter(Boolean).join(" ");
37
- if (typeof value === "object") {
38
- if (typeof value.message === "string") return value.message;
39
- if (typeof value.error === "string") return value.error;
40
- try {
41
- return JSON.stringify(value);
42
- } catch {
43
- return String(value);
44
- }
45
- }
46
- return String(value);
47
- }
48
-
49
- function humanize(value = "") {
50
- return (
51
- String(value)
52
- .replace(/[-_]/g, " ")
53
- .replace(/\b\w/g, (char) => char.toUpperCase())
54
- .trim() || "Unknown"
55
- );
56
- }
57
-
58
- function formatRelativeTime(value) {
59
- if (!value) return "Now";
60
- const time = new Date(value).getTime();
61
- if (Number.isNaN(time)) return "Now";
62
- const diffMinutes = Math.max(1, Math.round((Date.now() - time) / 60000));
63
- if (diffMinutes < 60) return `${diffMinutes}m`;
64
- const diffHours = Math.round(diffMinutes / 60);
65
- if (diffHours < 24) return `${diffHours}h`;
66
- return `${Math.round(diffHours / 24)}d`;
67
- }
68
-
69
- function makeSessionTitle(text = "") {
70
- const normalized = textValue(text).replace(/\s+/g, " ").trim();
71
- if (!normalized) return "New chat";
72
- return normalized.length > 52 ? `${normalized.slice(0, 52).trimEnd()}…` : normalized;
73
- }
74
-
75
- function buildUserContent(message) {
76
- const text = textValue(message.content).trim();
77
- const attachments = Array.isArray(message.attachments) ? message.attachments : [];
78
-
79
- if (attachments.length === 0) return text;
80
-
81
- const content = [];
82
- if (text) content.push({ type: "text", text });
83
-
84
- for (const attachment of attachments) {
85
- if (attachment?.dataUrl) {
86
- content.push({ type: "image_url", image_url: { url: attachment.dataUrl } });
87
- }
88
- }
89
-
90
- return content.length > 0 ? content : text;
91
- }
92
-
93
- function readAssistantText(chunk) {
94
- if (!chunk || typeof chunk !== "object") return "";
95
- const choice = chunk.choices?.[0];
96
- const delta = choice?.delta || {};
97
- const pieces = [delta.content, choice?.message?.content, chunk.output_text, chunk.text]
98
- .map(textValue)
99
- .filter(Boolean);
100
- return pieces[0] || "";
101
- }
102
-
103
- async function fileToDataUrl(file) {
104
- return await new Promise((resolve, reject) => {
105
- const reader = new FileReader();
106
- reader.onload = () => resolve(String(reader.result || ""));
107
- reader.onerror = () => reject(reader.error || new Error("Failed to read file"));
108
- reader.readAsDataURL(file);
109
- });
110
- }
111
-
112
- function cloneSession(session) {
113
- return {
114
- ...session,
115
- messages: Array.isArray(session.messages)
116
- ? session.messages.map((message) => ({ ...message }))
117
- : [],
118
- };
119
- }
120
-
121
- function getProviderLabel(connection) {
122
- return connection?.name || humanize(connection?.provider || connection?.id || "provider");
123
- }
124
-
125
- function normalizeStaticModel(model, connection) {
126
- if (!model?.id) return null;
127
- return {
128
- id: `${connection.provider}/${model.id}`,
129
- requestModel: `${connection.provider}/${model.id}`,
130
- name: model.name || model.id,
131
- providerId: connection.provider,
132
- providerName: getProviderLabel(connection),
133
- source: "static",
134
- };
135
- }
136
-
137
- function normalizeLiveModel(model, connection) {
138
- const rawId = typeof model === "string" ? model : model?.id || model?.name || model?.model || "";
139
- if (!rawId) return null;
140
-
141
- const displayName =
142
- typeof model === "string" ? model : model?.name || model?.displayName || rawId;
143
-
144
- let requestModel = rawId;
145
- const isCompatible =
146
- isOpenAICompatibleProvider(connection.provider) ||
147
- isAnthropicCompatibleProvider(connection.provider);
148
- if (isCompatible && !rawId.includes("/")) {
149
- requestModel = `${connection.provider}/${rawId}`;
150
- }
151
-
152
- return {
153
- id: requestModel,
154
- requestModel,
155
- name: displayName,
156
- providerId: connection.provider,
157
- providerName: getProviderLabel(connection),
158
- source: "live",
159
- };
160
- }
161
-
162
- function parseProviderModelsPayload(data) {
163
- if (Array.isArray(data?.models)) return data.models;
164
- if (Array.isArray(data?.data)) return data.data;
165
- if (Array.isArray(data?.results)) return data.results;
166
- if (Array.isArray(data)) return data;
167
- return [];
168
- }
169
-
170
- function dedupeModels(models) {
171
- const map = new Map();
172
- for (const model of models) {
173
- if (!model?.id) continue;
174
- if (!map.has(model.id)) map.set(model.id, model);
175
- }
176
- return Array.from(map.values());
177
- }
178
-
179
- export default function BasicChatPageClient() {
180
- const [providerGroups, setProviderGroups] = useState<any[]>([]);
181
- const [loadingData, setLoadingData] = useState<boolean>(true);
182
- const [loadError, setLoadError] = useState<string>("");
183
- const [sessions, setSessions] = useState(() => {
184
- if (typeof window === "undefined") return [];
185
- try {
186
- const saved = safeParse(globalThis.localStorage.getItem(STORAGE_KEYS.sessions), []);
187
- return Array.isArray(saved)
188
- ? saved.map((session) => ({
189
- ...session,
190
- messages: Array.isArray(session.messages) ? session.messages : [],
191
- }))
192
- : [];
193
- } catch {
194
- return [];
195
- }
196
- });
197
- const [activeSessionId, setActiveSessionId] = useState(() => {
198
- if (typeof window === "undefined") return "";
199
- return globalThis.localStorage.getItem(STORAGE_KEYS.activeSessionId) || "";
200
- });
201
- const [activeProviderId, setActiveProviderId] = useState(() => {
202
- if (typeof window === "undefined") return "";
203
- return globalThis.localStorage.getItem(STORAGE_KEYS.activeProviderId) || "";
204
- });
205
- const [activeModelId, setActiveModelId] = useState<string>("");
206
- const [draft, setDraft] = useState(() => {
207
- if (typeof window === "undefined") return "";
208
- return globalThis.localStorage.getItem(STORAGE_KEYS.draft) || "";
209
- });
210
- const [attachments, setAttachments] = useState<any[]>([]);
211
- const [isSending, setIsSending] = useState<boolean>(false);
212
- const [streamingMessageId, setStreamingMessageId] = useState<string>("");
213
- const [streamingText, setStreamingText] = useState<string>("");
214
- const [isHydrated, setIsHydrated] = useState<boolean>(false);
215
- const [modelMenuOpen, setModelMenuOpen] = useState<boolean>(false);
216
- const [historyOpen, setHistoryOpen] = useState<boolean>(false);
217
- const fileInputRef = useRef<any>(null);
218
- const abortRef = useRef<any>(null);
219
- const initializedRef = useRef<boolean>(false);
220
- const modelMenuRef = useRef<any>(null);
221
- const historyMenuRef = useRef<any>(null);
222
-
223
- useEffect(() => {
224
- setIsHydrated(true);
225
- }, []);
226
-
227
- useEffect(() => {
228
- let cancelled = false;
229
-
230
- async function loadData() {
231
- setLoadingData(true);
232
- setLoadError("");
233
-
234
- try {
235
- const providersRes = await fetch("/api/providers", { cache: "no-store" });
236
- const providersData = await providersRes.json().catch(() => ({}));
237
- const connections = Array.isArray(providersData.connections)
238
- ? providersData.connections.filter((connection) => connection?.isActive !== false)
239
- : [];
240
-
241
- if (connections.length === 0) {
242
- if (!cancelled) {
243
- setProviderGroups([]);
244
- setLoadError("No providers connected yet.");
245
- }
246
- return;
247
- }
248
-
249
- const providerMap = new Map();
250
-
251
- for (const connection of connections) {
252
- const providerId = connection.provider || connection.id;
253
- const providerName = getProviderLabel(connection);
254
- const providerType = isOpenAICompatibleProvider(providerId)
255
- ? "openai-compatible"
256
- : isAnthropicCompatibleProvider(providerId)
257
- ? "anthropic-compatible"
258
- : providerId;
259
-
260
- if (!providerMap.has(providerId)) {
261
- providerMap.set(providerId, {
262
- providerId,
263
- providerName,
264
- providerType,
265
- connections: [],
266
- models: [],
267
- });
268
- }
269
-
270
- const group = providerMap.get(providerId);
271
- group.providerName = group.providerName || providerName;
272
- group.providerType = group.providerType || providerType;
273
- group.connections.push(connection);
274
-
275
- const staticModels = getModelsByProviderId(providerId)
276
- .map((model) => normalizeStaticModel(model, connection))
277
- .filter(Boolean);
278
- group.models.push(...staticModels);
279
- }
280
-
281
- const liveResults = await Promise.all(
282
- connections.map(async (connection) => {
283
- try {
284
- const response = await fetch(`/api/providers/${connection.id}/models`, {
285
- cache: "no-store",
286
- });
287
- const data = await response.json().catch(() => ({}));
288
- if (!response.ok) return { connection, models: [] };
289
- const models = parseProviderModelsPayload(data)
290
- .map((model) => normalizeLiveModel(model, connection))
291
- .filter(Boolean);
292
- return { connection, models };
293
- } catch {
294
- return { connection, models: [] };
295
- }
296
- }),
297
- );
298
-
299
- for (const result of liveResults) {
300
- const providerId = result.connection.provider || result.connection.id;
301
- const group = providerMap.get(providerId);
302
- if (!group) continue;
303
- group.models.push(...result.models);
304
- }
305
-
306
- const normalized = Array.from(providerMap.values())
307
- .map((group) => ({
308
- ...group,
309
- models: dedupeModels(group.models).sort((a, b) => a.name.localeCompare(b.name)),
310
- }))
311
- .filter((group) => group.models.length > 0)
312
- .sort((a, b) => a.providerName.localeCompare(b.providerName));
313
-
314
- if (!cancelled) {
315
- setProviderGroups(normalized);
316
- if (normalized.length === 0) {
317
- setLoadError("Providers connected but no models available.");
318
- }
319
- }
320
- } catch (error) {
321
- if (!cancelled) {
322
- setLoadError(textValue(error?.message) || "Failed to load providers/models.");
323
- setProviderGroups([]);
324
- }
325
- } finally {
326
- if (!cancelled) setLoadingData(false);
327
- }
328
- }
329
-
330
- loadData();
331
- return () => {
332
- cancelled = true;
333
- };
334
- }, []);
335
-
336
- useEffect(() => {
337
- const handleClickOutside = (event: any) => {
338
- if (modelMenuRef.current && !modelMenuRef.current.contains(event.target)) {
339
- setModelMenuOpen(false);
340
- }
341
- if (historyMenuRef.current && !historyMenuRef.current.contains(event.target)) {
342
- setHistoryOpen(false);
343
- }
344
- };
345
-
346
- document.addEventListener("mousedown", handleClickOutside);
347
- return () => document.removeEventListener("mousedown", handleClickOutside);
348
- }, []);
349
-
350
- const modelIndex = useMemo(() => {
351
- const map = new Map();
352
- for (const group of providerGroups) {
353
- for (const model of group.models) {
354
- map.set(model.id, {
355
- ...model,
356
- providerId: group.providerId,
357
- providerName: group.providerName,
358
- });
359
- }
360
- }
361
- return map;
362
- }, [providerGroups]);
363
-
364
- const activeProviderGroup = useMemo(() => {
365
- return (
366
- providerGroups.find((group) => group.providerId === activeProviderId) ||
367
- providerGroups[0] ||
368
- null
369
- );
370
- }, [providerGroups, activeProviderId]);
371
-
372
- const activeModel = useMemo(() => {
373
- if (activeModelId && modelIndex.has(activeModelId)) return modelIndex.get(activeModelId);
374
- if (activeSessionId) {
375
- const session = sessions.find((item) => item.id === activeSessionId);
376
- if (session?.modelId && modelIndex.has(session.modelId))
377
- return modelIndex.get(session.modelId);
378
- }
379
- return activeProviderGroup?.models?.[0] || null;
380
- }, [activeModelId, modelIndex, activeProviderGroup, sessions, activeSessionId]);
381
-
382
- const currentSession = useMemo(
383
- () => sessions.find((session) => session.id === activeSessionId) || null,
384
- [sessions, activeSessionId],
385
- );
386
- const currentMessages = currentSession?.messages || [];
387
- const sessionItems = useMemo(
388
- () =>
389
- [...sessions].sort(
390
- (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime(),
391
- ),
392
- [sessions],
393
- );
394
- const canSend =
395
- !isSending && !!activeModel && (draft.trim().length > 0 || attachments.length > 0);
396
-
397
- useEffect(() => {
398
- if (!isHydrated) return;
399
- try {
400
- globalThis.localStorage.setItem(STORAGE_KEYS.sessions, JSON.stringify(sessions));
401
- globalThis.localStorage.setItem(STORAGE_KEYS.activeSessionId, activeSessionId);
402
- globalThis.localStorage.setItem(STORAGE_KEYS.activeProviderId, activeProviderId);
403
- globalThis.localStorage.setItem(STORAGE_KEYS.draft, draft);
404
- } catch {
405
- // Ignore storage errors.
406
- }
407
- }, [isHydrated, sessions, activeSessionId, activeProviderId, draft]);
408
-
409
- useEffect(() => {
410
- if (!isHydrated || loadingData || initializedRef.current) return;
411
- if (providerGroups.length === 0) return;
412
-
413
- const savedProvider =
414
- providerGroups.find((group) => group.providerId === activeProviderId) || providerGroups[0];
415
- const savedModel =
416
- activeModelId && modelIndex.has(activeModelId)
417
- ? modelIndex.get(activeModelId)
418
- : savedProvider.models[0];
419
-
420
- if (sessions.length > 0) {
421
- const session = sessions.find((item) => item.id === activeSessionId) || sessions[0];
422
- const sessionModel =
423
- session?.modelId && modelIndex.has(session.modelId)
424
- ? modelIndex.get(session.modelId)
425
- : savedModel;
426
- initializedRef.current = true;
427
- setActiveSessionId(session.id);
428
- setActiveProviderId(sessionModel?.providerId || savedProvider.providerId);
429
- setActiveModelId(sessionModel?.id || savedModel.id);
430
- return;
431
- }
432
-
433
- const session = {
434
- id: createId(),
435
- title: "New chat",
436
- providerId: savedProvider.providerId,
437
- providerName: savedProvider.providerName,
438
- modelId: savedModel.id,
439
- modelName: savedModel.name,
440
- createdAt: new Date().toISOString(),
441
- updatedAt: new Date().toISOString(),
442
- messages: [],
443
- };
444
-
445
- initializedRef.current = true;
446
- setSessions([session]);
447
- setActiveSessionId(session.id);
448
- setActiveProviderId(savedProvider.providerId);
449
- setActiveModelId(savedModel.id);
450
- }, [
451
- isHydrated,
452
- loadingData,
453
- providerGroups,
454
- modelIndex,
455
- sessions,
456
- activeSessionId,
457
- activeProviderId,
458
- activeModelId,
459
- ]);
460
-
461
- const updateSession = (sessionId, updater) => {
462
- setSessions((prev) =>
463
- prev.map((session) => (session.id === sessionId ? updater(cloneSession(session)) : session)),
464
- );
465
- };
466
-
467
- const ensureSessionForModel = (model) => {
468
- if (!model) return null;
469
- return {
470
- id: createId(),
471
- title: "New chat",
472
- providerId: model.providerId,
473
- providerName: model.providerName,
474
- modelId: model.id,
475
- modelName: model.name,
476
- createdAt: new Date().toISOString(),
477
- updatedAt: new Date().toISOString(),
478
- messages: [],
479
- };
480
- };
481
-
482
- const handleNewChat = () => {
483
- if (!activeModel) return;
484
- const session = ensureSessionForModel(activeModel);
485
- if (!session) return;
486
- setSessions((prev) => [session, ...prev]);
487
- setActiveSessionId(session.id);
488
- setActiveProviderId(session.providerId);
489
- setActiveModelId(session.modelId);
490
- setDraft("");
491
- setAttachments([]);
492
- setStreamingMessageId("");
493
- setStreamingText("");
494
- };
495
-
496
- const handleSelectSession = (sessionId) => {
497
- const session = sessions.find((item) => item.id === sessionId);
498
- if (!session) return;
499
- setActiveSessionId(sessionId);
500
- setActiveProviderId(session.providerId || activeProviderId);
501
- setActiveModelId(session.modelId || activeModelId);
502
- setHistoryOpen(false);
503
- };
504
-
505
- const handleDeleteCurrentChat = () => {
506
- if (!activeSessionId) return;
507
- const nextSessions = sessions.filter((session) => session.id !== activeSessionId);
508
- const fallback = nextSessions[0] || null;
509
- setSessions(nextSessions);
510
- if (fallback) {
511
- setActiveSessionId(fallback.id);
512
- setActiveProviderId(fallback.providerId);
513
- setActiveModelId(fallback.modelId);
514
- } else {
515
- setActiveSessionId("");
516
- setActiveProviderId("");
517
- setActiveModelId("");
518
- }
519
- };
520
-
521
- const handleSelectProvider = (providerId) => {
522
- const group = providerGroups.find((item) => item.providerId === providerId);
523
- if (!group || group.models.length === 0) return;
524
- const nextModel = group.models[0];
525
-
526
- const current = sessions.find((session) => session.id === activeSessionId);
527
- if (current && current.messages.length > 0) {
528
- const session = ensureSessionForModel(nextModel);
529
- if (!session) return;
530
- setSessions((prev) => [session, ...prev]);
531
- setActiveSessionId(session.id);
532
- } else if (current) {
533
- setSessions((prev) =>
534
- prev.map((item) =>
535
- item.id === current.id
536
- ? {
537
- ...item,
538
- providerId: group.providerId,
539
- providerName: group.providerName,
540
- modelId: nextModel.id,
541
- modelName: nextModel.name,
542
- }
543
- : item,
544
- ),
545
- );
546
- setActiveSessionId(current.id);
547
- }
548
-
549
- setActiveProviderId(group.providerId);
550
- setActiveModelId(nextModel.id);
551
- setModelMenuOpen(false);
552
- };
553
-
554
- const handleSelectModel = (modelId) => {
555
- const model = modelIndex.get(modelId);
556
- if (!model) return;
557
-
558
- const current = sessions.find((session) => session.id === activeSessionId);
559
- if (current && current.messages.length > 0) {
560
- const session = ensureSessionForModel(model);
561
- if (!session) return;
562
- setSessions((prev) => [session, ...prev]);
563
- setActiveSessionId(session.id);
564
- } else if (current) {
565
- setSessions((prev) =>
566
- prev.map((item) =>
567
- item.id === current.id
568
- ? {
569
- ...item,
570
- providerId: model.providerId,
571
- providerName: model.providerName,
572
- modelId: model.id,
573
- modelName: model.name,
574
- }
575
- : item,
576
- ),
577
- );
578
- setActiveSessionId(current.id);
579
- } else {
580
- const session = ensureSessionForModel(model);
581
- if (!session) return;
582
- setSessions((prev) => [session, ...prev]);
583
- setActiveSessionId(session.id);
584
- }
585
-
586
- setActiveProviderId(model.providerId);
587
- setActiveModelId(model.id);
588
- setModelMenuOpen(false);
589
- };
590
-
591
- const handleAttachFiles = async (event: any) => {
592
- const files: any[] = Array.from(event.target.files || []);
593
- if (files.length === 0) return;
594
-
595
- const images = files.filter((file) => file.type.startsWith("image/"));
596
- if (images.length === 0) {
597
- event.target.value = "";
598
- return;
599
- }
600
-
601
- const converted = await Promise.all(
602
- images.map(async (file) => ({
603
- id: createId(),
604
- name: file.name,
605
- type: file.type,
606
- size: file.size,
607
- dataUrl: await fileToDataUrl(file),
608
- })),
609
- );
610
-
611
- setAttachments((prev) => [...prev, ...converted]);
612
- event.target.value = "";
613
- };
614
-
615
- const removeAttachment = (attachmentId) => {
616
- setAttachments((prev) => prev.filter((attachment) => attachment.id !== attachmentId));
617
- };
618
-
619
- const handleStop = () => {
620
- abortRef.current?.abort();
621
- };
622
-
623
- const finalizeSessionTitle = (sessionId, titleSeed) => {
624
- const title = makeSessionTitle(titleSeed);
625
- updateSession(sessionId, (session) => ({
626
- ...session,
627
- title: session.title === "New chat" ? title : session.title,
628
- updatedAt: new Date().toISOString(),
629
- }));
630
- };
631
-
632
- const sendMessage = async () => {
633
- const model = activeModel || activeProviderGroup?.models?.[0] || null;
634
- if (!model) return;
635
-
636
- const userText = draft.trim();
637
- if (!userText && attachments.length === 0) return;
638
-
639
- let sessionId = activeSessionId;
640
- let session = sessions.find((item) => item.id === sessionId);
641
- if (!session) {
642
- session = ensureSessionForModel(model);
643
- if (!session) return;
644
- sessionId = session.id;
645
- setSessions((prev) => [session, ...prev]);
646
- setActiveSessionId(sessionId);
647
- }
648
-
649
- const userMessage = {
650
- id: createId(),
651
- role: "user",
652
- content: userText,
653
- attachments: attachments.map((attachment) => ({
654
- id: attachment.id,
655
- name: attachment.name,
656
- type: attachment.type,
657
- dataUrl: attachment.dataUrl,
658
- })),
659
- createdAt: new Date().toISOString(),
660
- };
661
-
662
- const assistantMessageId = createId();
663
- const assistantMessage = {
664
- id: assistantMessageId,
665
- role: "assistant",
666
- content: "",
667
- createdAt: new Date().toISOString(),
668
- status: "streaming",
669
- };
670
-
671
- const nextMessages = [...(session.messages || []), userMessage, assistantMessage];
672
- setSessions((prev) =>
673
- prev.map((item) =>
674
- item.id === sessionId
675
- ? {
676
- ...item,
677
- providerId: model.providerId,
678
- providerName: model.providerName,
679
- modelId: model.id,
680
- modelName: model.name,
681
- messages: nextMessages,
682
- updatedAt: new Date().toISOString(),
683
- title: item.title === "New chat" ? makeSessionTitle(userText) : item.title,
684
- }
685
- : item,
686
- ),
687
- );
688
- setDraft("");
689
- setAttachments([]);
690
- setIsSending(true);
691
- setStreamingMessageId(assistantMessageId);
692
- setStreamingText("");
693
- abortRef.current?.abort();
694
- abortRef.current = new AbortController();
695
-
696
- const requestMessages = nextMessages
697
- .filter((message) => !(message.role === "assistant" && message.id === assistantMessageId))
698
- .map((message) => ({
699
- role: message.role,
700
- content: message.role === "user" ? buildUserContent(message) : message.content,
701
- }));
702
-
703
- try {
704
- const response = await fetch("/api/dashboard/chat/completions", {
705
- method: "POST",
706
- headers: {
707
- "Content-Type": "application/json",
708
- Accept: "text/event-stream",
709
- },
710
- body: JSON.stringify({
711
- model: model.requestModel || model.id,
712
- messages: requestMessages,
713
- stream: true,
714
- }),
715
- signal: abortRef.current.signal,
716
- });
717
-
718
- if (!response.ok) {
719
- const errorData = await response.json().catch(() => ({}));
720
- throw new Error(
721
- textValue(errorData.error || errorData.message || `Request failed (${response.status})`),
722
- );
723
- }
724
-
725
- const reader = response.body?.getReader();
726
- if (!reader) {
727
- const data = await response.json().catch(() => ({}));
728
- const fallbackText = textValue(
729
- data?.choices?.[0]?.message?.content ||
730
- data?.output_text ||
731
- data?.error ||
732
- data?.message ||
733
- "",
734
- );
735
- updateSession(sessionId, (currentSession) => ({
736
- ...currentSession,
737
- messages: currentSession.messages.map((message) =>
738
- message.id === assistantMessageId
739
- ? { ...message, content: fallbackText, status: "done" }
740
- : message,
741
- ),
742
- updatedAt: new Date().toISOString(),
743
- }));
744
- return;
745
- }
746
-
747
- const decoder = new TextDecoder();
748
- let buffer = "";
749
- let assistantText = "";
750
-
751
- while (true) {
752
- const { value, done } = await reader.read();
753
- if (done) break;
754
-
755
- buffer += decoder.decode(value, { stream: true });
756
- const lines = buffer.split(/\r?\n/);
757
- buffer = lines.pop() || "";
758
-
759
- for (const line of lines) {
760
- const trimmed = line.trim();
761
- if (!trimmed.startsWith("data:")) continue;
762
-
763
- const payload = trimmed.slice(5).trim();
764
- if (!payload || payload === "[DONE]") continue;
765
-
766
- try {
767
- const chunk = JSON.parse(payload);
768
- const text = readAssistantText(chunk);
769
- if (!text) continue;
770
-
771
- assistantText += text;
772
- setStreamingText(assistantText);
773
- updateSession(sessionId, (currentSession) => ({
774
- ...currentSession,
775
- messages: currentSession.messages.map((message) =>
776
- message.id === assistantMessageId
777
- ? { ...message, content: assistantText, status: "streaming" }
778
- : message,
779
- ),
780
- updatedAt: new Date().toISOString(),
781
- }));
782
- } catch {
783
- // Ignore malformed chunks.
784
- }
785
- }
786
- }
787
-
788
- updateSession(sessionId, (currentSession) => ({
789
- ...currentSession,
790
- messages: currentSession.messages.map((message) =>
791
- message.id === assistantMessageId
792
- ? { ...message, content: assistantText || message.content, status: "done" }
793
- : message,
794
- ),
795
- updatedAt: new Date().toISOString(),
796
- }));
797
- finalizeSessionTitle(sessionId, userText);
798
- } catch (error) {
799
- if (error.name !== "AbortError") {
800
- const errorText = textValue(error?.message || error);
801
- updateSession(sessionId, (currentSession) => ({
802
- ...currentSession,
803
- messages: currentSession.messages.map((message) =>
804
- message.id === assistantMessageId
805
- ? { ...message, content: message.content || `Error: ${errorText}`, status: "error" }
806
- : message,
807
- ),
808
- updatedAt: new Date().toISOString(),
809
- }));
810
- setLoadError(errorText || "Failed to send message.");
811
- }
812
- } finally {
813
- setIsSending(false);
814
- setStreamingMessageId("");
815
- setStreamingText("");
816
- abortRef.current = null;
817
- }
818
- };
819
-
820
- const handleKeyDown = (event: any) => {
821
- if (event.key === "Enter" && !event.shiftKey) {
822
- event.preventDefault();
823
- if (canSend) sendMessage();
824
- }
825
- };
826
-
827
- const modelLabel = activeModel ? `${activeModel.name}` : "Select model";
828
- const modelSubLabel = activeModel ? activeModel.requestModel : "Choose from connected providers";
829
-
830
- return (
831
- <div className="relative flex-1 flex flex-col h-full min-h-0 min-w-0 bg-[#212121] text-white overflow-hidden">
832
- <div className="relative mx-auto flex flex-1 h-full min-h-0 w-full max-w-4xl flex-col">
833
- <div className="flex shrink-0 items-center justify-between gap-3 px-4 py-3 lg:px-6">
834
- <div ref={modelMenuRef} className="relative">
835
- <button
836
- type="button"
837
- onClick={() => setModelMenuOpen((value) => !value)}
838
- className="flex items-center gap-3 rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-left transition hover:bg-white/8"
839
- >
840
- <div className="min-w-0">
841
- <div className="flex items-center gap-2">
842
- <span className="text-sm font-semibold text-white">{modelLabel}</span>
843
- <Icon name="expand_more" className="text-[18px] text-white/70" />
844
- </div>
845
- <p className="truncate text-xs text-white/55">{modelSubLabel}</p>
846
- </div>
847
- </button>
848
-
849
- {modelMenuOpen ? (
850
- <div className="absolute left-0 top-[calc(100%+10px)] z-30 w-[min(520px,calc(100vw-2rem))] overflow-hidden rounded-[20px] border border-white/10 bg-[#262626] shadow-2xl shadow-black/50">
851
- <div className="border-b border-white/10 px-4 py-3">
852
- <p className="text-xs uppercase tracking-[0.22em] text-white/45">Models</p>
853
- <p className="text-sm text-white/75">Only from connected providers</p>
854
- </div>
855
- <div className="max-h-[60vh] overflow-y-auto p-2">
856
- {providerGroups.map((group) => (
857
- <div
858
- key={group.providerId}
859
- className="mb-2 rounded-[16px] border border-white/10 bg-black/20 p-2"
860
- >
861
- <div className="flex items-center justify-between px-2 py-2">
862
- <p className="text-sm font-semibold text-white">{group.providerName}</p>
863
- <Badge>{group.models.length}</Badge>
864
- </div>
865
- <div className="grid gap-2 sm:grid-cols-2">
866
- {group.models.map((model) => {
867
- const isActive = model.id === activeModelId;
868
- return (
869
- <button
870
- key={model.id}
871
- type="button"
872
- onClick={() => handleSelectModel(model.id)}
873
- className={`rounded-[14px] border px-3 py-3 text-left transition ${isActive ? "border-blue-400/40 bg-blue-500/15" : "border-white/10 bg-white/5 hover:bg-white/8"}`}
874
- >
875
- <div className="flex items-start justify-between gap-3">
876
- <div className="min-w-0">
877
- <p className="truncate text-sm font-medium text-white">
878
- {model.name}
879
- </p>
880
- <p className="truncate text-[11px] text-white/45">
881
- {model.requestModel}
882
- </p>
883
- </div>
884
- {isActive ? (
885
- <Icon name="check_circle" className="text-[18px] text-blue-300" />
886
- ) : null}
887
- </div>
888
- </button>
889
- );
890
- })}
891
- </div>
892
- </div>
893
- ))}
894
- </div>
895
- </div>
896
- ) : null}
897
- </div>
898
-
899
- <div className="flex items-center gap-2">
900
- <button
901
- type="button"
902
- onClick={() => setHistoryOpen((value) => !value)}
903
- className="rounded-2xl border border-white/10 bg-white/5 px-4 py-3 text-sm text-white/80 transition hover:bg-white/8"
904
- >
905
- History
906
- </button>
907
- <Button
908
- variant="ghost"
909
- size="sm"
910
- onClick={handleDeleteCurrentChat}
911
- disabled={!activeSessionId || sessions.length === 0}
912
- >
913
- <Icon name="delete" />
914
- Clear
915
- </Button>
916
- </div>
917
- </div>
918
-
919
- {historyOpen ? (
920
- <div
921
- ref={historyMenuRef}
922
- className="absolute right-4 top-[72px] z-20 w-[min(360px,calc(100vw-2rem))] rounded-[20px] border border-white/10 bg-[#262626] p-2 shadow-2xl shadow-black/50 lg:right-6"
923
- >
924
- <div className="px-3 py-2">
925
- <p className="text-xs uppercase tracking-[0.22em] text-white/45">Recent chats</p>
926
- </div>
927
- <div className="max-h-[48vh] space-y-2 overflow-y-auto p-1">
928
- {sessionItems.length === 0 ? (
929
- <div className="rounded-[16px] border border-dashed border-white/10 bg-white/5 p-4 text-sm text-white/55">
930
- No conversations yet.
931
- </div>
932
- ) : (
933
- sessionItems.map((session) => {
934
- const isActive = session.id === activeSessionId;
935
- const latestMessage =
936
- [...(session.messages || [])]
937
- .reverse()
938
- .find((message) => message.role === "user") || session.messages?.[0];
939
- return (
940
- <button
941
- key={session.id}
942
- type="button"
943
- onClick={() => handleSelectSession(session.id)}
944
- className={`w-full rounded-[16px] border px-3 py-3 text-left transition ${isActive ? "border-blue-400/40 bg-blue-500/15" : "border-white/10 bg-white/5 hover:bg-white/8"}`}
945
- >
946
- <div className="flex items-start justify-between gap-3">
947
- <div className="min-w-0 flex-1">
948
- <p className="truncate text-sm font-medium text-white">{session.title}</p>
949
- <p className="mt-1 truncate text-xs text-white/50">
950
- {textValue(latestMessage?.content) || "Empty chat"}
951
- </p>
952
- </div>
953
- <span className="text-[10px] text-white/40 shrink-0">
954
- {formatRelativeTime(session.updatedAt)}
955
- </span>
956
- </div>
957
- </button>
958
- );
959
- })
960
- )}
961
- </div>
962
- </div>
963
- ) : null}
964
-
965
- {loadError ? (
966
- <div className="mt-4 rounded-[18px] border border-rose-500/20 bg-rose-500/10 px-4 py-3 text-rose-100">
967
- <div className="flex items-start gap-3">
968
- <Icon name="error" className="text-[20px]" />
969
- <p className="text-sm leading-6">{loadError}</p>
970
- </div>
971
- </div>
972
- ) : null}
973
-
974
- <div className="flex flex-1 flex-col min-h-0">
975
- <div className="flex-1 overflow-y-auto py-4">
976
- {currentMessages.length === 0 ? (
977
- <div className="flex min-h-[50vh] items-center justify-center px-4 text-center">
978
- <div className="max-w-xl space-y-4">
979
- <div className="mx-auto flex size-16 items-center justify-center rounded-[20px] border border-white/10 bg-white/5 text-white/80">
980
- <Icon name="chat" className="text-[30px]" />
981
- </div>
982
- <div className="space-y-2">
983
- <h2 className="text-2xl font-semibold text-white">Start a conversation</h2>
984
- <p className="text-sm leading-6 text-white/60">
985
- Simple chat interface to interact with any AI model from connected providers.
986
- Select a model and start chatting!
987
- </p>
988
- </div>
989
- </div>
990
- </div>
991
- ) : null}
992
-
993
- <div className="mx-auto flex w-full max-w-3xl flex-col gap-4 px-4">
994
- {currentMessages.map((message) => {
995
- const isUser = message.role === "user";
996
- const isAssistant = message.role === "assistant";
997
- const isStreaming =
998
- isAssistant &&
999
- message.id === streamingMessageId &&
1000
- message.status === "streaming";
1001
- const content = textValue(message.content) || (isAssistant ? streamingText : "");
1002
-
1003
- return (
1004
- <div
1005
- key={message.id}
1006
- className={`flex w-full ${isUser ? "justify-end" : "justify-start"} mb-6`}
1007
- >
1008
- <div
1009
- className={`max-w-[min(88%,42rem)] ${isUser ? "rounded-3xl bg-[#2f2f2f] px-5 py-3.5 text-white" : "text-white/90"}`}
1010
- >
1011
- <div className="mb-1 flex items-center justify-between gap-3">
1012
- <span className="text-xs font-semibold">
1013
- {isUser ? "You" : activeModel?.name || "Assistant"}
1014
- </span>
1015
- </div>
1016
-
1017
- {message.attachments?.length ? (
1018
- <div className="mb-3 grid grid-cols-2 gap-2 sm:grid-cols-3 mt-2">
1019
- {message.attachments.map((attachment) => (
1020
- <a
1021
- key={attachment.id}
1022
- href={attachment.dataUrl}
1023
- target="_blank"
1024
- rel="noreferrer"
1025
- className="overflow-hidden rounded-[18px] border border-white/10 bg-black/20"
1026
- >
1027
- <img
1028
- src={attachment.dataUrl}
1029
- alt={attachment.name}
1030
- className="h-28 w-full object-cover"
1031
- loading="lazy"
1032
- decoding="async"
1033
- />
1034
- </a>
1035
- ))}
1036
- </div>
1037
- ) : null}
1038
-
1039
- <div className="whitespace-pre-wrap break-words text-[15px] leading-7">
1040
- {content}
1041
- {isAssistant && isStreaming && !streamingText ? (
1042
- <span className="inline-block animate-pulse">▋</span>
1043
- ) : null}
1044
- </div>
1045
- </div>
1046
- </div>
1047
- );
1048
- })}
1049
- </div>
1050
- </div>
1051
-
1052
- <div className="shrink-0 pt-2">
1053
- {attachments.length > 0 ? (
1054
- <div className="mx-auto mb-3 flex w-full max-w-3xl flex-wrap gap-2 px-4">
1055
- {attachments.map((attachment) => (
1056
- <div
1057
- key={attachment.id}
1058
- className="flex items-center gap-2 rounded-full border border-white/10 bg-white/5 px-3 py-2"
1059
- >
1060
- <span className="text-xs text-white/80 max-w-[12rem] truncate">
1061
- {attachment.name}
1062
- </span>
1063
- <button
1064
- type="button"
1065
- onClick={() => removeAttachment(attachment.id)}
1066
- className="text-white/55 hover:text-white"
1067
- aria-label="Remove attachment"
1068
- >
1069
- <Icon name="close" className="text-[18px]" />
1070
- </button>
1071
- </div>
1072
- ))}
1073
- </div>
1074
- ) : null}
1075
-
1076
- <div className="mx-auto w-full max-w-3xl px-4 pb-2">
1077
- <div className="rounded-[26px] bg-[#2f2f2f] px-3 pt-3 pb-2 shadow-[0_0_15px_rgba(0,0,0,0.10)] ring-1 ring-white/5">
1078
- <textarea
1079
- value={draft}
1080
- onChange={(event: any) => setDraft(event.target.value)}
1081
- onKeyDown={handleKeyDown}
1082
- placeholder="Message AI"
1083
- rows={1}
1084
- className="w-full resize-none bg-transparent px-2 text-[15px] leading-6 text-white outline-none placeholder:text-white/40 max-h-[25vh] overflow-y-auto"
1085
- />
1086
-
1087
- <div className="mt-2 flex items-center justify-between gap-3">
1088
- <div className="flex items-center gap-2">
1089
- <button
1090
- type="button"
1091
- onClick={() => fileInputRef.current?.click()}
1092
- disabled={!activeModel || loadingData}
1093
- className="p-2 text-white/50 hover:text-white transition rounded-full hover:bg-white/5"
1094
- >
1095
- <Icon name="attach_file" className="text-[20px]" />
1096
- </button>
1097
- <input
1098
- ref={fileInputRef}
1099
- type="file"
1100
- accept="image/*"
1101
- multiple
1102
- className="hidden"
1103
- onChange={handleAttachFiles}
1104
- />
1105
- <span className="text-xs font-medium text-white/30 truncate max-w-[120px]">
1106
- {activeModel ? activeModel.name : "No model"}
1107
- </span>
1108
- </div>
1109
-
1110
- <div className="flex items-center gap-2">
1111
- {isSending ? (
1112
- <button
1113
- type="button"
1114
- onClick={handleStop}
1115
- className="p-2 text-white bg-white/10 hover:bg-white/20 transition rounded-full h-8 w-8 flex items-center justify-center"
1116
- >
1117
- <Icon name="stop" className="text-[16px]" />
1118
- </button>
1119
- ) : null}
1120
- <button
1121
- onClick={sendMessage}
1122
- disabled={!canSend}
1123
- className={`h-8 w-8 rounded-full flex items-center justify-center transition ${canSend ? "bg-white text-black hover:opacity-90" : "bg-white/10 text-white/30 cursor-not-allowed"}`}
1124
- >
1125
- <Icon name="arrow_upward" className="text-[16px]" />
1126
- </button>
1127
- </div>
1128
- </div>
1129
- </div>
1130
- </div>
1131
- </div>
1132
-
1133
- <p className="mx-auto mt-2 max-w-3xl px-4 pb-4 text-center text-[11px] text-white/30">
1134
- Model list is filtered from connected providers.
1135
- </p>
1136
- </div>
1137
- </div>
1138
- </div>
1139
- );
1140
- }