ruflo 3.10.36 → 3.10.38

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