ruflo 3.42.3 → 3.42.4

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 (479) hide show
  1. package/README.md +422 -422
  2. package/bin/ruflo.js +77 -77
  3. package/package.json +117 -117
  4. package/src/chat-ui/Dockerfile +25 -25
  5. package/src/chat-ui/patch-mcp-url-safety.sh +28 -28
  6. package/src/config/config.example.json +76 -76
  7. package/src/mcp-bridge/Dockerfile +47 -47
  8. package/src/mcp-bridge/index.js +1780 -1780
  9. package/src/mcp-bridge/mcp-stdio-kernel.js +159 -159
  10. package/src/mcp-bridge/package.json +17 -17
  11. package/src/mcp-bridge/test-harness.js +470 -470
  12. package/src/mcp-bridge/test-runtime-security.mjs +208 -208
  13. package/src/mcp-bridge/test-security-lock.js +121 -121
  14. package/src/nginx/Dockerfile +10 -10
  15. package/src/nginx/nginx.conf +67 -67
  16. package/src/nginx/static/favicon-dark.svg +4 -4
  17. package/src/nginx/static/favicon.svg +4 -4
  18. package/src/nginx/static/icon.svg +5 -5
  19. package/src/nginx/static/logo.svg +9 -9
  20. package/src/nginx/static/manifest.json +22 -22
  21. package/src/nginx/static/welcome.js +184 -184
  22. package/src/ruvocal/.claude/skills/add-model-descriptions/SKILL.md +73 -73
  23. package/src/ruvocal/.devcontainer/Dockerfile +9 -9
  24. package/src/ruvocal/.devcontainer/devcontainer.json +36 -36
  25. package/src/ruvocal/.dockerignore +16 -16
  26. package/src/ruvocal/.eslintignore +13 -13
  27. package/src/ruvocal/.eslintrc.cjs +45 -45
  28. package/src/ruvocal/.gcloudignore +18 -18
  29. package/src/ruvocal/.github/ISSUE_TEMPLATE/bug-report--chat-ui-.md +43 -43
  30. package/src/ruvocal/.github/ISSUE_TEMPLATE/config-support.md +9 -9
  31. package/src/ruvocal/.github/ISSUE_TEMPLATE/feature-request--chat-ui-.md +17 -17
  32. package/src/ruvocal/.github/ISSUE_TEMPLATE/huggingchat.md +11 -11
  33. package/src/ruvocal/.github/release.yml +16 -16
  34. package/src/ruvocal/.github/workflows/build-docs.yml +18 -18
  35. package/src/ruvocal/.github/workflows/build-image.yml +142 -142
  36. package/src/ruvocal/.github/workflows/build-pr-docs.yml +20 -20
  37. package/src/ruvocal/.github/workflows/deploy-dev.yml +63 -63
  38. package/src/ruvocal/.github/workflows/deploy-prod.yml +78 -78
  39. package/src/ruvocal/.github/workflows/lint-and-test.yml +84 -84
  40. package/src/ruvocal/.github/workflows/slugify.yaml +72 -72
  41. package/src/ruvocal/.github/workflows/trufflehog.yml +17 -17
  42. package/src/ruvocal/.github/workflows/upload-pr-documentation.yml +16 -16
  43. package/src/ruvocal/.husky/lint-stage-config.js +4 -4
  44. package/src/ruvocal/.husky/pre-commit +2 -2
  45. package/src/ruvocal/.prettierignore +14 -14
  46. package/src/ruvocal/.prettierrc +7 -7
  47. package/src/ruvocal/CLAUDE.md +126 -126
  48. package/src/ruvocal/Dockerfile +96 -96
  49. package/src/ruvocal/LICENSE +202 -202
  50. package/src/ruvocal/PRIVACY.md +41 -41
  51. package/src/ruvocal/README.md +164 -164
  52. package/src/ruvocal/chart/Chart.yaml +5 -5
  53. package/src/ruvocal/chart/env/dev.yaml +260 -260
  54. package/src/ruvocal/chart/env/prod.yaml +273 -273
  55. package/src/ruvocal/chart/templates/_helpers.tpl +22 -22
  56. package/src/ruvocal/chart/templates/config.yaml +10 -10
  57. package/src/ruvocal/chart/templates/deployment.yaml +81 -81
  58. package/src/ruvocal/chart/templates/hpa.yaml +45 -45
  59. package/src/ruvocal/chart/templates/infisical.yaml +24 -24
  60. package/src/ruvocal/chart/templates/ingress-internal.yaml +32 -32
  61. package/src/ruvocal/chart/templates/ingress.yaml +32 -32
  62. package/src/ruvocal/chart/templates/network-policy.yaml +36 -36
  63. package/src/ruvocal/chart/templates/service-account.yaml +13 -13
  64. package/src/ruvocal/chart/templates/service-monitor.yaml +17 -17
  65. package/src/ruvocal/chart/templates/service.yaml +21 -21
  66. package/src/ruvocal/chart/values.yaml +73 -73
  67. package/src/ruvocal/cloudbuild.yaml +68 -68
  68. package/src/ruvocal/config/branding.env.example +19 -19
  69. package/src/ruvocal/docker-compose.yml +21 -21
  70. package/src/ruvocal/docs/adr/ADR-029-HUGGINGFACE-CHAT-UI-CLOUD-RUN.md +1236 -1236
  71. package/src/ruvocal/docs/adr/ADR-033-RUVECTOR-RUFLO-MCP-INTEGRATION.md +111 -111
  72. package/src/ruvocal/docs/adr/ADR-034-OPTIONAL-MCP-BACKENDS.md +117 -117
  73. package/src/ruvocal/docs/adr/ADR-035-MCP-TOOL-GROUPS.md +186 -186
  74. package/src/ruvocal/docs/adr/ADR-037-AUTOPILOT-CHAT-MODE.md +1500 -1500
  75. package/src/ruvocal/docs/adr/ADR-038-RUVOCAL-FORK.md +286 -286
  76. package/src/ruvocal/docs/source/_toctree.yml +30 -30
  77. package/src/ruvocal/docs/source/configuration/common-issues.md +38 -38
  78. package/src/ruvocal/docs/source/configuration/llm-router.md +105 -105
  79. package/src/ruvocal/docs/source/configuration/mcp-tools.md +84 -84
  80. package/src/ruvocal/docs/source/configuration/metrics.md +9 -9
  81. package/src/ruvocal/docs/source/configuration/open-id.md +57 -57
  82. package/src/ruvocal/docs/source/configuration/overview.md +89 -89
  83. package/src/ruvocal/docs/source/configuration/theming.md +20 -20
  84. package/src/ruvocal/docs/source/developing/architecture.md +48 -48
  85. package/src/ruvocal/docs/source/index.md +53 -53
  86. package/src/ruvocal/docs/source/installation/docker.md +43 -43
  87. package/src/ruvocal/docs/source/installation/helm.md +43 -43
  88. package/src/ruvocal/docs/source/installation/local.md +62 -62
  89. package/src/ruvocal/entrypoint.sh +18 -18
  90. package/src/ruvocal/mcp-bridge/Dockerfile +47 -47
  91. package/src/ruvocal/mcp-bridge/cloudbuild.yaml +49 -49
  92. package/src/ruvocal/mcp-bridge/index.js +1995 -1995
  93. package/src/ruvocal/mcp-bridge/mcp-stdio-kernel.js +159 -159
  94. package/src/ruvocal/mcp-bridge/package-lock.json +762 -762
  95. package/src/ruvocal/mcp-bridge/package.json +17 -17
  96. package/src/ruvocal/mcp-bridge/test-harness.js +470 -470
  97. package/src/ruvocal/package-lock.json +11741 -11741
  98. package/src/ruvocal/package.json +121 -121
  99. package/src/ruvocal/postcss.config.js +6 -6
  100. package/src/ruvocal/rvf.manifest.json +204 -204
  101. package/src/ruvocal/scripts/config.ts +64 -64
  102. package/src/ruvocal/scripts/generate-welcome.mjs +181 -181
  103. package/src/ruvocal/scripts/populate.ts +288 -288
  104. package/src/ruvocal/scripts/samples.txt +194 -194
  105. package/src/ruvocal/scripts/setups/vitest-setup-server.ts +44 -44
  106. package/src/ruvocal/scripts/updateLocalEnv.ts +48 -48
  107. package/src/ruvocal/src/ambient.d.ts +7 -7
  108. package/src/ruvocal/src/app.d.ts +29 -29
  109. package/src/ruvocal/src/app.html +53 -53
  110. package/src/ruvocal/src/hooks.server.ts +32 -32
  111. package/src/ruvocal/src/hooks.ts +6 -6
  112. package/src/ruvocal/src/lib/APIClient.ts +148 -148
  113. package/src/ruvocal/src/lib/actions/clickOutside.ts +18 -18
  114. package/src/ruvocal/src/lib/actions/snapScrollToBottom.ts +346 -346
  115. package/src/ruvocal/src/lib/buildPrompt.ts +33 -33
  116. package/src/ruvocal/src/lib/components/AnnouncementBanner.svelte +20 -20
  117. package/src/ruvocal/src/lib/components/BackgroundGenerationPoller.svelte +168 -168
  118. package/src/ruvocal/src/lib/components/CodeBlock.svelte +73 -73
  119. package/src/ruvocal/src/lib/components/CopyToClipBoardBtn.svelte +92 -92
  120. package/src/ruvocal/src/lib/components/DeleteConversationModal.svelte +75 -75
  121. package/src/ruvocal/src/lib/components/EditConversationModal.svelte +100 -100
  122. package/src/ruvocal/src/lib/components/ExpandNavigation.svelte +22 -22
  123. package/src/ruvocal/src/lib/components/FoundationBackground.svelte +242 -242
  124. package/src/ruvocal/src/lib/components/HoverTooltip.svelte +44 -44
  125. package/src/ruvocal/src/lib/components/HtmlPreviewModal.svelte +143 -143
  126. package/src/ruvocal/src/lib/components/InfiniteScroll.svelte +50 -50
  127. package/src/ruvocal/src/lib/components/MobileNav.svelte +300 -300
  128. package/src/ruvocal/src/lib/components/Modal.svelte +115 -115
  129. package/src/ruvocal/src/lib/components/ModelCardMetadata.svelte +71 -71
  130. package/src/ruvocal/src/lib/components/NavConversationItem.svelte +151 -151
  131. package/src/ruvocal/src/lib/components/NavMenu.svelte +313 -313
  132. package/src/ruvocal/src/lib/components/Pagination.svelte +97 -97
  133. package/src/ruvocal/src/lib/components/PaginationArrow.svelte +27 -27
  134. package/src/ruvocal/src/lib/components/Portal.svelte +24 -24
  135. package/src/ruvocal/src/lib/components/RetryBtn.svelte +18 -18
  136. package/src/ruvocal/src/lib/components/RuFloUniverse.svelte +185 -185
  137. package/src/ruvocal/src/lib/components/RufloHelpModal.svelte +411 -411
  138. package/src/ruvocal/src/lib/components/ScrollToBottomBtn.svelte +47 -47
  139. package/src/ruvocal/src/lib/components/ScrollToPreviousBtn.svelte +77 -77
  140. package/src/ruvocal/src/lib/components/ShareConversationModal.svelte +182 -182
  141. package/src/ruvocal/src/lib/components/StopGeneratingBtn.svelte +69 -69
  142. package/src/ruvocal/src/lib/components/SubscribeModal.svelte +87 -87
  143. package/src/ruvocal/src/lib/components/Switch.svelte +36 -36
  144. package/src/ruvocal/src/lib/components/SystemPromptModal.svelte +44 -44
  145. package/src/ruvocal/src/lib/components/Toast.svelte +27 -27
  146. package/src/ruvocal/src/lib/components/Tooltip.svelte +30 -30
  147. package/src/ruvocal/src/lib/components/WelcomeModal.svelte +46 -46
  148. package/src/ruvocal/src/lib/components/chat/Alternatives.svelte +77 -77
  149. package/src/ruvocal/src/lib/components/chat/BlockWrapper.svelte +72 -72
  150. package/src/ruvocal/src/lib/components/chat/ChatInput.svelte +490 -490
  151. package/src/ruvocal/src/lib/components/chat/ChatIntroduction.svelte +123 -123
  152. package/src/ruvocal/src/lib/components/chat/ChatMessage.svelte +548 -548
  153. package/src/ruvocal/src/lib/components/chat/ChatWindow.svelte +1057 -1057
  154. package/src/ruvocal/src/lib/components/chat/FileDropzone.svelte +92 -92
  155. package/src/ruvocal/src/lib/components/chat/ImageLightbox.svelte +66 -66
  156. package/src/ruvocal/src/lib/components/chat/MarkdownBlock.svelte +23 -23
  157. package/src/ruvocal/src/lib/components/chat/MarkdownRenderer.svelte +69 -69
  158. package/src/ruvocal/src/lib/components/chat/MarkdownRenderer.svelte.test.ts +58 -58
  159. package/src/ruvocal/src/lib/components/chat/MessageAvatar.svelte +103 -103
  160. package/src/ruvocal/src/lib/components/chat/ModelSwitch.svelte +64 -64
  161. package/src/ruvocal/src/lib/components/chat/OpenReasoningResults.svelte +81 -81
  162. package/src/ruvocal/src/lib/components/chat/TaskGroup.svelte +88 -88
  163. package/src/ruvocal/src/lib/components/chat/ToolUpdate.svelte +273 -273
  164. package/src/ruvocal/src/lib/components/chat/UploadedFile.svelte +253 -253
  165. package/src/ruvocal/src/lib/components/chat/UrlFetchModal.svelte +203 -203
  166. package/src/ruvocal/src/lib/components/chat/VoiceRecorder.svelte +214 -214
  167. package/src/ruvocal/src/lib/components/icons/IconBurger.svelte +20 -20
  168. package/src/ruvocal/src/lib/components/icons/IconCheap.svelte +20 -20
  169. package/src/ruvocal/src/lib/components/icons/IconChevron.svelte +24 -24
  170. package/src/ruvocal/src/lib/components/icons/IconDazzled.svelte +40 -40
  171. package/src/ruvocal/src/lib/components/icons/IconFast.svelte +20 -20
  172. package/src/ruvocal/src/lib/components/icons/IconLoading.svelte +22 -22
  173. package/src/ruvocal/src/lib/components/icons/IconMCP.svelte +28 -28
  174. package/src/ruvocal/src/lib/components/icons/IconMoon.svelte +21 -21
  175. package/src/ruvocal/src/lib/components/icons/IconNew.svelte +20 -20
  176. package/src/ruvocal/src/lib/components/icons/IconOmni.svelte +90 -90
  177. package/src/ruvocal/src/lib/components/icons/IconPaperclip.svelte +24 -24
  178. package/src/ruvocal/src/lib/components/icons/IconPro.svelte +37 -37
  179. package/src/ruvocal/src/lib/components/icons/IconShare.svelte +21 -21
  180. package/src/ruvocal/src/lib/components/icons/IconSun.svelte +93 -93
  181. package/src/ruvocal/src/lib/components/icons/Logo.svelte +68 -68
  182. package/src/ruvocal/src/lib/components/icons/LogoHuggingFaceBorderless.svelte +54 -54
  183. package/src/ruvocal/src/lib/components/mcp/AddServerForm.svelte +250 -250
  184. package/src/ruvocal/src/lib/components/mcp/MCPServerManager.svelte +185 -185
  185. package/src/ruvocal/src/lib/components/mcp/ServerCard.svelte +203 -203
  186. package/src/ruvocal/src/lib/components/players/AudioPlayer.svelte +82 -82
  187. package/src/ruvocal/src/lib/components/voice/AudioWaveform.svelte +96 -96
  188. package/src/ruvocal/src/lib/components/wasm/GalleryPanel.svelte +357 -357
  189. package/src/ruvocal/src/lib/constants/mcpExamples.ts +114 -114
  190. package/src/ruvocal/src/lib/constants/mime.ts +11 -11
  191. package/src/ruvocal/src/lib/constants/pagination.ts +1 -1
  192. package/src/ruvocal/src/lib/constants/publicSepToken.ts +1 -1
  193. package/src/ruvocal/src/lib/constants/routerExamples.ts +133 -133
  194. package/src/ruvocal/src/lib/constants/rvagentPresets.ts +206 -206
  195. package/src/ruvocal/src/lib/createShareLink.ts +27 -27
  196. package/src/ruvocal/src/lib/jobs/refresh-conversation-stats.ts +297 -297
  197. package/src/ruvocal/src/lib/migrations/lock.ts +56 -56
  198. package/src/ruvocal/src/lib/migrations/migrations.spec.ts +74 -74
  199. package/src/ruvocal/src/lib/migrations/migrations.ts +109 -109
  200. package/src/ruvocal/src/lib/migrations/routines/01-update-search-assistants.ts +50 -50
  201. package/src/ruvocal/src/lib/migrations/routines/02-update-assistants-models.ts +48 -48
  202. package/src/ruvocal/src/lib/migrations/routines/04-update-message-updates.ts +151 -151
  203. package/src/ruvocal/src/lib/migrations/routines/05-update-message-files.ts +56 -56
  204. package/src/ruvocal/src/lib/migrations/routines/06-trim-message-updates.ts +56 -56
  205. package/src/ruvocal/src/lib/migrations/routines/08-update-featured-to-review.ts +32 -32
  206. package/src/ruvocal/src/lib/migrations/routines/09-delete-empty-conversations.spec.ts +214 -214
  207. package/src/ruvocal/src/lib/migrations/routines/09-delete-empty-conversations.ts +88 -88
  208. package/src/ruvocal/src/lib/migrations/routines/10-update-reports-assistantid.ts +29 -29
  209. package/src/ruvocal/src/lib/migrations/routines/index.ts +15 -15
  210. package/src/ruvocal/src/lib/server/__tests__/conversation-stop-generating.spec.ts +103 -103
  211. package/src/ruvocal/src/lib/server/abortRegistry.ts +57 -57
  212. package/src/ruvocal/src/lib/server/abortedGenerations.ts +43 -43
  213. package/src/ruvocal/src/lib/server/adminToken.ts +62 -62
  214. package/src/ruvocal/src/lib/server/api/__tests__/conversations-id.spec.ts +296 -296
  215. package/src/ruvocal/src/lib/server/api/__tests__/conversations-message.spec.ts +216 -216
  216. package/src/ruvocal/src/lib/server/api/__tests__/conversations.spec.ts +235 -235
  217. package/src/ruvocal/src/lib/server/api/__tests__/misc.spec.ts +72 -72
  218. package/src/ruvocal/src/lib/server/api/__tests__/testHelpers.ts +86 -86
  219. package/src/ruvocal/src/lib/server/api/__tests__/user-reports.spec.ts +78 -78
  220. package/src/ruvocal/src/lib/server/api/__tests__/user.spec.ts +239 -239
  221. package/src/ruvocal/src/lib/server/api/types.ts +37 -37
  222. package/src/ruvocal/src/lib/server/api/utils/requireAuth.ts +22 -22
  223. package/src/ruvocal/src/lib/server/api/utils/resolveConversation.ts +69 -69
  224. package/src/ruvocal/src/lib/server/api/utils/resolveModel.ts +27 -27
  225. package/src/ruvocal/src/lib/server/api/utils/superjsonResponse.ts +15 -15
  226. package/src/ruvocal/src/lib/server/apiToken.ts +11 -11
  227. package/src/ruvocal/src/lib/server/auth.ts +554 -554
  228. package/src/ruvocal/src/lib/server/config.ts +187 -187
  229. package/src/ruvocal/src/lib/server/conversation.ts +83 -83
  230. package/src/ruvocal/src/lib/server/database/__tests__/rvf.spec.ts +709 -709
  231. package/src/ruvocal/src/lib/server/database/postgres.ts +700 -700
  232. package/src/ruvocal/src/lib/server/database/rvf.ts +1078 -1078
  233. package/src/ruvocal/src/lib/server/database.ts +145 -145
  234. package/src/ruvocal/src/lib/server/endpoints/document.ts +68 -68
  235. package/src/ruvocal/src/lib/server/endpoints/endpoints.ts +43 -43
  236. package/src/ruvocal/src/lib/server/endpoints/images.ts +211 -211
  237. package/src/ruvocal/src/lib/server/endpoints/openai/endpointOai.ts +266 -266
  238. package/src/ruvocal/src/lib/server/endpoints/openai/openAIChatToTextGenerationStream.ts +212 -212
  239. package/src/ruvocal/src/lib/server/endpoints/openai/openAICompletionToTextGenerationStream.ts +32 -32
  240. package/src/ruvocal/src/lib/server/endpoints/preprocessMessages.ts +61 -61
  241. package/src/ruvocal/src/lib/server/exitHandler.ts +59 -59
  242. package/src/ruvocal/src/lib/server/files/downloadFile.ts +34 -34
  243. package/src/ruvocal/src/lib/server/files/uploadFile.ts +29 -29
  244. package/src/ruvocal/src/lib/server/findRepoRoot.ts +13 -13
  245. package/src/ruvocal/src/lib/server/generateFromDefaultEndpoint.ts +46 -46
  246. package/src/ruvocal/src/lib/server/hooks/error.ts +37 -37
  247. package/src/ruvocal/src/lib/server/hooks/fetch.ts +22 -22
  248. package/src/ruvocal/src/lib/server/hooks/handle.ts +250 -250
  249. package/src/ruvocal/src/lib/server/hooks/init.ts +51 -51
  250. package/src/ruvocal/src/lib/server/isURLLocal.spec.ts +31 -31
  251. package/src/ruvocal/src/lib/server/isURLLocal.ts +74 -74
  252. package/src/ruvocal/src/lib/server/logger.ts +42 -42
  253. package/src/ruvocal/src/lib/server/mcp/clientPool.spec.ts +175 -175
  254. package/src/ruvocal/src/lib/server/mcp/hf.ts +32 -32
  255. package/src/ruvocal/src/lib/server/mcp/httpClient.ts +122 -122
  256. package/src/ruvocal/src/lib/server/mcp/registry.ts +76 -76
  257. package/src/ruvocal/src/lib/server/mcp/tools.ts +196 -196
  258. package/src/ruvocal/src/lib/server/metrics.ts +255 -255
  259. package/src/ruvocal/src/lib/server/models.ts +518 -518
  260. package/src/ruvocal/src/lib/server/requestContext.ts +55 -55
  261. package/src/ruvocal/src/lib/server/router/arch.ts +230 -230
  262. package/src/ruvocal/src/lib/server/router/endpoint.ts +316 -316
  263. package/src/ruvocal/src/lib/server/router/multimodal.ts +28 -28
  264. package/src/ruvocal/src/lib/server/router/policy.ts +49 -49
  265. package/src/ruvocal/src/lib/server/router/toolsRoute.ts +51 -51
  266. package/src/ruvocal/src/lib/server/router/types.ts +21 -21
  267. package/src/ruvocal/src/lib/server/sendSlack.ts +23 -23
  268. package/src/ruvocal/src/lib/server/textGeneration/generate.ts +258 -258
  269. package/src/ruvocal/src/lib/server/textGeneration/index.ts +96 -96
  270. package/src/ruvocal/src/lib/server/textGeneration/mcp/fileRefs.ts +155 -155
  271. package/src/ruvocal/src/lib/server/textGeneration/mcp/routerResolution.ts +108 -108
  272. package/src/ruvocal/src/lib/server/textGeneration/mcp/runMcpFlow.ts +831 -831
  273. package/src/ruvocal/src/lib/server/textGeneration/mcp/toolInvocation.ts +349 -349
  274. package/src/ruvocal/src/lib/server/textGeneration/mcp/wasmTools.test.ts +633 -633
  275. package/src/ruvocal/src/lib/server/textGeneration/reasoning.ts +23 -23
  276. package/src/ruvocal/src/lib/server/textGeneration/title.ts +83 -83
  277. package/src/ruvocal/src/lib/server/textGeneration/types.ts +28 -28
  278. package/src/ruvocal/src/lib/server/textGeneration/utils/prepareFiles.ts +88 -88
  279. package/src/ruvocal/src/lib/server/textGeneration/utils/routing.ts +21 -21
  280. package/src/ruvocal/src/lib/server/textGeneration/utils/toolPrompt.ts +49 -49
  281. package/src/ruvocal/src/lib/server/urlSafety.ts +77 -77
  282. package/src/ruvocal/src/lib/server/usageLimits.ts +30 -30
  283. package/src/ruvocal/src/lib/stores/autopilotStore.svelte.ts +175 -175
  284. package/src/ruvocal/src/lib/stores/backgroundGenerations.svelte.ts +32 -32
  285. package/src/ruvocal/src/lib/stores/backgroundGenerations.ts +1 -1
  286. package/src/ruvocal/src/lib/stores/errors.ts +9 -9
  287. package/src/ruvocal/src/lib/stores/isAborted.ts +3 -3
  288. package/src/ruvocal/src/lib/stores/isPro.ts +4 -4
  289. package/src/ruvocal/src/lib/stores/loading.ts +3 -3
  290. package/src/ruvocal/src/lib/stores/mcpServers.ts +534 -534
  291. package/src/ruvocal/src/lib/stores/pendingChatInput.ts +3 -3
  292. package/src/ruvocal/src/lib/stores/pendingMessage.ts +9 -9
  293. package/src/ruvocal/src/lib/stores/settings.ts +182 -182
  294. package/src/ruvocal/src/lib/stores/shareModal.ts +13 -13
  295. package/src/ruvocal/src/lib/stores/titleUpdate.ts +8 -8
  296. package/src/ruvocal/src/lib/stores/wasmMcp.ts +472 -472
  297. package/src/ruvocal/src/lib/switchTheme.ts +124 -124
  298. package/src/ruvocal/src/lib/types/AbortedGeneration.ts +8 -8
  299. package/src/ruvocal/src/lib/types/Assistant.ts +31 -31
  300. package/src/ruvocal/src/lib/types/AssistantStats.ts +11 -11
  301. package/src/ruvocal/src/lib/types/ConfigKey.ts +4 -4
  302. package/src/ruvocal/src/lib/types/ConvSidebar.ts +9 -9
  303. package/src/ruvocal/src/lib/types/Conversation.ts +27 -27
  304. package/src/ruvocal/src/lib/types/ConversationStats.ts +13 -13
  305. package/src/ruvocal/src/lib/types/Message.ts +41 -41
  306. package/src/ruvocal/src/lib/types/MessageEvent.ts +10 -10
  307. package/src/ruvocal/src/lib/types/MessageUpdate.ts +139 -139
  308. package/src/ruvocal/src/lib/types/MigrationResult.ts +7 -7
  309. package/src/ruvocal/src/lib/types/Model.ts +23 -23
  310. package/src/ruvocal/src/lib/types/Report.ts +12 -12
  311. package/src/ruvocal/src/lib/types/Review.ts +6 -6
  312. package/src/ruvocal/src/lib/types/Semaphore.ts +19 -19
  313. package/src/ruvocal/src/lib/types/Session.ts +22 -22
  314. package/src/ruvocal/src/lib/types/Settings.ts +93 -93
  315. package/src/ruvocal/src/lib/types/SharedConversation.ts +9 -9
  316. package/src/ruvocal/src/lib/types/Template.ts +6 -6
  317. package/src/ruvocal/src/lib/types/Timestamps.ts +4 -4
  318. package/src/ruvocal/src/lib/types/TokenCache.ts +6 -6
  319. package/src/ruvocal/src/lib/types/Tool.ts +77 -77
  320. package/src/ruvocal/src/lib/types/UrlDependency.ts +5 -5
  321. package/src/ruvocal/src/lib/types/User.ts +14 -14
  322. package/src/ruvocal/src/lib/utils/PublicConfig.svelte.ts +75 -75
  323. package/src/ruvocal/src/lib/utils/auth.ts +17 -17
  324. package/src/ruvocal/src/lib/utils/chunk.ts +33 -33
  325. package/src/ruvocal/src/lib/utils/cookiesAreEnabled.ts +13 -13
  326. package/src/ruvocal/src/lib/utils/debounce.ts +17 -17
  327. package/src/ruvocal/src/lib/utils/deepestChild.ts +6 -6
  328. package/src/ruvocal/src/lib/utils/favicon.ts +21 -21
  329. package/src/ruvocal/src/lib/utils/fetchJSON.ts +23 -23
  330. package/src/ruvocal/src/lib/utils/file2base64.ts +14 -14
  331. package/src/ruvocal/src/lib/utils/formatUserCount.ts +37 -37
  332. package/src/ruvocal/src/lib/utils/generationState.spec.ts +75 -75
  333. package/src/ruvocal/src/lib/utils/generationState.ts +26 -26
  334. package/src/ruvocal/src/lib/utils/getHref.ts +41 -41
  335. package/src/ruvocal/src/lib/utils/getReturnFromGenerator.ts +7 -7
  336. package/src/ruvocal/src/lib/utils/haptics.ts +64 -64
  337. package/src/ruvocal/src/lib/utils/hashConv.ts +12 -12
  338. package/src/ruvocal/src/lib/utils/hf.ts +17 -17
  339. package/src/ruvocal/src/lib/utils/isDesktop.ts +7 -7
  340. package/src/ruvocal/src/lib/utils/isUrl.ts +8 -8
  341. package/src/ruvocal/src/lib/utils/isVirtualKeyboard.ts +16 -16
  342. package/src/ruvocal/src/lib/utils/loadAttachmentsFromUrls.ts +115 -115
  343. package/src/ruvocal/src/lib/utils/marked.spec.ts +96 -96
  344. package/src/ruvocal/src/lib/utils/marked.ts +531 -531
  345. package/src/ruvocal/src/lib/utils/mcpValidation.ts +147 -147
  346. package/src/ruvocal/src/lib/utils/mergeAsyncGenerators.ts +38 -38
  347. package/src/ruvocal/src/lib/utils/messageUpdates.spec.ts +286 -286
  348. package/src/ruvocal/src/lib/utils/messageUpdates.ts +332 -332
  349. package/src/ruvocal/src/lib/utils/mime.ts +56 -56
  350. package/src/ruvocal/src/lib/utils/models.ts +14 -14
  351. package/src/ruvocal/src/lib/utils/parseBlocks.ts +120 -120
  352. package/src/ruvocal/src/lib/utils/parseIncompleteMarkdown.ts +644 -644
  353. package/src/ruvocal/src/lib/utils/parseStringToList.ts +10 -10
  354. package/src/ruvocal/src/lib/utils/randomUuid.ts +14 -14
  355. package/src/ruvocal/src/lib/utils/searchTokens.ts +33 -33
  356. package/src/ruvocal/src/lib/utils/sha256.ts +7 -7
  357. package/src/ruvocal/src/lib/utils/stringifyError.ts +12 -12
  358. package/src/ruvocal/src/lib/utils/sum.ts +3 -3
  359. package/src/ruvocal/src/lib/utils/template.spec.ts +59 -59
  360. package/src/ruvocal/src/lib/utils/template.ts +53 -53
  361. package/src/ruvocal/src/lib/utils/timeout.ts +9 -9
  362. package/src/ruvocal/src/lib/utils/toolProgress.spec.ts +46 -46
  363. package/src/ruvocal/src/lib/utils/toolProgress.ts +11 -11
  364. package/src/ruvocal/src/lib/utils/tree/addChildren.spec.ts +102 -102
  365. package/src/ruvocal/src/lib/utils/tree/addChildren.ts +48 -48
  366. package/src/ruvocal/src/lib/utils/tree/addSibling.spec.ts +81 -81
  367. package/src/ruvocal/src/lib/utils/tree/addSibling.ts +41 -41
  368. package/src/ruvocal/src/lib/utils/tree/buildSubtree.spec.ts +110 -110
  369. package/src/ruvocal/src/lib/utils/tree/buildSubtree.ts +24 -24
  370. package/src/ruvocal/src/lib/utils/tree/convertLegacyConversation.spec.ts +31 -31
  371. package/src/ruvocal/src/lib/utils/tree/convertLegacyConversation.ts +36 -36
  372. package/src/ruvocal/src/lib/utils/tree/isMessageId.spec.ts +15 -15
  373. package/src/ruvocal/src/lib/utils/tree/isMessageId.ts +5 -5
  374. package/src/ruvocal/src/lib/utils/tree/tree.d.ts +14 -14
  375. package/src/ruvocal/src/lib/utils/tree/treeHelpers.spec.ts +167 -167
  376. package/src/ruvocal/src/lib/utils/updates.ts +39 -39
  377. package/src/ruvocal/src/lib/utils/urlParams.ts +13 -13
  378. package/src/ruvocal/src/lib/wasm/idb.ts +438 -438
  379. package/src/ruvocal/src/lib/wasm/index.ts +1213 -1213
  380. package/src/ruvocal/src/lib/wasm/tests/wasm-capabilities.test.ts +565 -565
  381. package/src/ruvocal/src/lib/wasm/wasm.worker.ts +332 -332
  382. package/src/ruvocal/src/lib/wasm/workerClient.ts +166 -166
  383. package/src/ruvocal/src/lib/workers/autopilotWorker.ts +221 -221
  384. package/src/ruvocal/src/lib/workers/detailFetchWorker.ts +100 -100
  385. package/src/ruvocal/src/lib/workers/markdownWorker.ts +61 -61
  386. package/src/ruvocal/src/routes/+error.svelte +20 -20
  387. package/src/ruvocal/src/routes/+layout.svelte +324 -324
  388. package/src/ruvocal/src/routes/+layout.ts +91 -91
  389. package/src/ruvocal/src/routes/+page.svelte +168 -168
  390. package/src/ruvocal/src/routes/.well-known/oauth-cimd/+server.ts +37 -37
  391. package/src/ruvocal/src/routes/__debug/openai/+server.ts +21 -21
  392. package/src/ruvocal/src/routes/admin/export/+server.ts +159 -159
  393. package/src/ruvocal/src/routes/admin/stats/compute/+server.ts +16 -16
  394. package/src/ruvocal/src/routes/api/conversation/[id]/+server.ts +40 -40
  395. package/src/ruvocal/src/routes/api/conversation/[id]/message/[messageId]/+server.ts +42 -42
  396. package/src/ruvocal/src/routes/api/conversations/+server.ts +48 -48
  397. package/src/ruvocal/src/routes/api/fetch-url/+server.ts +147 -147
  398. package/src/ruvocal/src/routes/api/mcp/health/+server.ts +292 -292
  399. package/src/ruvocal/src/routes/api/mcp/servers/+server.ts +32 -32
  400. package/src/ruvocal/src/routes/api/models/+server.ts +25 -25
  401. package/src/ruvocal/src/routes/api/transcribe/+server.ts +104 -104
  402. package/src/ruvocal/src/routes/api/user/+server.ts +15 -15
  403. package/src/ruvocal/src/routes/api/user/validate-token/+server.ts +20 -20
  404. package/src/ruvocal/src/routes/api/v2/conversations/+server.ts +48 -48
  405. package/src/ruvocal/src/routes/api/v2/conversations/[id]/+server.ts +94 -94
  406. package/src/ruvocal/src/routes/api/v2/conversations/[id]/message/[messageId]/+server.ts +43 -43
  407. package/src/ruvocal/src/routes/api/v2/conversations/import-share/+server.ts +23 -23
  408. package/src/ruvocal/src/routes/api/v2/debug/config/+server.ts +16 -16
  409. package/src/ruvocal/src/routes/api/v2/debug/refresh/+server.ts +30 -30
  410. package/src/ruvocal/src/routes/api/v2/export/+server.ts +196 -196
  411. package/src/ruvocal/src/routes/api/v2/feature-flags/+server.ts +14 -14
  412. package/src/ruvocal/src/routes/api/v2/models/+server.ts +38 -38
  413. package/src/ruvocal/src/routes/api/v2/models/[namespace]/+server.ts +8 -8
  414. package/src/ruvocal/src/routes/api/v2/models/[namespace]/[model]/+server.ts +8 -8
  415. package/src/ruvocal/src/routes/api/v2/models/[namespace]/[model]/subscribe/+server.ts +28 -28
  416. package/src/ruvocal/src/routes/api/v2/models/[namespace]/subscribe/+server.ts +28 -28
  417. package/src/ruvocal/src/routes/api/v2/models/old/+server.ts +7 -7
  418. package/src/ruvocal/src/routes/api/v2/models/refresh/+server.ts +33 -33
  419. package/src/ruvocal/src/routes/api/v2/public-config/+server.ts +7 -7
  420. package/src/ruvocal/src/routes/api/v2/user/+server.ts +17 -17
  421. package/src/ruvocal/src/routes/api/v2/user/billing-orgs/+server.ts +73 -73
  422. package/src/ruvocal/src/routes/api/v2/user/reports/+server.ts +17 -17
  423. package/src/ruvocal/src/routes/api/v2/user/settings/+server.ts +110 -110
  424. package/src/ruvocal/src/routes/conversation/+server.ts +115 -115
  425. package/src/ruvocal/src/routes/conversation/[id]/+page.svelte +593 -593
  426. package/src/ruvocal/src/routes/conversation/[id]/+page.ts +60 -60
  427. package/src/ruvocal/src/routes/conversation/[id]/+server.ts +740 -740
  428. package/src/ruvocal/src/routes/conversation/[id]/message/[messageId]/prompt/+server.ts +66 -66
  429. package/src/ruvocal/src/routes/conversation/[id]/share/+server.ts +69 -69
  430. package/src/ruvocal/src/routes/conversation/[id]/stop-generating/+server.ts +35 -35
  431. package/src/ruvocal/src/routes/healthcheck/+server.ts +3 -3
  432. package/src/ruvocal/src/routes/login/+server.ts +5 -5
  433. package/src/ruvocal/src/routes/login/callback/+server.ts +103 -103
  434. package/src/ruvocal/src/routes/login/callback/updateUser.spec.ts +157 -157
  435. package/src/ruvocal/src/routes/login/callback/updateUser.ts +215 -215
  436. package/src/ruvocal/src/routes/logout/+server.ts +18 -18
  437. package/src/ruvocal/src/routes/metrics/+server.ts +18 -18
  438. package/src/ruvocal/src/routes/models/+page.svelte +233 -233
  439. package/src/ruvocal/src/routes/models/[...model]/+page.svelte +161 -161
  440. package/src/ruvocal/src/routes/models/[...model]/+page.ts +14 -14
  441. package/src/ruvocal/src/routes/models/[...model]/thumbnail.png/+server.ts +64 -64
  442. package/src/ruvocal/src/routes/models/[...model]/thumbnail.png/ModelThumbnail.svelte +28 -28
  443. package/src/ruvocal/src/routes/privacy/+page.svelte +11 -11
  444. package/src/ruvocal/src/routes/r/[id]/+page.ts +34 -34
  445. package/src/ruvocal/src/routes/settings/(nav)/+layout.svelte +282 -282
  446. package/src/ruvocal/src/routes/settings/(nav)/+layout.ts +1 -1
  447. package/src/ruvocal/src/routes/settings/(nav)/+server.ts +59 -59
  448. package/src/ruvocal/src/routes/settings/(nav)/[...model]/+page.svelte +464 -464
  449. package/src/ruvocal/src/routes/settings/(nav)/[...model]/+page.ts +14 -14
  450. package/src/ruvocal/src/routes/settings/(nav)/application/+page.svelte +362 -362
  451. package/src/ruvocal/src/routes/settings/+layout.svelte +40 -40
  452. package/src/ruvocal/src/styles/highlight-js.css +195 -195
  453. package/src/ruvocal/src/styles/main.css +144 -144
  454. package/src/ruvocal/static/chatui/favicon-dark.svg +3 -3
  455. package/src/ruvocal/static/chatui/favicon-dev.svg +3 -3
  456. package/src/ruvocal/static/chatui/favicon.svg +3 -3
  457. package/src/ruvocal/static/chatui/icon.svg +3 -3
  458. package/src/ruvocal/static/chatui/logo.svg +7 -7
  459. package/src/ruvocal/static/chatui/manifest.json +54 -54
  460. package/src/ruvocal/static/chatui/welcome.js +184 -184
  461. package/src/ruvocal/static/huggingchat/favicon-dark.svg +4 -4
  462. package/src/ruvocal/static/huggingchat/favicon-dev.svg +4 -4
  463. package/src/ruvocal/static/huggingchat/favicon.svg +4 -4
  464. package/src/ruvocal/static/huggingchat/fulltext-logo.svg +1 -1
  465. package/src/ruvocal/static/huggingchat/icon.svg +4 -4
  466. package/src/ruvocal/static/huggingchat/logo.svg +4 -4
  467. package/src/ruvocal/static/huggingchat/manifest.json +54 -54
  468. package/src/ruvocal/static/huggingchat/routes.chat.json +226 -226
  469. package/src/ruvocal/static/robots.txt +10 -10
  470. package/src/ruvocal/static/wasm/rvagent_wasm.js +1539 -1539
  471. package/src/ruvocal/stub/@reflink/reflink/package.json +5 -5
  472. package/src/ruvocal/svelte.config.js +53 -53
  473. package/src/ruvocal/tailwind.config.cjs +30 -30
  474. package/src/ruvocal/tsconfig.json +19 -19
  475. package/src/ruvocal/vite.config.ts +87 -87
  476. package/src/scripts/deploy.sh +116 -116
  477. package/src/scripts/generate-config.js +245 -245
  478. package/src/scripts/generate-welcome.js +187 -187
  479. package/src/scripts/package-rvf.sh +116 -116
@@ -1,593 +1,593 @@
1
- <script lang="ts">
2
- import ChatWindow from "$lib/components/chat/ChatWindow.svelte";
3
- import { pendingMessage } from "$lib/stores/pendingMessage";
4
- import { isAborted } from "$lib/stores/isAborted";
5
- import { onMount } from "svelte";
6
- import { page } from "$app/state";
7
- import { beforeNavigate, invalidateAll } from "$app/navigation";
8
- import { base } from "$app/paths";
9
- import { ERROR_MESSAGES, error } from "$lib/stores/errors";
10
- import { findCurrentModel } from "$lib/utils/models";
11
- import type { Message } from "$lib/types/Message";
12
- import { MessageUpdateStatus, MessageUpdateType } from "$lib/types/MessageUpdate";
13
- import titleUpdate from "$lib/stores/titleUpdate";
14
- import file2base64 from "$lib/utils/file2base64";
15
- import { addChildren } from "$lib/utils/tree/addChildren";
16
- import { addSibling } from "$lib/utils/tree/addSibling";
17
- import { fetchMessageUpdates, resolveStreamingMode } from "$lib/utils/messageUpdates";
18
- import type { v4 } from "uuid";
19
- import { useSettingsStore } from "$lib/stores/settings.js";
20
- import { enabledServers } from "$lib/stores/mcpServers";
21
- import { browser } from "$app/environment";
22
- import {
23
- addBackgroundGeneration,
24
- removeBackgroundGeneration,
25
- } from "$lib/stores/backgroundGenerations";
26
- import type { TreeNode, TreeId } from "$lib/utils/tree/tree";
27
- import "katex/dist/katex.min.css";
28
- import { updateDebouncer } from "$lib/utils/updates.js";
29
- import SubscribeModal from "$lib/components/SubscribeModal.svelte";
30
- import { loading } from "$lib/stores/loading.js";
31
- import { requireAuthUser } from "$lib/utils/auth.js";
32
- import { isConversationGenerationActive } from "$lib/utils/generationState";
33
-
34
- let { data = $bindable() } = $props();
35
-
36
- let convId = $derived(page.params.id ?? "");
37
- let pending = $state(false);
38
- let initialRun = true;
39
- let showSubscribeModal = $state(false);
40
- let stopRequested = $state(false);
41
-
42
- let files: File[] = $state([]);
43
-
44
- let conversations = $state(data.conversations);
45
- $effect(() => {
46
- conversations = data.conversations;
47
- });
48
-
49
- function createMessagesPath<T>(messages: TreeNode<T>[], msgId?: TreeId): TreeNode<T>[] {
50
- if (initialRun) {
51
- if (!msgId && page.url.searchParams.get("leafId")) {
52
- msgId = page.url.searchParams.get("leafId") as string;
53
- page.url.searchParams.delete("leafId");
54
- }
55
- if (!msgId && browser && localStorage.getItem("leafId")) {
56
- msgId = localStorage.getItem("leafId") as string;
57
- }
58
- initialRun = false;
59
- }
60
-
61
- const msg = messages.find((msg) => msg.id === msgId) ?? messages.at(-1);
62
- if (!msg) return [];
63
- // ancestor path
64
- const { ancestors } = msg;
65
- const path = [];
66
- if (ancestors?.length) {
67
- for (const ancestorId of ancestors) {
68
- const ancestor = messages.find((msg) => msg.id === ancestorId);
69
- if (ancestor) {
70
- path.push(ancestor);
71
- }
72
- }
73
- }
74
-
75
- // push the node itself in the middle
76
- path.push(msg);
77
-
78
- // children path
79
- let childrenIds = msg.children;
80
- while (childrenIds?.length) {
81
- let lastChildId = childrenIds.at(-1);
82
- const lastChild = messages.find((msg) => msg.id === lastChildId);
83
- if (lastChild) {
84
- path.push(lastChild);
85
- }
86
- childrenIds = lastChild?.children;
87
- }
88
-
89
- return path;
90
- }
91
-
92
- function createMessagesAlternatives<T>(messages: TreeNode<T>[]): TreeId[][] {
93
- const alternatives = [];
94
- for (const message of messages) {
95
- if (message.children?.length) {
96
- alternatives.push(message.children);
97
- }
98
- }
99
- return alternatives;
100
- }
101
-
102
- // this function is used to send new message to the backends
103
- async function writeMessage({
104
- prompt,
105
- messageId = messagesPath.at(-1)?.id ?? undefined,
106
- isRetry = false,
107
- }: {
108
- prompt?: string;
109
- messageId?: ReturnType<typeof v4>;
110
- isRetry?: boolean;
111
- }): Promise<void> {
112
- try {
113
- stopRequested = false;
114
- $isAborted = false;
115
- $loading = true;
116
- pending = true;
117
- const base64Files = await Promise.all(
118
- (files ?? []).map((file) =>
119
- file2base64(file).then((value) => ({
120
- type: "base64" as const,
121
- value,
122
- mime: file.type,
123
- name: file.name,
124
- }))
125
- )
126
- );
127
-
128
- let messageToWriteToId: Message["id"] | undefined = undefined;
129
- // used for building the prompt, subtree of the conversation that goes from the latest message to the root
130
-
131
- if (isRetry && messageId) {
132
- // two cases, if we're retrying a user message with a newPrompt set,
133
- // it means we're editing a user message
134
- // if we're retrying on an assistant message, newPrompt cannot be set
135
- // it means we're retrying the last assistant message for a new answer
136
-
137
- const messageToRetry = messages.find((message) => message.id === messageId);
138
-
139
- if (!messageToRetry) {
140
- $error = "Message not found";
141
- }
142
-
143
- if (messageToRetry?.from === "user" && prompt) {
144
- // add a sibling to this message from the user, with the alternative prompt
145
- // add a children to that sibling, where we can write to
146
- const newUserMessageId = addSibling(
147
- {
148
- messages,
149
- rootMessageId: data.rootMessageId,
150
- },
151
- {
152
- from: "user",
153
- content: prompt,
154
- files: messageToRetry.files,
155
- },
156
- messageId
157
- );
158
- messageToWriteToId = addChildren(
159
- {
160
- messages,
161
- rootMessageId: data.rootMessageId,
162
- },
163
- { from: "assistant", content: "" },
164
- newUserMessageId
165
- );
166
- } else if (messageToRetry?.from === "assistant") {
167
- // we're retrying an assistant message, to generate a new answer
168
- // just add a sibling to the assistant answer where we can write to
169
- messageToWriteToId = addSibling(
170
- {
171
- messages,
172
- rootMessageId: data.rootMessageId,
173
- },
174
- { from: "assistant", content: "" },
175
- messageId
176
- );
177
- }
178
- } else {
179
- // just a normal linear conversation, so we add the user message
180
- // and the blank assistant message back to back
181
- const newUserMessageId = addChildren(
182
- {
183
- messages,
184
- rootMessageId: data.rootMessageId,
185
- },
186
- {
187
- from: "user",
188
- content: prompt ?? "",
189
- files: base64Files,
190
- },
191
- messageId
192
- );
193
-
194
- if (!data.rootMessageId) {
195
- data.rootMessageId = newUserMessageId;
196
- }
197
-
198
- messageToWriteToId = addChildren(
199
- {
200
- messages,
201
- rootMessageId: data.rootMessageId,
202
- },
203
- {
204
- from: "assistant",
205
- content: "",
206
- },
207
- newUserMessageId
208
- );
209
- }
210
-
211
- const userMessage = messages.find((message) => message.id === messageId);
212
- const messageToWriteTo = messages.find((message) => message.id === messageToWriteToId);
213
- if (!messageToWriteTo) {
214
- throw new Error("Message to write to not found");
215
- }
216
-
217
- const messageUpdatesAbortController = new AbortController();
218
- const streamingMode = resolveStreamingMode($settings);
219
-
220
- const messageUpdatesIterator = await fetchMessageUpdates(
221
- convId,
222
- {
223
- base,
224
- inputs: prompt,
225
- messageId,
226
- isRetry,
227
- files: isRetry ? userMessage?.files : base64Files,
228
- selectedMcpServerNames: $enabledServers.map((s) => s.name),
229
- selectedMcpServers: $enabledServers.map((s) => ({
230
- name: s.name,
231
- url: s.url,
232
- headers: s.headers,
233
- })),
234
- streamingMode,
235
- autopilot: $settings.autopilotEnabled === true,
236
- autopilotMaxSteps:
237
- typeof $settings.autopilotMaxSteps === "number"
238
- ? $settings.autopilotMaxSteps
239
- : undefined,
240
- },
241
- messageUpdatesAbortController.signal
242
- ).catch((err) => {
243
- error.set(err.message);
244
- });
245
- if (messageUpdatesIterator === undefined) return;
246
-
247
- files = [];
248
- let buffer = "";
249
- // Initialize lastUpdateTime outside the loop to persist between updates
250
- let lastUpdateTime = new Date();
251
- let frameFlushScheduled = false;
252
- let frameFlushTimeout: ReturnType<typeof setTimeout> | undefined;
253
-
254
- const flushBuffer = (currentTime: Date) => {
255
- if (buffer.length === 0) return;
256
- messageToWriteTo.content += buffer;
257
- buffer = "";
258
- lastUpdateTime = currentTime;
259
- };
260
-
261
- const scheduleFrameFlush = () => {
262
- if (frameFlushScheduled) return;
263
- frameFlushScheduled = true;
264
- const flush = () => {
265
- if (!frameFlushScheduled) return;
266
- frameFlushScheduled = false;
267
- if (frameFlushTimeout !== undefined) {
268
- clearTimeout(frameFlushTimeout);
269
- frameFlushTimeout = undefined;
270
- }
271
- flushBuffer(new Date());
272
- };
273
- if (typeof requestAnimationFrame === "function") {
274
- requestAnimationFrame(flush);
275
- }
276
- // requestAnimationFrame is suspended in background tabs. Keep a
277
- // bounded fallback so buffered tokens still reach the DOM there.
278
- frameFlushTimeout = setTimeout(flush, 300);
279
- };
280
-
281
- for await (const update of messageUpdatesIterator) {
282
- if ($isAborted) {
283
- messageUpdatesAbortController.abort();
284
- return;
285
- }
286
-
287
- // Remove null characters added due to remote keylogging prevention
288
- // See server code for more details
289
- if (update.type === MessageUpdateType.Stream) {
290
- update.token = update.token.replaceAll("\0", "");
291
- }
292
-
293
- const isKeepAlive =
294
- update.type === MessageUpdateType.Status &&
295
- update.status === MessageUpdateStatus.KeepAlive;
296
-
297
- if (!isKeepAlive) {
298
- if (update.type === MessageUpdateType.Stream) {
299
- const existingUpdates = messageToWriteTo.updates ?? [];
300
- const lastUpdate = existingUpdates.at(-1);
301
- if (lastUpdate?.type === MessageUpdateType.Stream) {
302
- // Create fresh objects/arrays so the UI reacts to merged tokens
303
- const merged = {
304
- ...lastUpdate,
305
- token: (lastUpdate.token ?? "") + (update.token ?? ""),
306
- };
307
- messageToWriteTo.updates = [...existingUpdates.slice(0, -1), merged];
308
- } else {
309
- messageToWriteTo.updates = [...existingUpdates, update];
310
- }
311
- } else {
312
- messageToWriteTo.updates = [...(messageToWriteTo.updates ?? []), update];
313
- }
314
- }
315
- const currentTime = new Date();
316
-
317
- // If we receive a non-stream update (e.g. tool/status/final answer),
318
- // flush any buffered stream tokens so the UI doesn't appear to cut
319
- // mid-sentence while tools are running or the final answer arrives.
320
- if (update.type !== MessageUpdateType.Stream && buffer.length > 0) {
321
- flushBuffer(currentTime);
322
- }
323
-
324
- if (update.type === MessageUpdateType.Stream) {
325
- buffer += update.token;
326
- if (streamingMode === "smooth") {
327
- // Coalesce UI updates to animation frames for smooth mode.
328
- scheduleFrameFlush();
329
- } else if (
330
- currentTime.getTime() - lastUpdateTime.getTime() >
331
- updateDebouncer.maxUpdateTime
332
- ) {
333
- flushBuffer(currentTime);
334
- }
335
- pending = false;
336
- } else if (update.type === MessageUpdateType.FinalAnswer) {
337
- // Mirror server-side merge behavior so the UI reflects the
338
- // final text once tools complete, while preserving any
339
- // pre‑tool streamed content when appropriate.
340
- const finalText = update.text ?? "";
341
- const isInterrupted = update.interrupted === true;
342
- const hadTools =
343
- messageToWriteTo.updates?.some((u) => u.type === MessageUpdateType.Tool) ?? false;
344
-
345
- if (isInterrupted) {
346
- // Preserve streamed content on abort. If we never streamed, fall back to finalText.
347
- if (!messageToWriteTo.content) {
348
- messageToWriteTo.content = finalText;
349
- }
350
- } else if (hadTools) {
351
- const existing = messageToWriteTo.content;
352
- const trimmedExistingSuffix = existing.replace(/\s+$/, "");
353
- const trimmedFinalPrefix = finalText.replace(/^\s+/, "");
354
- const alreadyStreamed =
355
- finalText &&
356
- (existing.endsWith(finalText) ||
357
- (trimmedFinalPrefix.length > 0 &&
358
- trimmedExistingSuffix.endsWith(trimmedFinalPrefix)));
359
-
360
- if (existing && existing.length > 0) {
361
- if (alreadyStreamed) {
362
- // A. Already streamed the same final text; keep as-is.
363
- messageToWriteTo.content = existing;
364
- } else if (
365
- finalText &&
366
- (finalText.startsWith(existing) ||
367
- (trimmedExistingSuffix.length > 0 &&
368
- trimmedFinalPrefix.startsWith(trimmedExistingSuffix)))
369
- ) {
370
- // B. Final text already includes streamed prefix; use it verbatim.
371
- messageToWriteTo.content = finalText;
372
- } else {
373
- // C. Merge with a paragraph break for readability.
374
- const needsGap = !/\n\n$/.test(existing) && !/^\n/.test(finalText ?? "");
375
- messageToWriteTo.content = existing + (needsGap ? "\n\n" : "") + finalText;
376
- }
377
- } else {
378
- messageToWriteTo.content = finalText;
379
- }
380
- } else {
381
- // No tools: final answer replaces streamed content so
382
- // the provider's final text is authoritative.
383
- messageToWriteTo.content = finalText;
384
- }
385
- } else if (
386
- update.type === MessageUpdateType.Status &&
387
- update.status === MessageUpdateStatus.Error
388
- ) {
389
- // Check if this is a 402 payment required error
390
- if (update.statusCode === 402) {
391
- showSubscribeModal = true;
392
- } else {
393
- $error = update.message ?? "An error has occurred";
394
- }
395
- } else if (update.type === MessageUpdateType.Title) {
396
- const convInData = conversations.find(({ id }) => id === page.params.id);
397
- if (convInData) {
398
- convInData.title = update.title;
399
-
400
- $titleUpdate = {
401
- title: update.title,
402
- convId,
403
- };
404
- }
405
- } else if (update.type === MessageUpdateType.File) {
406
- messageToWriteTo.files = [
407
- ...(messageToWriteTo.files ?? []),
408
- { type: "hash", value: update.sha, mime: update.mime, name: update.name },
409
- ];
410
- } else if (update.type === MessageUpdateType.RouterMetadata) {
411
- // Update router metadata immediately when received
412
- messageToWriteTo.routerMetadata = {
413
- route: update.route,
414
- model: update.model,
415
- };
416
- } else if (update.type === MessageUpdateType.AutopilotStep) {
417
- // Track autopilot step progress — stored on updates array for UI rendering
418
- }
419
- }
420
-
421
- if (buffer.length > 0) {
422
- flushBuffer(new Date());
423
- }
424
- } catch (err) {
425
- if (err instanceof Error && err.message.includes("overloaded")) {
426
- $error = "Too much traffic, please try again.";
427
- } else if (err instanceof Error && err.message.includes("429")) {
428
- $error = ERROR_MESSAGES.rateLimited;
429
- } else if (err instanceof Error) {
430
- $error = err.message;
431
- } else {
432
- $error = ERROR_MESSAGES.default;
433
- }
434
- console.error(err);
435
- } finally {
436
- $loading = false;
437
- pending = false;
438
- await invalidateAll();
439
- }
440
- }
441
-
442
- async function stopGeneration() {
443
- stopRequested = true;
444
- $isAborted = true;
445
- $loading = false;
446
-
447
- const sendStopRequest = async () => {
448
- const response = await fetch(`${base}/conversation/${page.params.id}/stop-generating`, {
449
- method: "POST",
450
- });
451
- if (!response.ok) {
452
- throw new Error(`Stop request failed: ${response.status}`);
453
- }
454
- };
455
-
456
- try {
457
- await sendStopRequest();
458
- } catch (firstErr) {
459
- try {
460
- await new Promise((resolve) => setTimeout(resolve, 300));
461
- await sendStopRequest();
462
- } catch (retryErr) {
463
- console.error("Failed to stop generation", firstErr, retryErr);
464
- $error = "Failed to stop generation. Please try again.";
465
- }
466
- }
467
- }
468
-
469
- function handleKeydown(event: KeyboardEvent) {
470
- // Stop generation on ESC key when loading
471
- if (event.key === "Escape" && $loading) {
472
- event.preventDefault();
473
- stopGeneration();
474
- }
475
- }
476
-
477
- onMount(async () => {
478
- if ($pendingMessage) {
479
- files = $pendingMessage.files;
480
- await writeMessage({ prompt: $pendingMessage.content });
481
- $pendingMessage = undefined;
482
- }
483
-
484
- const streaming = isConversationGenerationActive(messages);
485
- if (streaming) {
486
- addBackgroundGeneration({ id: convId, startedAt: Date.now() });
487
- $loading = true;
488
- }
489
- });
490
-
491
- async function onMessage(content: string) {
492
- await writeMessage({ prompt: content });
493
- }
494
-
495
- async function onRetry(payload: { id: Message["id"]; content?: string }) {
496
- if (requireAuthUser()) return;
497
-
498
- const lastMsgId = payload.id;
499
- messagesPath = createMessagesPath(messages, lastMsgId);
500
-
501
- await writeMessage({
502
- prompt: payload.content,
503
- messageId: payload.id,
504
- isRetry: true,
505
- });
506
- }
507
-
508
- async function onShowAlternateMsg(payload: { id: Message["id"] }) {
509
- const msgId = payload.id;
510
- messagesPath = createMessagesPath(messages, msgId);
511
- }
512
-
513
- const settings = useSettingsStore();
514
- let messages = $state(data.messages);
515
- $effect(() => {
516
- messages = data.messages;
517
- });
518
-
519
- $effect(() => {
520
- page.params.id;
521
- stopRequested = false;
522
- });
523
-
524
- $effect(() => {
525
- const streaming = isConversationGenerationActive(messages);
526
- if (stopRequested) {
527
- $loading = false;
528
- } else if (streaming) {
529
- $loading = true;
530
- } else if (!pending) {
531
- $loading = false;
532
- }
533
-
534
- if (!streaming && browser) {
535
- removeBackgroundGeneration(convId);
536
- }
537
- });
538
-
539
- // create a linear list of `messagesPath` from `messages` that is a tree of threaded messages
540
- let messagesPath = $derived(createMessagesPath(messages));
541
- let messagesAlternatives = $derived(createMessagesAlternatives(messages));
542
-
543
- $effect(() => {
544
- if (browser && messagesPath.at(-1)?.id) {
545
- localStorage.setItem("leafId", messagesPath.at(-1)?.id as string);
546
- }
547
- });
548
-
549
- beforeNavigate((navigation) => {
550
- if (!page.params.id) return;
551
-
552
- const navigatingAway =
553
- navigation.to?.route.id !== page.route.id || navigation.to?.params?.id !== page.params.id;
554
-
555
- if ($loading && navigatingAway) {
556
- addBackgroundGeneration({ id: page.params.id, startedAt: Date.now() });
557
- }
558
-
559
- $isAborted = true;
560
- $loading = false;
561
- });
562
-
563
- let title = $derived.by(() => {
564
- const rawTitle = conversations.find((conv) => conv.id === page.params.id)?.title ?? data.title;
565
- return rawTitle ? rawTitle.charAt(0).toUpperCase() + rawTitle.slice(1) : rawTitle;
566
- });
567
- </script>
568
-
569
- <svelte:window onkeydown={handleKeydown} />
570
-
571
- <svelte:head>
572
- <title>{title}</title>
573
- </svelte:head>
574
-
575
- <ChatWindow
576
- loading={$loading}
577
- {pending}
578
- messages={messagesPath as Message[]}
579
- {messagesAlternatives}
580
- shared={data.shared}
581
- preprompt={data.preprompt}
582
- bind:files
583
- onmessage={onMessage}
584
- onretry={onRetry}
585
- onshowAlternateMsg={onShowAlternateMsg}
586
- onstop={stopGeneration}
587
- models={data.models}
588
- currentModel={findCurrentModel(data.models, data.oldModels, data.model)}
589
- />
590
-
591
- {#if showSubscribeModal}
592
- <SubscribeModal close={() => (showSubscribeModal = false)} />
593
- {/if}
1
+ <script lang="ts">
2
+ import ChatWindow from "$lib/components/chat/ChatWindow.svelte";
3
+ import { pendingMessage } from "$lib/stores/pendingMessage";
4
+ import { isAborted } from "$lib/stores/isAborted";
5
+ import { onMount } from "svelte";
6
+ import { page } from "$app/state";
7
+ import { beforeNavigate, invalidateAll } from "$app/navigation";
8
+ import { base } from "$app/paths";
9
+ import { ERROR_MESSAGES, error } from "$lib/stores/errors";
10
+ import { findCurrentModel } from "$lib/utils/models";
11
+ import type { Message } from "$lib/types/Message";
12
+ import { MessageUpdateStatus, MessageUpdateType } from "$lib/types/MessageUpdate";
13
+ import titleUpdate from "$lib/stores/titleUpdate";
14
+ import file2base64 from "$lib/utils/file2base64";
15
+ import { addChildren } from "$lib/utils/tree/addChildren";
16
+ import { addSibling } from "$lib/utils/tree/addSibling";
17
+ import { fetchMessageUpdates, resolveStreamingMode } from "$lib/utils/messageUpdates";
18
+ import type { v4 } from "uuid";
19
+ import { useSettingsStore } from "$lib/stores/settings.js";
20
+ import { enabledServers } from "$lib/stores/mcpServers";
21
+ import { browser } from "$app/environment";
22
+ import {
23
+ addBackgroundGeneration,
24
+ removeBackgroundGeneration,
25
+ } from "$lib/stores/backgroundGenerations";
26
+ import type { TreeNode, TreeId } from "$lib/utils/tree/tree";
27
+ import "katex/dist/katex.min.css";
28
+ import { updateDebouncer } from "$lib/utils/updates.js";
29
+ import SubscribeModal from "$lib/components/SubscribeModal.svelte";
30
+ import { loading } from "$lib/stores/loading.js";
31
+ import { requireAuthUser } from "$lib/utils/auth.js";
32
+ import { isConversationGenerationActive } from "$lib/utils/generationState";
33
+
34
+ let { data = $bindable() } = $props();
35
+
36
+ let convId = $derived(page.params.id ?? "");
37
+ let pending = $state(false);
38
+ let initialRun = true;
39
+ let showSubscribeModal = $state(false);
40
+ let stopRequested = $state(false);
41
+
42
+ let files: File[] = $state([]);
43
+
44
+ let conversations = $state(data.conversations);
45
+ $effect(() => {
46
+ conversations = data.conversations;
47
+ });
48
+
49
+ function createMessagesPath<T>(messages: TreeNode<T>[], msgId?: TreeId): TreeNode<T>[] {
50
+ if (initialRun) {
51
+ if (!msgId && page.url.searchParams.get("leafId")) {
52
+ msgId = page.url.searchParams.get("leafId") as string;
53
+ page.url.searchParams.delete("leafId");
54
+ }
55
+ if (!msgId && browser && localStorage.getItem("leafId")) {
56
+ msgId = localStorage.getItem("leafId") as string;
57
+ }
58
+ initialRun = false;
59
+ }
60
+
61
+ const msg = messages.find((msg) => msg.id === msgId) ?? messages.at(-1);
62
+ if (!msg) return [];
63
+ // ancestor path
64
+ const { ancestors } = msg;
65
+ const path = [];
66
+ if (ancestors?.length) {
67
+ for (const ancestorId of ancestors) {
68
+ const ancestor = messages.find((msg) => msg.id === ancestorId);
69
+ if (ancestor) {
70
+ path.push(ancestor);
71
+ }
72
+ }
73
+ }
74
+
75
+ // push the node itself in the middle
76
+ path.push(msg);
77
+
78
+ // children path
79
+ let childrenIds = msg.children;
80
+ while (childrenIds?.length) {
81
+ let lastChildId = childrenIds.at(-1);
82
+ const lastChild = messages.find((msg) => msg.id === lastChildId);
83
+ if (lastChild) {
84
+ path.push(lastChild);
85
+ }
86
+ childrenIds = lastChild?.children;
87
+ }
88
+
89
+ return path;
90
+ }
91
+
92
+ function createMessagesAlternatives<T>(messages: TreeNode<T>[]): TreeId[][] {
93
+ const alternatives = [];
94
+ for (const message of messages) {
95
+ if (message.children?.length) {
96
+ alternatives.push(message.children);
97
+ }
98
+ }
99
+ return alternatives;
100
+ }
101
+
102
+ // this function is used to send new message to the backends
103
+ async function writeMessage({
104
+ prompt,
105
+ messageId = messagesPath.at(-1)?.id ?? undefined,
106
+ isRetry = false,
107
+ }: {
108
+ prompt?: string;
109
+ messageId?: ReturnType<typeof v4>;
110
+ isRetry?: boolean;
111
+ }): Promise<void> {
112
+ try {
113
+ stopRequested = false;
114
+ $isAborted = false;
115
+ $loading = true;
116
+ pending = true;
117
+ const base64Files = await Promise.all(
118
+ (files ?? []).map((file) =>
119
+ file2base64(file).then((value) => ({
120
+ type: "base64" as const,
121
+ value,
122
+ mime: file.type,
123
+ name: file.name,
124
+ }))
125
+ )
126
+ );
127
+
128
+ let messageToWriteToId: Message["id"] | undefined = undefined;
129
+ // used for building the prompt, subtree of the conversation that goes from the latest message to the root
130
+
131
+ if (isRetry && messageId) {
132
+ // two cases, if we're retrying a user message with a newPrompt set,
133
+ // it means we're editing a user message
134
+ // if we're retrying on an assistant message, newPrompt cannot be set
135
+ // it means we're retrying the last assistant message for a new answer
136
+
137
+ const messageToRetry = messages.find((message) => message.id === messageId);
138
+
139
+ if (!messageToRetry) {
140
+ $error = "Message not found";
141
+ }
142
+
143
+ if (messageToRetry?.from === "user" && prompt) {
144
+ // add a sibling to this message from the user, with the alternative prompt
145
+ // add a children to that sibling, where we can write to
146
+ const newUserMessageId = addSibling(
147
+ {
148
+ messages,
149
+ rootMessageId: data.rootMessageId,
150
+ },
151
+ {
152
+ from: "user",
153
+ content: prompt,
154
+ files: messageToRetry.files,
155
+ },
156
+ messageId
157
+ );
158
+ messageToWriteToId = addChildren(
159
+ {
160
+ messages,
161
+ rootMessageId: data.rootMessageId,
162
+ },
163
+ { from: "assistant", content: "" },
164
+ newUserMessageId
165
+ );
166
+ } else if (messageToRetry?.from === "assistant") {
167
+ // we're retrying an assistant message, to generate a new answer
168
+ // just add a sibling to the assistant answer where we can write to
169
+ messageToWriteToId = addSibling(
170
+ {
171
+ messages,
172
+ rootMessageId: data.rootMessageId,
173
+ },
174
+ { from: "assistant", content: "" },
175
+ messageId
176
+ );
177
+ }
178
+ } else {
179
+ // just a normal linear conversation, so we add the user message
180
+ // and the blank assistant message back to back
181
+ const newUserMessageId = addChildren(
182
+ {
183
+ messages,
184
+ rootMessageId: data.rootMessageId,
185
+ },
186
+ {
187
+ from: "user",
188
+ content: prompt ?? "",
189
+ files: base64Files,
190
+ },
191
+ messageId
192
+ );
193
+
194
+ if (!data.rootMessageId) {
195
+ data.rootMessageId = newUserMessageId;
196
+ }
197
+
198
+ messageToWriteToId = addChildren(
199
+ {
200
+ messages,
201
+ rootMessageId: data.rootMessageId,
202
+ },
203
+ {
204
+ from: "assistant",
205
+ content: "",
206
+ },
207
+ newUserMessageId
208
+ );
209
+ }
210
+
211
+ const userMessage = messages.find((message) => message.id === messageId);
212
+ const messageToWriteTo = messages.find((message) => message.id === messageToWriteToId);
213
+ if (!messageToWriteTo) {
214
+ throw new Error("Message to write to not found");
215
+ }
216
+
217
+ const messageUpdatesAbortController = new AbortController();
218
+ const streamingMode = resolveStreamingMode($settings);
219
+
220
+ const messageUpdatesIterator = await fetchMessageUpdates(
221
+ convId,
222
+ {
223
+ base,
224
+ inputs: prompt,
225
+ messageId,
226
+ isRetry,
227
+ files: isRetry ? userMessage?.files : base64Files,
228
+ selectedMcpServerNames: $enabledServers.map((s) => s.name),
229
+ selectedMcpServers: $enabledServers.map((s) => ({
230
+ name: s.name,
231
+ url: s.url,
232
+ headers: s.headers,
233
+ })),
234
+ streamingMode,
235
+ autopilot: $settings.autopilotEnabled === true,
236
+ autopilotMaxSteps:
237
+ typeof $settings.autopilotMaxSteps === "number"
238
+ ? $settings.autopilotMaxSteps
239
+ : undefined,
240
+ },
241
+ messageUpdatesAbortController.signal
242
+ ).catch((err) => {
243
+ error.set(err.message);
244
+ });
245
+ if (messageUpdatesIterator === undefined) return;
246
+
247
+ files = [];
248
+ let buffer = "";
249
+ // Initialize lastUpdateTime outside the loop to persist between updates
250
+ let lastUpdateTime = new Date();
251
+ let frameFlushScheduled = false;
252
+ let frameFlushTimeout: ReturnType<typeof setTimeout> | undefined;
253
+
254
+ const flushBuffer = (currentTime: Date) => {
255
+ if (buffer.length === 0) return;
256
+ messageToWriteTo.content += buffer;
257
+ buffer = "";
258
+ lastUpdateTime = currentTime;
259
+ };
260
+
261
+ const scheduleFrameFlush = () => {
262
+ if (frameFlushScheduled) return;
263
+ frameFlushScheduled = true;
264
+ const flush = () => {
265
+ if (!frameFlushScheduled) return;
266
+ frameFlushScheduled = false;
267
+ if (frameFlushTimeout !== undefined) {
268
+ clearTimeout(frameFlushTimeout);
269
+ frameFlushTimeout = undefined;
270
+ }
271
+ flushBuffer(new Date());
272
+ };
273
+ if (typeof requestAnimationFrame === "function") {
274
+ requestAnimationFrame(flush);
275
+ }
276
+ // requestAnimationFrame is suspended in background tabs. Keep a
277
+ // bounded fallback so buffered tokens still reach the DOM there.
278
+ frameFlushTimeout = setTimeout(flush, 300);
279
+ };
280
+
281
+ for await (const update of messageUpdatesIterator) {
282
+ if ($isAborted) {
283
+ messageUpdatesAbortController.abort();
284
+ return;
285
+ }
286
+
287
+ // Remove null characters added due to remote keylogging prevention
288
+ // See server code for more details
289
+ if (update.type === MessageUpdateType.Stream) {
290
+ update.token = update.token.replaceAll("\0", "");
291
+ }
292
+
293
+ const isKeepAlive =
294
+ update.type === MessageUpdateType.Status &&
295
+ update.status === MessageUpdateStatus.KeepAlive;
296
+
297
+ if (!isKeepAlive) {
298
+ if (update.type === MessageUpdateType.Stream) {
299
+ const existingUpdates = messageToWriteTo.updates ?? [];
300
+ const lastUpdate = existingUpdates.at(-1);
301
+ if (lastUpdate?.type === MessageUpdateType.Stream) {
302
+ // Create fresh objects/arrays so the UI reacts to merged tokens
303
+ const merged = {
304
+ ...lastUpdate,
305
+ token: (lastUpdate.token ?? "") + (update.token ?? ""),
306
+ };
307
+ messageToWriteTo.updates = [...existingUpdates.slice(0, -1), merged];
308
+ } else {
309
+ messageToWriteTo.updates = [...existingUpdates, update];
310
+ }
311
+ } else {
312
+ messageToWriteTo.updates = [...(messageToWriteTo.updates ?? []), update];
313
+ }
314
+ }
315
+ const currentTime = new Date();
316
+
317
+ // If we receive a non-stream update (e.g. tool/status/final answer),
318
+ // flush any buffered stream tokens so the UI doesn't appear to cut
319
+ // mid-sentence while tools are running or the final answer arrives.
320
+ if (update.type !== MessageUpdateType.Stream && buffer.length > 0) {
321
+ flushBuffer(currentTime);
322
+ }
323
+
324
+ if (update.type === MessageUpdateType.Stream) {
325
+ buffer += update.token;
326
+ if (streamingMode === "smooth") {
327
+ // Coalesce UI updates to animation frames for smooth mode.
328
+ scheduleFrameFlush();
329
+ } else if (
330
+ currentTime.getTime() - lastUpdateTime.getTime() >
331
+ updateDebouncer.maxUpdateTime
332
+ ) {
333
+ flushBuffer(currentTime);
334
+ }
335
+ pending = false;
336
+ } else if (update.type === MessageUpdateType.FinalAnswer) {
337
+ // Mirror server-side merge behavior so the UI reflects the
338
+ // final text once tools complete, while preserving any
339
+ // pre‑tool streamed content when appropriate.
340
+ const finalText = update.text ?? "";
341
+ const isInterrupted = update.interrupted === true;
342
+ const hadTools =
343
+ messageToWriteTo.updates?.some((u) => u.type === MessageUpdateType.Tool) ?? false;
344
+
345
+ if (isInterrupted) {
346
+ // Preserve streamed content on abort. If we never streamed, fall back to finalText.
347
+ if (!messageToWriteTo.content) {
348
+ messageToWriteTo.content = finalText;
349
+ }
350
+ } else if (hadTools) {
351
+ const existing = messageToWriteTo.content;
352
+ const trimmedExistingSuffix = existing.replace(/\s+$/, "");
353
+ const trimmedFinalPrefix = finalText.replace(/^\s+/, "");
354
+ const alreadyStreamed =
355
+ finalText &&
356
+ (existing.endsWith(finalText) ||
357
+ (trimmedFinalPrefix.length > 0 &&
358
+ trimmedExistingSuffix.endsWith(trimmedFinalPrefix)));
359
+
360
+ if (existing && existing.length > 0) {
361
+ if (alreadyStreamed) {
362
+ // A. Already streamed the same final text; keep as-is.
363
+ messageToWriteTo.content = existing;
364
+ } else if (
365
+ finalText &&
366
+ (finalText.startsWith(existing) ||
367
+ (trimmedExistingSuffix.length > 0 &&
368
+ trimmedFinalPrefix.startsWith(trimmedExistingSuffix)))
369
+ ) {
370
+ // B. Final text already includes streamed prefix; use it verbatim.
371
+ messageToWriteTo.content = finalText;
372
+ } else {
373
+ // C. Merge with a paragraph break for readability.
374
+ const needsGap = !/\n\n$/.test(existing) && !/^\n/.test(finalText ?? "");
375
+ messageToWriteTo.content = existing + (needsGap ? "\n\n" : "") + finalText;
376
+ }
377
+ } else {
378
+ messageToWriteTo.content = finalText;
379
+ }
380
+ } else {
381
+ // No tools: final answer replaces streamed content so
382
+ // the provider's final text is authoritative.
383
+ messageToWriteTo.content = finalText;
384
+ }
385
+ } else if (
386
+ update.type === MessageUpdateType.Status &&
387
+ update.status === MessageUpdateStatus.Error
388
+ ) {
389
+ // Check if this is a 402 payment required error
390
+ if (update.statusCode === 402) {
391
+ showSubscribeModal = true;
392
+ } else {
393
+ $error = update.message ?? "An error has occurred";
394
+ }
395
+ } else if (update.type === MessageUpdateType.Title) {
396
+ const convInData = conversations.find(({ id }) => id === page.params.id);
397
+ if (convInData) {
398
+ convInData.title = update.title;
399
+
400
+ $titleUpdate = {
401
+ title: update.title,
402
+ convId,
403
+ };
404
+ }
405
+ } else if (update.type === MessageUpdateType.File) {
406
+ messageToWriteTo.files = [
407
+ ...(messageToWriteTo.files ?? []),
408
+ { type: "hash", value: update.sha, mime: update.mime, name: update.name },
409
+ ];
410
+ } else if (update.type === MessageUpdateType.RouterMetadata) {
411
+ // Update router metadata immediately when received
412
+ messageToWriteTo.routerMetadata = {
413
+ route: update.route,
414
+ model: update.model,
415
+ };
416
+ } else if (update.type === MessageUpdateType.AutopilotStep) {
417
+ // Track autopilot step progress — stored on updates array for UI rendering
418
+ }
419
+ }
420
+
421
+ if (buffer.length > 0) {
422
+ flushBuffer(new Date());
423
+ }
424
+ } catch (err) {
425
+ if (err instanceof Error && err.message.includes("overloaded")) {
426
+ $error = "Too much traffic, please try again.";
427
+ } else if (err instanceof Error && err.message.includes("429")) {
428
+ $error = ERROR_MESSAGES.rateLimited;
429
+ } else if (err instanceof Error) {
430
+ $error = err.message;
431
+ } else {
432
+ $error = ERROR_MESSAGES.default;
433
+ }
434
+ console.error(err);
435
+ } finally {
436
+ $loading = false;
437
+ pending = false;
438
+ await invalidateAll();
439
+ }
440
+ }
441
+
442
+ async function stopGeneration() {
443
+ stopRequested = true;
444
+ $isAborted = true;
445
+ $loading = false;
446
+
447
+ const sendStopRequest = async () => {
448
+ const response = await fetch(`${base}/conversation/${page.params.id}/stop-generating`, {
449
+ method: "POST",
450
+ });
451
+ if (!response.ok) {
452
+ throw new Error(`Stop request failed: ${response.status}`);
453
+ }
454
+ };
455
+
456
+ try {
457
+ await sendStopRequest();
458
+ } catch (firstErr) {
459
+ try {
460
+ await new Promise((resolve) => setTimeout(resolve, 300));
461
+ await sendStopRequest();
462
+ } catch (retryErr) {
463
+ console.error("Failed to stop generation", firstErr, retryErr);
464
+ $error = "Failed to stop generation. Please try again.";
465
+ }
466
+ }
467
+ }
468
+
469
+ function handleKeydown(event: KeyboardEvent) {
470
+ // Stop generation on ESC key when loading
471
+ if (event.key === "Escape" && $loading) {
472
+ event.preventDefault();
473
+ stopGeneration();
474
+ }
475
+ }
476
+
477
+ onMount(async () => {
478
+ if ($pendingMessage) {
479
+ files = $pendingMessage.files;
480
+ await writeMessage({ prompt: $pendingMessage.content });
481
+ $pendingMessage = undefined;
482
+ }
483
+
484
+ const streaming = isConversationGenerationActive(messages);
485
+ if (streaming) {
486
+ addBackgroundGeneration({ id: convId, startedAt: Date.now() });
487
+ $loading = true;
488
+ }
489
+ });
490
+
491
+ async function onMessage(content: string) {
492
+ await writeMessage({ prompt: content });
493
+ }
494
+
495
+ async function onRetry(payload: { id: Message["id"]; content?: string }) {
496
+ if (requireAuthUser()) return;
497
+
498
+ const lastMsgId = payload.id;
499
+ messagesPath = createMessagesPath(messages, lastMsgId);
500
+
501
+ await writeMessage({
502
+ prompt: payload.content,
503
+ messageId: payload.id,
504
+ isRetry: true,
505
+ });
506
+ }
507
+
508
+ async function onShowAlternateMsg(payload: { id: Message["id"] }) {
509
+ const msgId = payload.id;
510
+ messagesPath = createMessagesPath(messages, msgId);
511
+ }
512
+
513
+ const settings = useSettingsStore();
514
+ let messages = $state(data.messages);
515
+ $effect(() => {
516
+ messages = data.messages;
517
+ });
518
+
519
+ $effect(() => {
520
+ page.params.id;
521
+ stopRequested = false;
522
+ });
523
+
524
+ $effect(() => {
525
+ const streaming = isConversationGenerationActive(messages);
526
+ if (stopRequested) {
527
+ $loading = false;
528
+ } else if (streaming) {
529
+ $loading = true;
530
+ } else if (!pending) {
531
+ $loading = false;
532
+ }
533
+
534
+ if (!streaming && browser) {
535
+ removeBackgroundGeneration(convId);
536
+ }
537
+ });
538
+
539
+ // create a linear list of `messagesPath` from `messages` that is a tree of threaded messages
540
+ let messagesPath = $derived(createMessagesPath(messages));
541
+ let messagesAlternatives = $derived(createMessagesAlternatives(messages));
542
+
543
+ $effect(() => {
544
+ if (browser && messagesPath.at(-1)?.id) {
545
+ localStorage.setItem("leafId", messagesPath.at(-1)?.id as string);
546
+ }
547
+ });
548
+
549
+ beforeNavigate((navigation) => {
550
+ if (!page.params.id) return;
551
+
552
+ const navigatingAway =
553
+ navigation.to?.route.id !== page.route.id || navigation.to?.params?.id !== page.params.id;
554
+
555
+ if ($loading && navigatingAway) {
556
+ addBackgroundGeneration({ id: page.params.id, startedAt: Date.now() });
557
+ }
558
+
559
+ $isAborted = true;
560
+ $loading = false;
561
+ });
562
+
563
+ let title = $derived.by(() => {
564
+ const rawTitle = conversations.find((conv) => conv.id === page.params.id)?.title ?? data.title;
565
+ return rawTitle ? rawTitle.charAt(0).toUpperCase() + rawTitle.slice(1) : rawTitle;
566
+ });
567
+ </script>
568
+
569
+ <svelte:window onkeydown={handleKeydown} />
570
+
571
+ <svelte:head>
572
+ <title>{title}</title>
573
+ </svelte:head>
574
+
575
+ <ChatWindow
576
+ loading={$loading}
577
+ {pending}
578
+ messages={messagesPath as Message[]}
579
+ {messagesAlternatives}
580
+ shared={data.shared}
581
+ preprompt={data.preprompt}
582
+ bind:files
583
+ onmessage={onMessage}
584
+ onretry={onRetry}
585
+ onshowAlternateMsg={onShowAlternateMsg}
586
+ onstop={stopGeneration}
587
+ models={data.models}
588
+ currentModel={findCurrentModel(data.models, data.oldModels, data.model)}
589
+ />
590
+
591
+ {#if showSubscribeModal}
592
+ <SubscribeModal close={() => (showSubscribeModal = false)} />
593
+ {/if}