ruflo 3.7.0-alpha.11 → 3.7.0-alpha.13
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.
- package/README.md +393 -393
- package/bin/ruflo.js +57 -57
- package/package.json +1 -1
- package/src/chat-ui/Dockerfile +25 -25
- package/src/chat-ui/patch-mcp-url-safety.sh +28 -28
- package/src/config/config.example.json +76 -76
- package/src/mcp-bridge/Dockerfile +45 -45
- package/src/mcp-bridge/index.js +1668 -1668
- package/src/mcp-bridge/mcp-stdio-kernel.js +159 -159
- package/src/mcp-bridge/package.json +17 -17
- package/src/mcp-bridge/test-harness.js +470 -470
- package/src/nginx/Dockerfile +10 -10
- package/src/nginx/nginx.conf +67 -67
- package/src/nginx/static/favicon-dark.svg +4 -4
- package/src/nginx/static/favicon.svg +4 -4
- package/src/nginx/static/icon.svg +5 -5
- package/src/nginx/static/logo.svg +9 -9
- package/src/nginx/static/manifest.json +22 -22
- package/src/nginx/static/welcome.js +184 -184
- package/src/ruvocal/.claude/skills/add-model-descriptions/SKILL.md +73 -73
- package/src/ruvocal/.claude-flow/daemon-state.json +135 -0
- package/src/ruvocal/.claude-flow/data/pending-insights.jsonl +0 -0
- package/src/ruvocal/.claude-flow/data/ranked-context.json +5 -0
- package/src/ruvocal/.claude-flow/logs/daemon.log +31 -0
- package/src/ruvocal/.claude-flow/logs/headless/audit_1777949411822_juxau0_prompt.log +989 -0
- package/src/ruvocal/.claude-flow/logs/headless/audit_1777949411822_juxau0_result.log +67 -0
- package/src/ruvocal/.claude-flow/logs/headless/audit_1777950042278_jvj5xq_prompt.log +989 -0
- package/src/ruvocal/.claude-flow/logs/headless/audit_1777950042278_jvj5xq_result.log +93 -0
- package/src/ruvocal/.claude-flow/logs/headless/optimize_1777949531823_yt5yc2_prompt.log +1498 -0
- package/src/ruvocal/.claude-flow/logs/headless/optimize_1777949531823_yt5yc2_result.log +93 -0
- package/src/ruvocal/.claude-flow/logs/headless/testgaps_1777949771821_elw1j4_prompt.log +1498 -0
- package/src/ruvocal/.claude-flow/logs/headless/testgaps_1777949771821_elw1j4_result.log +100 -0
- package/src/ruvocal/.claude-flow/metrics/codebase-map.json +11 -0
- package/src/ruvocal/.claude-flow/metrics/consolidation.json +6 -0
- package/src/ruvocal/.claude-flow/neural/stats.json +6 -0
- package/src/ruvocal/.claude-flow/sessions/current.json +13 -0
- package/src/ruvocal/.devcontainer/Dockerfile +9 -9
- package/src/ruvocal/.devcontainer/devcontainer.json +36 -36
- package/src/ruvocal/.dockerignore +16 -16
- package/src/ruvocal/.eslintignore +13 -13
- package/src/ruvocal/.eslintrc.cjs +45 -45
- package/src/ruvocal/.gcloudignore +18 -18
- package/src/ruvocal/.github/ISSUE_TEMPLATE/bug-report--chat-ui-.md +43 -43
- package/src/ruvocal/.github/ISSUE_TEMPLATE/config-support.md +9 -9
- package/src/ruvocal/.github/ISSUE_TEMPLATE/feature-request--chat-ui-.md +17 -17
- package/src/ruvocal/.github/ISSUE_TEMPLATE/huggingchat.md +11 -11
- package/src/ruvocal/.github/release.yml +16 -16
- package/src/ruvocal/.github/workflows/build-docs.yml +18 -18
- package/src/ruvocal/.github/workflows/build-image.yml +142 -142
- package/src/ruvocal/.github/workflows/build-pr-docs.yml +20 -20
- package/src/ruvocal/.github/workflows/deploy-dev.yml +63 -63
- package/src/ruvocal/.github/workflows/deploy-prod.yml +78 -78
- package/src/ruvocal/.github/workflows/lint-and-test.yml +84 -84
- package/src/ruvocal/.github/workflows/slugify.yaml +72 -72
- package/src/ruvocal/.github/workflows/trufflehog.yml +17 -17
- package/src/ruvocal/.github/workflows/upload-pr-documentation.yml +16 -16
- package/src/ruvocal/.husky/lint-stage-config.js +4 -4
- package/src/ruvocal/.husky/pre-commit +2 -2
- package/src/ruvocal/.prettierignore +14 -14
- package/src/ruvocal/.prettierrc +7 -7
- package/src/ruvocal/.swarm/attestation.db +0 -0
- package/src/ruvocal/.swarm/hnsw.index +0 -0
- package/src/ruvocal/.swarm/hnsw.metadata.json +1 -0
- package/src/ruvocal/.swarm/memory.db +0 -0
- package/src/ruvocal/.swarm/schema.sql +305 -0
- package/src/ruvocal/CLAUDE.md +126 -126
- package/src/ruvocal/Dockerfile +96 -96
- package/src/ruvocal/LICENSE +202 -202
- package/src/ruvocal/PRIVACY.md +41 -41
- package/src/ruvocal/README.md +164 -164
- package/src/ruvocal/chart/Chart.yaml +5 -5
- package/src/ruvocal/chart/env/dev.yaml +260 -260
- package/src/ruvocal/chart/env/prod.yaml +273 -273
- package/src/ruvocal/chart/templates/_helpers.tpl +22 -22
- package/src/ruvocal/chart/templates/config.yaml +10 -10
- package/src/ruvocal/chart/templates/deployment.yaml +81 -81
- package/src/ruvocal/chart/templates/hpa.yaml +45 -45
- package/src/ruvocal/chart/templates/infisical.yaml +24 -24
- package/src/ruvocal/chart/templates/ingress-internal.yaml +32 -32
- package/src/ruvocal/chart/templates/ingress.yaml +32 -32
- package/src/ruvocal/chart/templates/network-policy.yaml +36 -36
- package/src/ruvocal/chart/templates/service-account.yaml +13 -13
- package/src/ruvocal/chart/templates/service-monitor.yaml +17 -17
- package/src/ruvocal/chart/templates/service.yaml +21 -21
- package/src/ruvocal/chart/values.yaml +73 -73
- package/src/ruvocal/cloudbuild.yaml +68 -68
- package/src/ruvocal/config/branding.env.example +19 -19
- package/src/ruvocal/docker-compose.yml +21 -21
- package/src/ruvocal/docs/adr/ADR-029-HUGGINGFACE-CHAT-UI-CLOUD-RUN.md +1236 -1236
- package/src/ruvocal/docs/adr/ADR-033-RUVECTOR-RUFLO-MCP-INTEGRATION.md +111 -111
- package/src/ruvocal/docs/adr/ADR-034-OPTIONAL-MCP-BACKENDS.md +117 -117
- package/src/ruvocal/docs/adr/ADR-035-MCP-TOOL-GROUPS.md +186 -186
- package/src/ruvocal/docs/adr/ADR-037-AUTOPILOT-CHAT-MODE.md +1500 -1500
- package/src/ruvocal/docs/adr/ADR-038-RUVOCAL-FORK.md +286 -286
- package/src/ruvocal/docs/source/_toctree.yml +30 -30
- package/src/ruvocal/docs/source/configuration/common-issues.md +38 -38
- package/src/ruvocal/docs/source/configuration/llm-router.md +105 -105
- package/src/ruvocal/docs/source/configuration/mcp-tools.md +84 -84
- package/src/ruvocal/docs/source/configuration/metrics.md +9 -9
- package/src/ruvocal/docs/source/configuration/open-id.md +57 -57
- package/src/ruvocal/docs/source/configuration/overview.md +89 -89
- package/src/ruvocal/docs/source/configuration/theming.md +20 -20
- package/src/ruvocal/docs/source/developing/architecture.md +48 -48
- package/src/ruvocal/docs/source/index.md +53 -53
- package/src/ruvocal/docs/source/installation/docker.md +43 -43
- package/src/ruvocal/docs/source/installation/helm.md +43 -43
- package/src/ruvocal/docs/source/installation/local.md +62 -62
- package/src/ruvocal/entrypoint.sh +18 -18
- package/src/ruvocal/mcp-bridge/Dockerfile +45 -45
- package/src/ruvocal/mcp-bridge/cloudbuild.yaml +49 -49
- package/src/ruvocal/mcp-bridge/index.js +1878 -1878
- package/src/ruvocal/mcp-bridge/mcp-stdio-kernel.js +159 -159
- package/src/ruvocal/mcp-bridge/package-lock.json +762 -762
- package/src/ruvocal/mcp-bridge/package.json +17 -17
- package/src/ruvocal/mcp-bridge/test-harness.js +470 -470
- package/src/ruvocal/package-lock.json +11741 -11741
- package/src/ruvocal/package.json +121 -121
- package/src/ruvocal/postcss.config.js +6 -6
- package/src/ruvocal/rvf.manifest.json +204 -204
- package/src/ruvocal/scripts/config.ts +64 -64
- package/src/ruvocal/scripts/generate-welcome.mjs +181 -181
- package/src/ruvocal/scripts/populate.ts +288 -288
- package/src/ruvocal/scripts/samples.txt +194 -194
- package/src/ruvocal/scripts/setups/vitest-setup-server.ts +44 -44
- package/src/ruvocal/scripts/updateLocalEnv.ts +48 -48
- package/src/ruvocal/src/ambient.d.ts +7 -7
- package/src/ruvocal/src/app.d.ts +29 -29
- package/src/ruvocal/src/app.html +53 -53
- package/src/ruvocal/src/hooks.server.ts +32 -32
- package/src/ruvocal/src/hooks.ts +6 -6
- package/src/ruvocal/src/lib/APIClient.ts +148 -148
- package/src/ruvocal/src/lib/actions/clickOutside.ts +18 -18
- package/src/ruvocal/src/lib/actions/snapScrollToBottom.ts +346 -346
- package/src/ruvocal/src/lib/buildPrompt.ts +33 -33
- package/src/ruvocal/src/lib/components/AnnouncementBanner.svelte +20 -20
- package/src/ruvocal/src/lib/components/BackgroundGenerationPoller.svelte +168 -168
- package/src/ruvocal/src/lib/components/CodeBlock.svelte +73 -73
- package/src/ruvocal/src/lib/components/CopyToClipBoardBtn.svelte +92 -92
- package/src/ruvocal/src/lib/components/DeleteConversationModal.svelte +75 -75
- package/src/ruvocal/src/lib/components/EditConversationModal.svelte +100 -100
- package/src/ruvocal/src/lib/components/ExpandNavigation.svelte +22 -22
- package/src/ruvocal/src/lib/components/FoundationBackground.svelte +242 -242
- package/src/ruvocal/src/lib/components/HoverTooltip.svelte +44 -44
- package/src/ruvocal/src/lib/components/HtmlPreviewModal.svelte +143 -143
- package/src/ruvocal/src/lib/components/InfiniteScroll.svelte +50 -50
- package/src/ruvocal/src/lib/components/MobileNav.svelte +300 -300
- package/src/ruvocal/src/lib/components/Modal.svelte +115 -115
- package/src/ruvocal/src/lib/components/ModelCardMetadata.svelte +71 -71
- package/src/ruvocal/src/lib/components/NavConversationItem.svelte +151 -151
- package/src/ruvocal/src/lib/components/NavMenu.svelte +313 -313
- package/src/ruvocal/src/lib/components/Pagination.svelte +97 -97
- package/src/ruvocal/src/lib/components/PaginationArrow.svelte +27 -27
- package/src/ruvocal/src/lib/components/Portal.svelte +24 -24
- package/src/ruvocal/src/lib/components/RetryBtn.svelte +18 -18
- package/src/ruvocal/src/lib/components/RuFloUniverse.svelte +185 -185
- package/src/ruvocal/src/lib/components/RufloHelpModal.svelte +411 -411
- package/src/ruvocal/src/lib/components/ScrollToBottomBtn.svelte +47 -47
- package/src/ruvocal/src/lib/components/ScrollToPreviousBtn.svelte +77 -77
- package/src/ruvocal/src/lib/components/ShareConversationModal.svelte +182 -182
- package/src/ruvocal/src/lib/components/StopGeneratingBtn.svelte +69 -69
- package/src/ruvocal/src/lib/components/SubscribeModal.svelte +87 -87
- package/src/ruvocal/src/lib/components/Switch.svelte +36 -36
- package/src/ruvocal/src/lib/components/SystemPromptModal.svelte +44 -44
- package/src/ruvocal/src/lib/components/Toast.svelte +27 -27
- package/src/ruvocal/src/lib/components/Tooltip.svelte +30 -30
- package/src/ruvocal/src/lib/components/WelcomeModal.svelte +46 -46
- package/src/ruvocal/src/lib/components/chat/Alternatives.svelte +77 -77
- package/src/ruvocal/src/lib/components/chat/BlockWrapper.svelte +72 -72
- package/src/ruvocal/src/lib/components/chat/ChatInput.svelte +490 -490
- package/src/ruvocal/src/lib/components/chat/ChatIntroduction.svelte +123 -123
- package/src/ruvocal/src/lib/components/chat/ChatMessage.svelte +548 -548
- package/src/ruvocal/src/lib/components/chat/ChatWindow.svelte +1057 -1057
- package/src/ruvocal/src/lib/components/chat/FileDropzone.svelte +92 -92
- package/src/ruvocal/src/lib/components/chat/ImageLightbox.svelte +66 -66
- package/src/ruvocal/src/lib/components/chat/MarkdownBlock.svelte +23 -23
- package/src/ruvocal/src/lib/components/chat/MarkdownRenderer.svelte +69 -69
- package/src/ruvocal/src/lib/components/chat/MarkdownRenderer.svelte.test.ts +58 -58
- package/src/ruvocal/src/lib/components/chat/MessageAvatar.svelte +103 -103
- package/src/ruvocal/src/lib/components/chat/ModelSwitch.svelte +64 -64
- package/src/ruvocal/src/lib/components/chat/OpenReasoningResults.svelte +81 -81
- package/src/ruvocal/src/lib/components/chat/TaskGroup.svelte +88 -88
- package/src/ruvocal/src/lib/components/chat/ToolUpdate.svelte +273 -273
- package/src/ruvocal/src/lib/components/chat/UploadedFile.svelte +253 -253
- package/src/ruvocal/src/lib/components/chat/UrlFetchModal.svelte +203 -203
- package/src/ruvocal/src/lib/components/chat/VoiceRecorder.svelte +214 -214
- package/src/ruvocal/src/lib/components/icons/IconBurger.svelte +20 -20
- package/src/ruvocal/src/lib/components/icons/IconCheap.svelte +20 -20
- package/src/ruvocal/src/lib/components/icons/IconChevron.svelte +24 -24
- package/src/ruvocal/src/lib/components/icons/IconDazzled.svelte +40 -40
- package/src/ruvocal/src/lib/components/icons/IconFast.svelte +20 -20
- package/src/ruvocal/src/lib/components/icons/IconLoading.svelte +22 -22
- package/src/ruvocal/src/lib/components/icons/IconMCP.svelte +28 -28
- package/src/ruvocal/src/lib/components/icons/IconMoon.svelte +21 -21
- package/src/ruvocal/src/lib/components/icons/IconNew.svelte +20 -20
- package/src/ruvocal/src/lib/components/icons/IconOmni.svelte +90 -90
- package/src/ruvocal/src/lib/components/icons/IconPaperclip.svelte +24 -24
- package/src/ruvocal/src/lib/components/icons/IconPro.svelte +37 -37
- package/src/ruvocal/src/lib/components/icons/IconShare.svelte +21 -21
- package/src/ruvocal/src/lib/components/icons/IconSun.svelte +93 -93
- package/src/ruvocal/src/lib/components/icons/Logo.svelte +68 -68
- package/src/ruvocal/src/lib/components/icons/LogoHuggingFaceBorderless.svelte +54 -54
- package/src/ruvocal/src/lib/components/mcp/AddServerForm.svelte +250 -250
- package/src/ruvocal/src/lib/components/mcp/MCPServerManager.svelte +185 -185
- package/src/ruvocal/src/lib/components/mcp/ServerCard.svelte +203 -203
- package/src/ruvocal/src/lib/components/players/AudioPlayer.svelte +82 -82
- package/src/ruvocal/src/lib/components/voice/AudioWaveform.svelte +96 -96
- package/src/ruvocal/src/lib/components/wasm/GalleryPanel.svelte +357 -357
- package/src/ruvocal/src/lib/constants/mcpExamples.ts +114 -114
- package/src/ruvocal/src/lib/constants/mime.ts +11 -11
- package/src/ruvocal/src/lib/constants/pagination.ts +1 -1
- package/src/ruvocal/src/lib/constants/publicSepToken.ts +1 -1
- package/src/ruvocal/src/lib/constants/routerExamples.ts +133 -133
- package/src/ruvocal/src/lib/constants/rvagentPresets.ts +206 -206
- package/src/ruvocal/src/lib/createShareLink.ts +27 -27
- package/src/ruvocal/src/lib/jobs/refresh-conversation-stats.ts +297 -297
- package/src/ruvocal/src/lib/migrations/lock.ts +56 -56
- package/src/ruvocal/src/lib/migrations/migrations.spec.ts +74 -74
- package/src/ruvocal/src/lib/migrations/migrations.ts +109 -109
- package/src/ruvocal/src/lib/migrations/routines/01-update-search-assistants.ts +50 -50
- package/src/ruvocal/src/lib/migrations/routines/02-update-assistants-models.ts +48 -48
- package/src/ruvocal/src/lib/migrations/routines/04-update-message-updates.ts +151 -151
- package/src/ruvocal/src/lib/migrations/routines/05-update-message-files.ts +56 -56
- package/src/ruvocal/src/lib/migrations/routines/06-trim-message-updates.ts +56 -56
- package/src/ruvocal/src/lib/migrations/routines/08-update-featured-to-review.ts +32 -32
- package/src/ruvocal/src/lib/migrations/routines/09-delete-empty-conversations.spec.ts +214 -214
- package/src/ruvocal/src/lib/migrations/routines/09-delete-empty-conversations.ts +88 -88
- package/src/ruvocal/src/lib/migrations/routines/10-update-reports-assistantid.ts +29 -29
- package/src/ruvocal/src/lib/migrations/routines/index.ts +15 -15
- package/src/ruvocal/src/lib/server/__tests__/conversation-stop-generating.spec.ts +103 -103
- package/src/ruvocal/src/lib/server/abortRegistry.ts +57 -57
- package/src/ruvocal/src/lib/server/abortedGenerations.ts +43 -43
- package/src/ruvocal/src/lib/server/adminToken.ts +62 -62
- package/src/ruvocal/src/lib/server/api/__tests__/conversations-id.spec.ts +296 -296
- package/src/ruvocal/src/lib/server/api/__tests__/conversations-message.spec.ts +216 -216
- package/src/ruvocal/src/lib/server/api/__tests__/conversations.spec.ts +235 -235
- package/src/ruvocal/src/lib/server/api/__tests__/misc.spec.ts +72 -72
- package/src/ruvocal/src/lib/server/api/__tests__/testHelpers.ts +86 -86
- package/src/ruvocal/src/lib/server/api/__tests__/user-reports.spec.ts +78 -78
- package/src/ruvocal/src/lib/server/api/__tests__/user.spec.ts +239 -239
- package/src/ruvocal/src/lib/server/api/types.ts +37 -37
- package/src/ruvocal/src/lib/server/api/utils/requireAuth.ts +22 -22
- package/src/ruvocal/src/lib/server/api/utils/resolveConversation.ts +69 -69
- package/src/ruvocal/src/lib/server/api/utils/resolveModel.ts +27 -27
- package/src/ruvocal/src/lib/server/api/utils/superjsonResponse.ts +15 -15
- package/src/ruvocal/src/lib/server/apiToken.ts +11 -11
- package/src/ruvocal/src/lib/server/auth.ts +554 -554
- package/src/ruvocal/src/lib/server/config.ts +187 -187
- package/src/ruvocal/src/lib/server/conversation.ts +83 -83
- package/src/ruvocal/src/lib/server/database/__tests__/rvf.spec.ts +709 -709
- package/src/ruvocal/src/lib/server/database/postgres.ts +700 -700
- package/src/ruvocal/src/lib/server/database/rvf.ts +1078 -1078
- package/src/ruvocal/src/lib/server/database.ts +145 -145
- package/src/ruvocal/src/lib/server/endpoints/document.ts +68 -68
- package/src/ruvocal/src/lib/server/endpoints/endpoints.ts +43 -43
- package/src/ruvocal/src/lib/server/endpoints/images.ts +211 -211
- package/src/ruvocal/src/lib/server/endpoints/openai/endpointOai.ts +266 -266
- package/src/ruvocal/src/lib/server/endpoints/openai/openAIChatToTextGenerationStream.ts +212 -212
- package/src/ruvocal/src/lib/server/endpoints/openai/openAICompletionToTextGenerationStream.ts +32 -32
- package/src/ruvocal/src/lib/server/endpoints/preprocessMessages.ts +61 -61
- package/src/ruvocal/src/lib/server/exitHandler.ts +59 -59
- package/src/ruvocal/src/lib/server/files/downloadFile.ts +34 -34
- package/src/ruvocal/src/lib/server/files/uploadFile.ts +29 -29
- package/src/ruvocal/src/lib/server/findRepoRoot.ts +13 -13
- package/src/ruvocal/src/lib/server/generateFromDefaultEndpoint.ts +46 -46
- package/src/ruvocal/src/lib/server/hooks/error.ts +37 -37
- package/src/ruvocal/src/lib/server/hooks/fetch.ts +22 -22
- package/src/ruvocal/src/lib/server/hooks/handle.ts +250 -250
- package/src/ruvocal/src/lib/server/hooks/init.ts +51 -51
- package/src/ruvocal/src/lib/server/isURLLocal.spec.ts +31 -31
- package/src/ruvocal/src/lib/server/isURLLocal.ts +74 -74
- package/src/ruvocal/src/lib/server/logger.ts +42 -42
- package/src/ruvocal/src/lib/server/mcp/clientPool.spec.ts +175 -175
- package/src/ruvocal/src/lib/server/mcp/hf.ts +32 -32
- package/src/ruvocal/src/lib/server/mcp/httpClient.ts +122 -122
- package/src/ruvocal/src/lib/server/mcp/registry.ts +76 -76
- package/src/ruvocal/src/lib/server/mcp/tools.ts +196 -196
- package/src/ruvocal/src/lib/server/metrics.ts +255 -255
- package/src/ruvocal/src/lib/server/models.ts +518 -518
- package/src/ruvocal/src/lib/server/requestContext.ts +55 -55
- package/src/ruvocal/src/lib/server/router/arch.ts +230 -230
- package/src/ruvocal/src/lib/server/router/endpoint.ts +316 -316
- package/src/ruvocal/src/lib/server/router/multimodal.ts +28 -28
- package/src/ruvocal/src/lib/server/router/policy.ts +49 -49
- package/src/ruvocal/src/lib/server/router/toolsRoute.ts +51 -51
- package/src/ruvocal/src/lib/server/router/types.ts +21 -21
- package/src/ruvocal/src/lib/server/sendSlack.ts +23 -23
- package/src/ruvocal/src/lib/server/textGeneration/generate.ts +258 -258
- package/src/ruvocal/src/lib/server/textGeneration/index.ts +96 -96
- package/src/ruvocal/src/lib/server/textGeneration/mcp/fileRefs.ts +155 -155
- package/src/ruvocal/src/lib/server/textGeneration/mcp/routerResolution.ts +108 -108
- package/src/ruvocal/src/lib/server/textGeneration/mcp/runMcpFlow.ts +831 -831
- package/src/ruvocal/src/lib/server/textGeneration/mcp/toolInvocation.ts +349 -349
- package/src/ruvocal/src/lib/server/textGeneration/mcp/wasmTools.test.ts +633 -633
- package/src/ruvocal/src/lib/server/textGeneration/reasoning.ts +23 -23
- package/src/ruvocal/src/lib/server/textGeneration/title.ts +83 -83
- package/src/ruvocal/src/lib/server/textGeneration/types.ts +28 -28
- package/src/ruvocal/src/lib/server/textGeneration/utils/prepareFiles.ts +88 -88
- package/src/ruvocal/src/lib/server/textGeneration/utils/routing.ts +21 -21
- package/src/ruvocal/src/lib/server/textGeneration/utils/toolPrompt.ts +49 -49
- package/src/ruvocal/src/lib/server/urlSafety.ts +77 -77
- package/src/ruvocal/src/lib/server/usageLimits.ts +30 -30
- package/src/ruvocal/src/lib/stores/autopilotStore.svelte.ts +175 -175
- package/src/ruvocal/src/lib/stores/backgroundGenerations.svelte.ts +32 -32
- package/src/ruvocal/src/lib/stores/backgroundGenerations.ts +1 -1
- package/src/ruvocal/src/lib/stores/errors.ts +9 -9
- package/src/ruvocal/src/lib/stores/isAborted.ts +3 -3
- package/src/ruvocal/src/lib/stores/isPro.ts +4 -4
- package/src/ruvocal/src/lib/stores/loading.ts +3 -3
- package/src/ruvocal/src/lib/stores/mcpServers.ts +534 -534
- package/src/ruvocal/src/lib/stores/pendingChatInput.ts +3 -3
- package/src/ruvocal/src/lib/stores/pendingMessage.ts +9 -9
- package/src/ruvocal/src/lib/stores/settings.ts +182 -182
- package/src/ruvocal/src/lib/stores/shareModal.ts +13 -13
- package/src/ruvocal/src/lib/stores/titleUpdate.ts +8 -8
- package/src/ruvocal/src/lib/stores/wasmMcp.ts +472 -472
- package/src/ruvocal/src/lib/switchTheme.ts +124 -124
- package/src/ruvocal/src/lib/types/AbortedGeneration.ts +8 -8
- package/src/ruvocal/src/lib/types/Assistant.ts +31 -31
- package/src/ruvocal/src/lib/types/AssistantStats.ts +11 -11
- package/src/ruvocal/src/lib/types/ConfigKey.ts +4 -4
- package/src/ruvocal/src/lib/types/ConvSidebar.ts +9 -9
- package/src/ruvocal/src/lib/types/Conversation.ts +27 -27
- package/src/ruvocal/src/lib/types/ConversationStats.ts +13 -13
- package/src/ruvocal/src/lib/types/Message.ts +41 -41
- package/src/ruvocal/src/lib/types/MessageEvent.ts +10 -10
- package/src/ruvocal/src/lib/types/MessageUpdate.ts +139 -139
- package/src/ruvocal/src/lib/types/MigrationResult.ts +7 -7
- package/src/ruvocal/src/lib/types/Model.ts +23 -23
- package/src/ruvocal/src/lib/types/Report.ts +12 -12
- package/src/ruvocal/src/lib/types/Review.ts +6 -6
- package/src/ruvocal/src/lib/types/Semaphore.ts +19 -19
- package/src/ruvocal/src/lib/types/Session.ts +22 -22
- package/src/ruvocal/src/lib/types/Settings.ts +93 -93
- package/src/ruvocal/src/lib/types/SharedConversation.ts +9 -9
- package/src/ruvocal/src/lib/types/Template.ts +6 -6
- package/src/ruvocal/src/lib/types/Timestamps.ts +4 -4
- package/src/ruvocal/src/lib/types/TokenCache.ts +6 -6
- package/src/ruvocal/src/lib/types/Tool.ts +77 -77
- package/src/ruvocal/src/lib/types/UrlDependency.ts +5 -5
- package/src/ruvocal/src/lib/types/User.ts +14 -14
- package/src/ruvocal/src/lib/utils/PublicConfig.svelte.ts +75 -75
- package/src/ruvocal/src/lib/utils/auth.ts +17 -17
- package/src/ruvocal/src/lib/utils/chunk.ts +33 -33
- package/src/ruvocal/src/lib/utils/cookiesAreEnabled.ts +13 -13
- package/src/ruvocal/src/lib/utils/debounce.ts +17 -17
- package/src/ruvocal/src/lib/utils/deepestChild.ts +6 -6
- package/src/ruvocal/src/lib/utils/favicon.ts +21 -21
- package/src/ruvocal/src/lib/utils/fetchJSON.ts +23 -23
- package/src/ruvocal/src/lib/utils/file2base64.ts +14 -14
- package/src/ruvocal/src/lib/utils/formatUserCount.ts +37 -37
- package/src/ruvocal/src/lib/utils/generationState.spec.ts +75 -75
- package/src/ruvocal/src/lib/utils/generationState.ts +26 -26
- package/src/ruvocal/src/lib/utils/getHref.ts +41 -41
- package/src/ruvocal/src/lib/utils/getReturnFromGenerator.ts +7 -7
- package/src/ruvocal/src/lib/utils/haptics.ts +64 -64
- package/src/ruvocal/src/lib/utils/hashConv.ts +12 -12
- package/src/ruvocal/src/lib/utils/hf.ts +17 -17
- package/src/ruvocal/src/lib/utils/isDesktop.ts +7 -7
- package/src/ruvocal/src/lib/utils/isUrl.ts +8 -8
- package/src/ruvocal/src/lib/utils/isVirtualKeyboard.ts +16 -16
- package/src/ruvocal/src/lib/utils/loadAttachmentsFromUrls.ts +115 -115
- package/src/ruvocal/src/lib/utils/marked.spec.ts +96 -96
- package/src/ruvocal/src/lib/utils/marked.ts +531 -531
- package/src/ruvocal/src/lib/utils/mcpValidation.ts +147 -147
- package/src/ruvocal/src/lib/utils/mergeAsyncGenerators.ts +38 -38
- package/src/ruvocal/src/lib/utils/messageUpdates.spec.ts +262 -262
- package/src/ruvocal/src/lib/utils/messageUpdates.ts +324 -324
- package/src/ruvocal/src/lib/utils/mime.ts +56 -56
- package/src/ruvocal/src/lib/utils/models.ts +14 -14
- package/src/ruvocal/src/lib/utils/parseBlocks.ts +120 -120
- package/src/ruvocal/src/lib/utils/parseIncompleteMarkdown.ts +644 -644
- package/src/ruvocal/src/lib/utils/parseStringToList.ts +10 -10
- package/src/ruvocal/src/lib/utils/randomUuid.ts +14 -14
- package/src/ruvocal/src/lib/utils/searchTokens.ts +33 -33
- package/src/ruvocal/src/lib/utils/sha256.ts +7 -7
- package/src/ruvocal/src/lib/utils/stringifyError.ts +12 -12
- package/src/ruvocal/src/lib/utils/sum.ts +3 -3
- package/src/ruvocal/src/lib/utils/template.spec.ts +59 -59
- package/src/ruvocal/src/lib/utils/template.ts +53 -53
- package/src/ruvocal/src/lib/utils/timeout.ts +9 -9
- package/src/ruvocal/src/lib/utils/toolProgress.spec.ts +46 -46
- package/src/ruvocal/src/lib/utils/toolProgress.ts +11 -11
- package/src/ruvocal/src/lib/utils/tree/addChildren.spec.ts +102 -102
- package/src/ruvocal/src/lib/utils/tree/addChildren.ts +48 -48
- package/src/ruvocal/src/lib/utils/tree/addSibling.spec.ts +81 -81
- package/src/ruvocal/src/lib/utils/tree/addSibling.ts +41 -41
- package/src/ruvocal/src/lib/utils/tree/buildSubtree.spec.ts +110 -110
- package/src/ruvocal/src/lib/utils/tree/buildSubtree.ts +24 -24
- package/src/ruvocal/src/lib/utils/tree/convertLegacyConversation.spec.ts +31 -31
- package/src/ruvocal/src/lib/utils/tree/convertLegacyConversation.ts +36 -36
- package/src/ruvocal/src/lib/utils/tree/isMessageId.spec.ts +15 -15
- package/src/ruvocal/src/lib/utils/tree/isMessageId.ts +5 -5
- package/src/ruvocal/src/lib/utils/tree/tree.d.ts +14 -14
- package/src/ruvocal/src/lib/utils/tree/treeHelpers.spec.ts +167 -167
- package/src/ruvocal/src/lib/utils/updates.ts +39 -39
- package/src/ruvocal/src/lib/utils/urlParams.ts +13 -13
- package/src/ruvocal/src/lib/wasm/idb.ts +438 -438
- package/src/ruvocal/src/lib/wasm/index.ts +1213 -1213
- package/src/ruvocal/src/lib/wasm/tests/wasm-capabilities.test.ts +565 -565
- package/src/ruvocal/src/lib/wasm/wasm.worker.ts +332 -332
- package/src/ruvocal/src/lib/wasm/workerClient.ts +166 -166
- package/src/ruvocal/src/lib/workers/autopilotWorker.ts +221 -221
- package/src/ruvocal/src/lib/workers/detailFetchWorker.ts +100 -100
- package/src/ruvocal/src/lib/workers/markdownWorker.ts +61 -61
- package/src/ruvocal/src/routes/+error.svelte +20 -20
- package/src/ruvocal/src/routes/+layout.svelte +324 -324
- package/src/ruvocal/src/routes/+layout.ts +91 -91
- package/src/ruvocal/src/routes/+page.svelte +168 -168
- package/src/ruvocal/src/routes/.well-known/oauth-cimd/+server.ts +37 -37
- package/src/ruvocal/src/routes/__debug/openai/+server.ts +21 -21
- package/src/ruvocal/src/routes/admin/export/+server.ts +159 -159
- package/src/ruvocal/src/routes/admin/stats/compute/+server.ts +16 -16
- package/src/ruvocal/src/routes/api/conversation/[id]/+server.ts +40 -40
- package/src/ruvocal/src/routes/api/conversation/[id]/message/[messageId]/+server.ts +42 -42
- package/src/ruvocal/src/routes/api/conversations/+server.ts +48 -48
- package/src/ruvocal/src/routes/api/fetch-url/+server.ts +147 -147
- package/src/ruvocal/src/routes/api/mcp/health/+server.ts +292 -292
- package/src/ruvocal/src/routes/api/mcp/servers/+server.ts +32 -32
- package/src/ruvocal/src/routes/api/models/+server.ts +25 -25
- package/src/ruvocal/src/routes/api/transcribe/+server.ts +104 -104
- package/src/ruvocal/src/routes/api/user/+server.ts +15 -15
- package/src/ruvocal/src/routes/api/user/validate-token/+server.ts +20 -20
- package/src/ruvocal/src/routes/api/v2/conversations/+server.ts +48 -48
- package/src/ruvocal/src/routes/api/v2/conversations/[id]/+server.ts +94 -94
- package/src/ruvocal/src/routes/api/v2/conversations/[id]/message/[messageId]/+server.ts +43 -43
- package/src/ruvocal/src/routes/api/v2/conversations/import-share/+server.ts +23 -23
- package/src/ruvocal/src/routes/api/v2/debug/config/+server.ts +16 -16
- package/src/ruvocal/src/routes/api/v2/debug/refresh/+server.ts +30 -30
- package/src/ruvocal/src/routes/api/v2/export/+server.ts +196 -196
- package/src/ruvocal/src/routes/api/v2/feature-flags/+server.ts +14 -14
- package/src/ruvocal/src/routes/api/v2/models/+server.ts +38 -38
- package/src/ruvocal/src/routes/api/v2/models/[namespace]/+server.ts +8 -8
- package/src/ruvocal/src/routes/api/v2/models/[namespace]/[model]/+server.ts +8 -8
- package/src/ruvocal/src/routes/api/v2/models/[namespace]/[model]/subscribe/+server.ts +28 -28
- package/src/ruvocal/src/routes/api/v2/models/[namespace]/subscribe/+server.ts +28 -28
- package/src/ruvocal/src/routes/api/v2/models/old/+server.ts +7 -7
- package/src/ruvocal/src/routes/api/v2/models/refresh/+server.ts +33 -33
- package/src/ruvocal/src/routes/api/v2/public-config/+server.ts +7 -7
- package/src/ruvocal/src/routes/api/v2/user/+server.ts +17 -17
- package/src/ruvocal/src/routes/api/v2/user/billing-orgs/+server.ts +73 -73
- package/src/ruvocal/src/routes/api/v2/user/reports/+server.ts +17 -17
- package/src/ruvocal/src/routes/api/v2/user/settings/+server.ts +110 -110
- package/src/ruvocal/src/routes/conversation/+server.ts +115 -115
- package/src/ruvocal/src/routes/conversation/[id]/+page.svelte +586 -586
- package/src/ruvocal/src/routes/conversation/[id]/+page.ts +60 -60
- package/src/ruvocal/src/routes/conversation/[id]/+server.ts +740 -740
- package/src/ruvocal/src/routes/conversation/[id]/message/[messageId]/prompt/+server.ts +66 -66
- package/src/ruvocal/src/routes/conversation/[id]/share/+server.ts +69 -69
- package/src/ruvocal/src/routes/conversation/[id]/stop-generating/+server.ts +35 -35
- package/src/ruvocal/src/routes/healthcheck/+server.ts +3 -3
- package/src/ruvocal/src/routes/login/+server.ts +5 -5
- package/src/ruvocal/src/routes/login/callback/+server.ts +103 -103
- package/src/ruvocal/src/routes/login/callback/updateUser.spec.ts +157 -157
- package/src/ruvocal/src/routes/login/callback/updateUser.ts +215 -215
- package/src/ruvocal/src/routes/logout/+server.ts +18 -18
- package/src/ruvocal/src/routes/metrics/+server.ts +18 -18
- package/src/ruvocal/src/routes/models/+page.svelte +233 -233
- package/src/ruvocal/src/routes/models/[...model]/+page.svelte +161 -161
- package/src/ruvocal/src/routes/models/[...model]/+page.ts +14 -14
- package/src/ruvocal/src/routes/models/[...model]/thumbnail.png/+server.ts +64 -64
- package/src/ruvocal/src/routes/models/[...model]/thumbnail.png/ModelThumbnail.svelte +28 -28
- package/src/ruvocal/src/routes/privacy/+page.svelte +11 -11
- package/src/ruvocal/src/routes/r/[id]/+page.ts +34 -34
- package/src/ruvocal/src/routes/settings/(nav)/+layout.svelte +282 -282
- package/src/ruvocal/src/routes/settings/(nav)/+layout.ts +1 -1
- package/src/ruvocal/src/routes/settings/(nav)/+server.ts +59 -59
- package/src/ruvocal/src/routes/settings/(nav)/[...model]/+page.svelte +464 -464
- package/src/ruvocal/src/routes/settings/(nav)/[...model]/+page.ts +14 -14
- package/src/ruvocal/src/routes/settings/(nav)/application/+page.svelte +362 -362
- package/src/ruvocal/src/routes/settings/+layout.svelte +40 -40
- package/src/ruvocal/src/styles/highlight-js.css +195 -195
- package/src/ruvocal/src/styles/main.css +144 -144
- package/src/ruvocal/static/chatui/favicon-dark.svg +3 -3
- package/src/ruvocal/static/chatui/favicon-dev.svg +3 -3
- package/src/ruvocal/static/chatui/favicon.svg +3 -3
- package/src/ruvocal/static/chatui/icon.svg +3 -3
- package/src/ruvocal/static/chatui/logo.svg +7 -7
- package/src/ruvocal/static/chatui/manifest.json +54 -54
- package/src/ruvocal/static/chatui/welcome.js +184 -184
- package/src/ruvocal/static/huggingchat/favicon-dark.svg +4 -4
- package/src/ruvocal/static/huggingchat/favicon-dev.svg +4 -4
- package/src/ruvocal/static/huggingchat/favicon.svg +4 -4
- package/src/ruvocal/static/huggingchat/fulltext-logo.svg +1 -1
- package/src/ruvocal/static/huggingchat/icon.svg +4 -4
- package/src/ruvocal/static/huggingchat/logo.svg +4 -4
- package/src/ruvocal/static/huggingchat/manifest.json +54 -54
- package/src/ruvocal/static/huggingchat/routes.chat.json +226 -226
- package/src/ruvocal/static/robots.txt +10 -10
- package/src/ruvocal/static/wasm/rvagent_wasm.js +1539 -1539
- package/src/ruvocal/stub/@reflink/reflink/package.json +5 -5
- package/src/ruvocal/svelte.config.js +53 -53
- package/src/ruvocal/tailwind.config.cjs +30 -30
- package/src/ruvocal/tsconfig.json +19 -19
- package/src/ruvocal/vite.config.ts +87 -87
- package/src/scripts/deploy.sh +116 -116
- package/src/scripts/generate-config.js +245 -245
- package/src/scripts/generate-welcome.js +187 -187
- package/src/scripts/package-rvf.sh +116 -116
|
@@ -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}
|