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,211 +1,211 @@
|
|
|
1
|
-
import type { Sharp } from "sharp";
|
|
2
|
-
import sharp from "sharp";
|
|
3
|
-
import type { MessageFile } from "$lib/types/Message";
|
|
4
|
-
import { z, type util } from "zod";
|
|
5
|
-
|
|
6
|
-
export interface ImageProcessorOptions<TMimeType extends string = string> {
|
|
7
|
-
supportedMimeTypes: TMimeType[];
|
|
8
|
-
preferredMimeType: TMimeType;
|
|
9
|
-
maxSizeInMB: number;
|
|
10
|
-
maxWidth: number;
|
|
11
|
-
maxHeight: number;
|
|
12
|
-
}
|
|
13
|
-
export type ImageProcessor<TMimeType extends string = string> = (file: MessageFile) => Promise<{
|
|
14
|
-
image: Buffer;
|
|
15
|
-
mime: TMimeType;
|
|
16
|
-
}>;
|
|
17
|
-
|
|
18
|
-
export function createImageProcessorOptionsValidator<TMimeType extends string = string>(
|
|
19
|
-
defaults: ImageProcessorOptions<TMimeType>
|
|
20
|
-
) {
|
|
21
|
-
return z
|
|
22
|
-
.object({
|
|
23
|
-
supportedMimeTypes: z
|
|
24
|
-
.array(
|
|
25
|
-
z.enum<string, [TMimeType, ...TMimeType[]]>([
|
|
26
|
-
defaults.supportedMimeTypes[0],
|
|
27
|
-
...defaults.supportedMimeTypes.slice(1),
|
|
28
|
-
])
|
|
29
|
-
)
|
|
30
|
-
.default(defaults.supportedMimeTypes),
|
|
31
|
-
preferredMimeType: z
|
|
32
|
-
.enum([defaults.supportedMimeTypes[0], ...defaults.supportedMimeTypes.slice(1)])
|
|
33
|
-
.default(defaults.preferredMimeType as util.noUndefined<TMimeType>),
|
|
34
|
-
maxSizeInMB: z.number().positive().default(defaults.maxSizeInMB),
|
|
35
|
-
maxWidth: z.number().int().positive().default(defaults.maxWidth),
|
|
36
|
-
maxHeight: z.number().int().positive().default(defaults.maxHeight),
|
|
37
|
-
})
|
|
38
|
-
.default(defaults);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function makeImageProcessor<TMimeType extends string = string>(
|
|
42
|
-
options: ImageProcessorOptions<TMimeType>
|
|
43
|
-
): ImageProcessor<TMimeType> {
|
|
44
|
-
return async (file) => {
|
|
45
|
-
const { supportedMimeTypes, preferredMimeType, maxSizeInMB, maxWidth, maxHeight } = options;
|
|
46
|
-
const { mime, value } = file;
|
|
47
|
-
|
|
48
|
-
const buffer = Buffer.from(value, "base64");
|
|
49
|
-
let sharpInst = sharp(buffer);
|
|
50
|
-
|
|
51
|
-
const metadata = await sharpInst.metadata();
|
|
52
|
-
if (!metadata) throw Error("Failed to read image metadata");
|
|
53
|
-
const { width, height } = metadata;
|
|
54
|
-
if (width === undefined || height === undefined) throw Error("Failed to read image size");
|
|
55
|
-
|
|
56
|
-
const tooLargeInSize = width > maxWidth || height > maxHeight;
|
|
57
|
-
const tooLargeInBytes = buffer.byteLength > maxSizeInMB * 1000 * 1000;
|
|
58
|
-
|
|
59
|
-
const outputMime = chooseMimeType(supportedMimeTypes, preferredMimeType, mime, {
|
|
60
|
-
preferSizeReduction: tooLargeInBytes,
|
|
61
|
-
});
|
|
62
|
-
|
|
63
|
-
// Resize if necessary
|
|
64
|
-
if (tooLargeInSize || tooLargeInBytes) {
|
|
65
|
-
const size = chooseImageSize({
|
|
66
|
-
mime: outputMime,
|
|
67
|
-
width,
|
|
68
|
-
height,
|
|
69
|
-
maxWidth,
|
|
70
|
-
maxHeight,
|
|
71
|
-
maxSizeInMB,
|
|
72
|
-
});
|
|
73
|
-
if (size.width !== width || size.height !== height) {
|
|
74
|
-
sharpInst = resizeImage(sharpInst, size.width, size.height);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
// Convert format if necessary
|
|
79
|
-
// We always want to convert the image when the file was too large in bytes
|
|
80
|
-
// so we can guarantee that ideal options are used, which are expected when
|
|
81
|
-
// choosing the image size
|
|
82
|
-
if (outputMime !== mime || tooLargeInBytes) {
|
|
83
|
-
sharpInst = convertImage(sharpInst, outputMime);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const processedImage = await sharpInst.toBuffer();
|
|
87
|
-
return { image: processedImage, mime: outputMime };
|
|
88
|
-
};
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
const outputFormats = ["png", "jpeg", "webp", "avif", "tiff", "gif"] as const;
|
|
92
|
-
type OutputImgFormat = (typeof outputFormats)[number];
|
|
93
|
-
const isOutputFormat = (format: string): format is (typeof outputFormats)[number] =>
|
|
94
|
-
outputFormats.includes(format as OutputImgFormat);
|
|
95
|
-
|
|
96
|
-
export function convertImage(sharpInst: Sharp, outputMime: string): Sharp {
|
|
97
|
-
const [type, format] = outputMime.split("/");
|
|
98
|
-
if (type !== "image") throw Error(`Requested non-image mime type: ${outputMime}`);
|
|
99
|
-
if (!isOutputFormat(format)) {
|
|
100
|
-
throw Error(`Requested to convert to an unsupported format: ${format}`);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
return sharpInst[format]();
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// heic/heif requires proprietary license
|
|
107
|
-
// TODO: blocking heif may be incorrect considering it also supports av1, so we should instead
|
|
108
|
-
// detect the compression method used via sharp().metadata().compression
|
|
109
|
-
// TODO: consider what to do about animated formats: apng, gif, animated webp, ...
|
|
110
|
-
const blocklistedMimes = ["image/heic", "image/heif"];
|
|
111
|
-
|
|
112
|
-
/** Sorted from largest to smallest */
|
|
113
|
-
const mimesBySizeDesc = [
|
|
114
|
-
"image/png",
|
|
115
|
-
"image/tiff",
|
|
116
|
-
"image/gif",
|
|
117
|
-
"image/jpeg",
|
|
118
|
-
"image/webp",
|
|
119
|
-
"image/avif",
|
|
120
|
-
];
|
|
121
|
-
|
|
122
|
-
/**
|
|
123
|
-
* Defaults to preferred format or uses existing mime if supported
|
|
124
|
-
* When preferSizeReduction is true, it will choose the smallest format that is supported
|
|
125
|
-
**/
|
|
126
|
-
function chooseMimeType<T extends readonly string[]>(
|
|
127
|
-
supportedMimes: T,
|
|
128
|
-
preferredMime: string,
|
|
129
|
-
mime: string,
|
|
130
|
-
{ preferSizeReduction }: { preferSizeReduction: boolean }
|
|
131
|
-
): T[number] {
|
|
132
|
-
if (!supportedMimes.includes(preferredMime)) {
|
|
133
|
-
const supportedMimesStr = supportedMimes.join(", ");
|
|
134
|
-
throw Error(
|
|
135
|
-
`Preferred format "${preferredMime}" not found in supported mimes: ${supportedMimesStr}`
|
|
136
|
-
);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
const [type] = mime.split("/");
|
|
140
|
-
if (type !== "image") throw Error(`Received non-image mime type: ${mime}`);
|
|
141
|
-
|
|
142
|
-
if (supportedMimes.includes(mime) && !preferSizeReduction) return mime;
|
|
143
|
-
|
|
144
|
-
if (blocklistedMimes.includes(mime)) throw Error(`Received blocklisted mime type: ${mime}`);
|
|
145
|
-
|
|
146
|
-
const smallestMime = mimesBySizeDesc.findLast((m) => supportedMimes.includes(m));
|
|
147
|
-
return smallestMime ?? preferredMime;
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
interface ImageSizeOptions {
|
|
151
|
-
mime: string;
|
|
152
|
-
width: number;
|
|
153
|
-
height: number;
|
|
154
|
-
maxWidth: number;
|
|
155
|
-
maxHeight: number;
|
|
156
|
-
maxSizeInMB: number;
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/** Resizes the image to fit within the specified size in MB by guessing the output size */
|
|
160
|
-
export function chooseImageSize({
|
|
161
|
-
mime,
|
|
162
|
-
width,
|
|
163
|
-
height,
|
|
164
|
-
maxWidth,
|
|
165
|
-
maxHeight,
|
|
166
|
-
maxSizeInMB,
|
|
167
|
-
}: ImageSizeOptions): { width: number; height: number } {
|
|
168
|
-
const biggestDiscrepency = Math.max(1, width / maxWidth, height / maxHeight);
|
|
169
|
-
|
|
170
|
-
let selectedWidth = Math.ceil(width / biggestDiscrepency);
|
|
171
|
-
let selectedHeight = Math.ceil(height / biggestDiscrepency);
|
|
172
|
-
|
|
173
|
-
do {
|
|
174
|
-
const estimatedSize = estimateImageSizeInBytes(mime, selectedWidth, selectedHeight);
|
|
175
|
-
if (estimatedSize < maxSizeInMB * 1024 * 1024) {
|
|
176
|
-
return { width: selectedWidth, height: selectedHeight };
|
|
177
|
-
}
|
|
178
|
-
selectedWidth = Math.floor(selectedWidth / 1.1);
|
|
179
|
-
selectedHeight = Math.floor(selectedHeight / 1.1);
|
|
180
|
-
} while (selectedWidth > 1 && selectedHeight > 1);
|
|
181
|
-
|
|
182
|
-
throw Error(`Failed to resize image to fit within ${maxSizeInMB}MB`);
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
const mimeToCompressionRatio: Record<string, number> = {
|
|
186
|
-
"image/png": 1 / 2,
|
|
187
|
-
"image/jpeg": 1 / 10,
|
|
188
|
-
"image/webp": 1 / 4,
|
|
189
|
-
"image/avif": 1 / 5,
|
|
190
|
-
"image/tiff": 1,
|
|
191
|
-
"image/gif": 1 / 5,
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Guesses the side of an image in MB based on its format and dimensions
|
|
196
|
-
* Should guess the worst case
|
|
197
|
-
**/
|
|
198
|
-
function estimateImageSizeInBytes(mime: string, width: number, height: number): number {
|
|
199
|
-
const compressionRatio = mimeToCompressionRatio[mime];
|
|
200
|
-
if (!compressionRatio) throw Error(`Unsupported image format: ${mime}`);
|
|
201
|
-
|
|
202
|
-
const bitsPerPixel = 32; // Assuming 32-bit color depth for 8-bit R G B A
|
|
203
|
-
const bytesPerPixel = bitsPerPixel / 8;
|
|
204
|
-
const uncompressedSize = width * height * bytesPerPixel;
|
|
205
|
-
|
|
206
|
-
return uncompressedSize * compressionRatio;
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export function resizeImage(sharpInst: Sharp, maxWidth: number, maxHeight: number): Sharp {
|
|
210
|
-
return sharpInst.resize({ width: maxWidth, height: maxHeight, fit: "inside" });
|
|
211
|
-
}
|
|
1
|
+
import type { Sharp } from "sharp";
|
|
2
|
+
import sharp from "sharp";
|
|
3
|
+
import type { MessageFile } from "$lib/types/Message";
|
|
4
|
+
import { z, type util } from "zod";
|
|
5
|
+
|
|
6
|
+
export interface ImageProcessorOptions<TMimeType extends string = string> {
|
|
7
|
+
supportedMimeTypes: TMimeType[];
|
|
8
|
+
preferredMimeType: TMimeType;
|
|
9
|
+
maxSizeInMB: number;
|
|
10
|
+
maxWidth: number;
|
|
11
|
+
maxHeight: number;
|
|
12
|
+
}
|
|
13
|
+
export type ImageProcessor<TMimeType extends string = string> = (file: MessageFile) => Promise<{
|
|
14
|
+
image: Buffer;
|
|
15
|
+
mime: TMimeType;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
export function createImageProcessorOptionsValidator<TMimeType extends string = string>(
|
|
19
|
+
defaults: ImageProcessorOptions<TMimeType>
|
|
20
|
+
) {
|
|
21
|
+
return z
|
|
22
|
+
.object({
|
|
23
|
+
supportedMimeTypes: z
|
|
24
|
+
.array(
|
|
25
|
+
z.enum<string, [TMimeType, ...TMimeType[]]>([
|
|
26
|
+
defaults.supportedMimeTypes[0],
|
|
27
|
+
...defaults.supportedMimeTypes.slice(1),
|
|
28
|
+
])
|
|
29
|
+
)
|
|
30
|
+
.default(defaults.supportedMimeTypes),
|
|
31
|
+
preferredMimeType: z
|
|
32
|
+
.enum([defaults.supportedMimeTypes[0], ...defaults.supportedMimeTypes.slice(1)])
|
|
33
|
+
.default(defaults.preferredMimeType as util.noUndefined<TMimeType>),
|
|
34
|
+
maxSizeInMB: z.number().positive().default(defaults.maxSizeInMB),
|
|
35
|
+
maxWidth: z.number().int().positive().default(defaults.maxWidth),
|
|
36
|
+
maxHeight: z.number().int().positive().default(defaults.maxHeight),
|
|
37
|
+
})
|
|
38
|
+
.default(defaults);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function makeImageProcessor<TMimeType extends string = string>(
|
|
42
|
+
options: ImageProcessorOptions<TMimeType>
|
|
43
|
+
): ImageProcessor<TMimeType> {
|
|
44
|
+
return async (file) => {
|
|
45
|
+
const { supportedMimeTypes, preferredMimeType, maxSizeInMB, maxWidth, maxHeight } = options;
|
|
46
|
+
const { mime, value } = file;
|
|
47
|
+
|
|
48
|
+
const buffer = Buffer.from(value, "base64");
|
|
49
|
+
let sharpInst = sharp(buffer);
|
|
50
|
+
|
|
51
|
+
const metadata = await sharpInst.metadata();
|
|
52
|
+
if (!metadata) throw Error("Failed to read image metadata");
|
|
53
|
+
const { width, height } = metadata;
|
|
54
|
+
if (width === undefined || height === undefined) throw Error("Failed to read image size");
|
|
55
|
+
|
|
56
|
+
const tooLargeInSize = width > maxWidth || height > maxHeight;
|
|
57
|
+
const tooLargeInBytes = buffer.byteLength > maxSizeInMB * 1000 * 1000;
|
|
58
|
+
|
|
59
|
+
const outputMime = chooseMimeType(supportedMimeTypes, preferredMimeType, mime, {
|
|
60
|
+
preferSizeReduction: tooLargeInBytes,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// Resize if necessary
|
|
64
|
+
if (tooLargeInSize || tooLargeInBytes) {
|
|
65
|
+
const size = chooseImageSize({
|
|
66
|
+
mime: outputMime,
|
|
67
|
+
width,
|
|
68
|
+
height,
|
|
69
|
+
maxWidth,
|
|
70
|
+
maxHeight,
|
|
71
|
+
maxSizeInMB,
|
|
72
|
+
});
|
|
73
|
+
if (size.width !== width || size.height !== height) {
|
|
74
|
+
sharpInst = resizeImage(sharpInst, size.width, size.height);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Convert format if necessary
|
|
79
|
+
// We always want to convert the image when the file was too large in bytes
|
|
80
|
+
// so we can guarantee that ideal options are used, which are expected when
|
|
81
|
+
// choosing the image size
|
|
82
|
+
if (outputMime !== mime || tooLargeInBytes) {
|
|
83
|
+
sharpInst = convertImage(sharpInst, outputMime);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const processedImage = await sharpInst.toBuffer();
|
|
87
|
+
return { image: processedImage, mime: outputMime };
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const outputFormats = ["png", "jpeg", "webp", "avif", "tiff", "gif"] as const;
|
|
92
|
+
type OutputImgFormat = (typeof outputFormats)[number];
|
|
93
|
+
const isOutputFormat = (format: string): format is (typeof outputFormats)[number] =>
|
|
94
|
+
outputFormats.includes(format as OutputImgFormat);
|
|
95
|
+
|
|
96
|
+
export function convertImage(sharpInst: Sharp, outputMime: string): Sharp {
|
|
97
|
+
const [type, format] = outputMime.split("/");
|
|
98
|
+
if (type !== "image") throw Error(`Requested non-image mime type: ${outputMime}`);
|
|
99
|
+
if (!isOutputFormat(format)) {
|
|
100
|
+
throw Error(`Requested to convert to an unsupported format: ${format}`);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return sharpInst[format]();
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// heic/heif requires proprietary license
|
|
107
|
+
// TODO: blocking heif may be incorrect considering it also supports av1, so we should instead
|
|
108
|
+
// detect the compression method used via sharp().metadata().compression
|
|
109
|
+
// TODO: consider what to do about animated formats: apng, gif, animated webp, ...
|
|
110
|
+
const blocklistedMimes = ["image/heic", "image/heif"];
|
|
111
|
+
|
|
112
|
+
/** Sorted from largest to smallest */
|
|
113
|
+
const mimesBySizeDesc = [
|
|
114
|
+
"image/png",
|
|
115
|
+
"image/tiff",
|
|
116
|
+
"image/gif",
|
|
117
|
+
"image/jpeg",
|
|
118
|
+
"image/webp",
|
|
119
|
+
"image/avif",
|
|
120
|
+
];
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* Defaults to preferred format or uses existing mime if supported
|
|
124
|
+
* When preferSizeReduction is true, it will choose the smallest format that is supported
|
|
125
|
+
**/
|
|
126
|
+
function chooseMimeType<T extends readonly string[]>(
|
|
127
|
+
supportedMimes: T,
|
|
128
|
+
preferredMime: string,
|
|
129
|
+
mime: string,
|
|
130
|
+
{ preferSizeReduction }: { preferSizeReduction: boolean }
|
|
131
|
+
): T[number] {
|
|
132
|
+
if (!supportedMimes.includes(preferredMime)) {
|
|
133
|
+
const supportedMimesStr = supportedMimes.join(", ");
|
|
134
|
+
throw Error(
|
|
135
|
+
`Preferred format "${preferredMime}" not found in supported mimes: ${supportedMimesStr}`
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
const [type] = mime.split("/");
|
|
140
|
+
if (type !== "image") throw Error(`Received non-image mime type: ${mime}`);
|
|
141
|
+
|
|
142
|
+
if (supportedMimes.includes(mime) && !preferSizeReduction) return mime;
|
|
143
|
+
|
|
144
|
+
if (blocklistedMimes.includes(mime)) throw Error(`Received blocklisted mime type: ${mime}`);
|
|
145
|
+
|
|
146
|
+
const smallestMime = mimesBySizeDesc.findLast((m) => supportedMimes.includes(m));
|
|
147
|
+
return smallestMime ?? preferredMime;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
interface ImageSizeOptions {
|
|
151
|
+
mime: string;
|
|
152
|
+
width: number;
|
|
153
|
+
height: number;
|
|
154
|
+
maxWidth: number;
|
|
155
|
+
maxHeight: number;
|
|
156
|
+
maxSizeInMB: number;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/** Resizes the image to fit within the specified size in MB by guessing the output size */
|
|
160
|
+
export function chooseImageSize({
|
|
161
|
+
mime,
|
|
162
|
+
width,
|
|
163
|
+
height,
|
|
164
|
+
maxWidth,
|
|
165
|
+
maxHeight,
|
|
166
|
+
maxSizeInMB,
|
|
167
|
+
}: ImageSizeOptions): { width: number; height: number } {
|
|
168
|
+
const biggestDiscrepency = Math.max(1, width / maxWidth, height / maxHeight);
|
|
169
|
+
|
|
170
|
+
let selectedWidth = Math.ceil(width / biggestDiscrepency);
|
|
171
|
+
let selectedHeight = Math.ceil(height / biggestDiscrepency);
|
|
172
|
+
|
|
173
|
+
do {
|
|
174
|
+
const estimatedSize = estimateImageSizeInBytes(mime, selectedWidth, selectedHeight);
|
|
175
|
+
if (estimatedSize < maxSizeInMB * 1024 * 1024) {
|
|
176
|
+
return { width: selectedWidth, height: selectedHeight };
|
|
177
|
+
}
|
|
178
|
+
selectedWidth = Math.floor(selectedWidth / 1.1);
|
|
179
|
+
selectedHeight = Math.floor(selectedHeight / 1.1);
|
|
180
|
+
} while (selectedWidth > 1 && selectedHeight > 1);
|
|
181
|
+
|
|
182
|
+
throw Error(`Failed to resize image to fit within ${maxSizeInMB}MB`);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
const mimeToCompressionRatio: Record<string, number> = {
|
|
186
|
+
"image/png": 1 / 2,
|
|
187
|
+
"image/jpeg": 1 / 10,
|
|
188
|
+
"image/webp": 1 / 4,
|
|
189
|
+
"image/avif": 1 / 5,
|
|
190
|
+
"image/tiff": 1,
|
|
191
|
+
"image/gif": 1 / 5,
|
|
192
|
+
};
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Guesses the side of an image in MB based on its format and dimensions
|
|
196
|
+
* Should guess the worst case
|
|
197
|
+
**/
|
|
198
|
+
function estimateImageSizeInBytes(mime: string, width: number, height: number): number {
|
|
199
|
+
const compressionRatio = mimeToCompressionRatio[mime];
|
|
200
|
+
if (!compressionRatio) throw Error(`Unsupported image format: ${mime}`);
|
|
201
|
+
|
|
202
|
+
const bitsPerPixel = 32; // Assuming 32-bit color depth for 8-bit R G B A
|
|
203
|
+
const bytesPerPixel = bitsPerPixel / 8;
|
|
204
|
+
const uncompressedSize = width * height * bytesPerPixel;
|
|
205
|
+
|
|
206
|
+
return uncompressedSize * compressionRatio;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function resizeImage(sharpInst: Sharp, maxWidth: number, maxHeight: number): Sharp {
|
|
210
|
+
return sharpInst.resize({ width: maxWidth, height: maxHeight, fit: "inside" });
|
|
211
|
+
}
|