ruflo 3.42.3 → 3.42.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +422 -422
- package/bin/ruflo.js +77 -77
- package/package.json +117 -117
- 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 +47 -47
- package/src/mcp-bridge/index.js +1780 -1780
- 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/mcp-bridge/test-runtime-security.mjs +208 -208
- package/src/mcp-bridge/test-security-lock.js +121 -121
- 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/.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/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 +47 -47
- package/src/ruvocal/mcp-bridge/cloudbuild.yaml +49 -49
- package/src/ruvocal/mcp-bridge/index.js +1995 -1995
- 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 +286 -286
- package/src/ruvocal/src/lib/utils/messageUpdates.ts +332 -332
- 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 +593 -593
- 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,490 +1,490 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import { onMount, tick } from "svelte";
|
|
3
|
-
|
|
4
|
-
import { afterNavigate } from "$app/navigation";
|
|
5
|
-
|
|
6
|
-
import { DropdownMenu } from "bits-ui";
|
|
7
|
-
import IconPlus from "~icons/lucide/plus";
|
|
8
|
-
import CarbonImage from "~icons/carbon/image";
|
|
9
|
-
import CarbonDocument from "~icons/carbon/document";
|
|
10
|
-
import CarbonUpload from "~icons/carbon/upload";
|
|
11
|
-
import CarbonLink from "~icons/carbon/link";
|
|
12
|
-
import CarbonChevronRight from "~icons/carbon/chevron-right";
|
|
13
|
-
import CarbonClose from "~icons/carbon/close";
|
|
14
|
-
import UrlFetchModal from "./UrlFetchModal.svelte";
|
|
15
|
-
import { TEXT_MIME_ALLOWLIST, IMAGE_MIME_ALLOWLIST_DEFAULT } from "$lib/constants/mime";
|
|
16
|
-
import MCPServerManager from "$lib/components/mcp/MCPServerManager.svelte";
|
|
17
|
-
import IconMCP from "$lib/components/icons/IconMCP.svelte";
|
|
18
|
-
|
|
19
|
-
import { isVirtualKeyboard } from "$lib/utils/isVirtualKeyboard";
|
|
20
|
-
import { requireAuthUser } from "$lib/utils/auth";
|
|
21
|
-
import {
|
|
22
|
-
enabledServersCount,
|
|
23
|
-
selectedServerIds,
|
|
24
|
-
allMcpServers,
|
|
25
|
-
toggleServer,
|
|
26
|
-
disableAllServers,
|
|
27
|
-
} from "$lib/stores/mcpServers";
|
|
28
|
-
import { getMcpServerFaviconUrl } from "$lib/utils/favicon";
|
|
29
|
-
import { page } from "$app/state";
|
|
30
|
-
|
|
31
|
-
interface Props {
|
|
32
|
-
files?: File[];
|
|
33
|
-
mimeTypes?: string[];
|
|
34
|
-
value?: string;
|
|
35
|
-
placeholder?: string;
|
|
36
|
-
loading?: boolean;
|
|
37
|
-
disabled?: boolean;
|
|
38
|
-
// tools removed
|
|
39
|
-
modelIsMultimodal?: boolean;
|
|
40
|
-
// Whether the currently selected model supports tool calling (incl. overrides)
|
|
41
|
-
modelSupportsTools?: boolean;
|
|
42
|
-
children?: import("svelte").Snippet;
|
|
43
|
-
onPaste?: (e: ClipboardEvent) => void;
|
|
44
|
-
focused?: boolean;
|
|
45
|
-
onsubmit?: () => void;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
let {
|
|
49
|
-
files = $bindable([]),
|
|
50
|
-
mimeTypes = [],
|
|
51
|
-
value = $bindable(""),
|
|
52
|
-
placeholder = "",
|
|
53
|
-
loading = false,
|
|
54
|
-
disabled = false,
|
|
55
|
-
|
|
56
|
-
modelIsMultimodal = false,
|
|
57
|
-
modelSupportsTools = true,
|
|
58
|
-
children,
|
|
59
|
-
onPaste,
|
|
60
|
-
focused = $bindable(false),
|
|
61
|
-
onsubmit,
|
|
62
|
-
}: Props = $props();
|
|
63
|
-
|
|
64
|
-
const onFileChange = async (e: Event) => {
|
|
65
|
-
if (!e.target) return;
|
|
66
|
-
const target = e.target as HTMLInputElement;
|
|
67
|
-
const selected = Array.from(target.files ?? []);
|
|
68
|
-
if (selected.length === 0) return;
|
|
69
|
-
files = [...files, ...selected];
|
|
70
|
-
await tick();
|
|
71
|
-
void focusTextarea();
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
let textareaElement: HTMLTextAreaElement | undefined = $state();
|
|
75
|
-
let isCompositionOn = $state(false);
|
|
76
|
-
let blurTimeout: ReturnType<typeof setTimeout> | null = $state(null);
|
|
77
|
-
|
|
78
|
-
let fileInputEl: HTMLInputElement | undefined = $state();
|
|
79
|
-
let isUrlModalOpen = $state(false);
|
|
80
|
-
let isMcpManagerOpen = $state(false);
|
|
81
|
-
let isDropdownOpen = $state(false);
|
|
82
|
-
|
|
83
|
-
function openPickerWithAccept(accept: string) {
|
|
84
|
-
if (!fileInputEl) return;
|
|
85
|
-
const allAccept = mimeTypes.join(",");
|
|
86
|
-
fileInputEl.setAttribute("accept", accept);
|
|
87
|
-
fileInputEl.click();
|
|
88
|
-
queueMicrotask(() => fileInputEl?.setAttribute("accept", allAccept));
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
function openFilePickerText() {
|
|
92
|
-
const textAccept =
|
|
93
|
-
mimeTypes.filter((m) => !(m === "image/*" || m.startsWith("image/"))).join(",") ||
|
|
94
|
-
TEXT_MIME_ALLOWLIST.join(",");
|
|
95
|
-
openPickerWithAccept(textAccept);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
function openFilePickerImage() {
|
|
99
|
-
const imageAccept =
|
|
100
|
-
mimeTypes.filter((m) => m === "image/*" || m.startsWith("image/")).join(",") ||
|
|
101
|
-
IMAGE_MIME_ALLOWLIST_DEFAULT.join(",");
|
|
102
|
-
openPickerWithAccept(imageAccept);
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const waitForAnimationFrame = () =>
|
|
106
|
-
typeof requestAnimationFrame === "function"
|
|
107
|
-
? new Promise<void>((resolve) => {
|
|
108
|
-
requestAnimationFrame(() => resolve());
|
|
109
|
-
})
|
|
110
|
-
: Promise.resolve();
|
|
111
|
-
|
|
112
|
-
async function focusTextarea() {
|
|
113
|
-
if (page.data.shared && page.data.loginEnabled && !page.data.user) return;
|
|
114
|
-
if (!textareaElement || textareaElement.disabled || isVirtualKeyboard()) return;
|
|
115
|
-
if (typeof document !== "undefined" && document.activeElement === textareaElement) return;
|
|
116
|
-
|
|
117
|
-
await tick();
|
|
118
|
-
|
|
119
|
-
if (typeof requestAnimationFrame === "function") {
|
|
120
|
-
await waitForAnimationFrame();
|
|
121
|
-
await waitForAnimationFrame();
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
if (!textareaElement || textareaElement.disabled || isVirtualKeyboard()) return;
|
|
125
|
-
|
|
126
|
-
try {
|
|
127
|
-
textareaElement.focus({ preventScroll: true });
|
|
128
|
-
} catch {
|
|
129
|
-
textareaElement.focus();
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Retry only when focus failed due to #app being inert (modal closing transition)
|
|
133
|
-
if (
|
|
134
|
-
typeof document !== "undefined" &&
|
|
135
|
-
document.activeElement !== textareaElement &&
|
|
136
|
-
document.getElementById("app")?.hasAttribute("inert")
|
|
137
|
-
) {
|
|
138
|
-
setTimeout(() => {
|
|
139
|
-
if (!textareaElement || textareaElement.disabled || isVirtualKeyboard()) return;
|
|
140
|
-
if (document.activeElement === textareaElement) return;
|
|
141
|
-
try {
|
|
142
|
-
textareaElement.focus({ preventScroll: true });
|
|
143
|
-
} catch {
|
|
144
|
-
textareaElement.focus();
|
|
145
|
-
}
|
|
146
|
-
}, 350);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
function handleFetchedFiles(newFiles: File[]) {
|
|
151
|
-
if (!newFiles?.length) return;
|
|
152
|
-
files = [...files, ...newFiles];
|
|
153
|
-
queueMicrotask(async () => {
|
|
154
|
-
await tick();
|
|
155
|
-
void focusTextarea();
|
|
156
|
-
});
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
onMount(() => {
|
|
160
|
-
void focusTextarea();
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
afterNavigate(() => {
|
|
164
|
-
void focusTextarea();
|
|
165
|
-
});
|
|
166
|
-
|
|
167
|
-
function adjustTextareaHeight() {
|
|
168
|
-
if (!textareaElement) {
|
|
169
|
-
return;
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
textareaElement.style.height = "auto";
|
|
173
|
-
textareaElement.style.height = `${textareaElement.scrollHeight}px`;
|
|
174
|
-
|
|
175
|
-
if (textareaElement.selectionStart === textareaElement.value.length) {
|
|
176
|
-
textareaElement.scrollTop = textareaElement.scrollHeight;
|
|
177
|
-
}
|
|
178
|
-
}
|
|
179
|
-
|
|
180
|
-
$effect(() => {
|
|
181
|
-
if (!textareaElement) return;
|
|
182
|
-
void value;
|
|
183
|
-
adjustTextareaHeight();
|
|
184
|
-
});
|
|
185
|
-
|
|
186
|
-
function handleKeydown(event: KeyboardEvent) {
|
|
187
|
-
if (
|
|
188
|
-
event.key === "Enter" &&
|
|
189
|
-
!event.shiftKey &&
|
|
190
|
-
!isCompositionOn &&
|
|
191
|
-
!isVirtualKeyboard() &&
|
|
192
|
-
value.trim() !== ""
|
|
193
|
-
) {
|
|
194
|
-
event.preventDefault();
|
|
195
|
-
tick();
|
|
196
|
-
onsubmit?.();
|
|
197
|
-
}
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
function handleFocus() {
|
|
201
|
-
if (requireAuthUser()) {
|
|
202
|
-
return;
|
|
203
|
-
}
|
|
204
|
-
if (blurTimeout) {
|
|
205
|
-
clearTimeout(blurTimeout);
|
|
206
|
-
blurTimeout = null;
|
|
207
|
-
}
|
|
208
|
-
focused = true;
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
function handleBlur() {
|
|
212
|
-
if (!isVirtualKeyboard()) {
|
|
213
|
-
focused = false;
|
|
214
|
-
return;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
if (blurTimeout) {
|
|
218
|
-
clearTimeout(blurTimeout);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
blurTimeout = setTimeout(() => {
|
|
222
|
-
blurTimeout = null;
|
|
223
|
-
focused = false;
|
|
224
|
-
});
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Show file upload when any mime is allowed (text always; images if multimodal)
|
|
228
|
-
let showFileUpload = $derived(mimeTypes.length > 0);
|
|
229
|
-
let showNoTools = $derived(!showFileUpload);
|
|
230
|
-
let selectedServers = $derived(
|
|
231
|
-
$allMcpServers.filter((server) => $selectedServerIds.has(server.id))
|
|
232
|
-
);
|
|
233
|
-
</script>
|
|
234
|
-
|
|
235
|
-
<div class="flex min-h-full flex-1 flex-col" onpaste={onPaste}>
|
|
236
|
-
<textarea
|
|
237
|
-
rows="1"
|
|
238
|
-
tabindex="0"
|
|
239
|
-
inputmode="text"
|
|
240
|
-
class="scrollbar-custom max-h-[4lh] w-full resize-none overflow-y-auto overflow-x-hidden border-0 bg-transparent px-2.5 py-2.5 outline-none focus:ring-0 focus-visible:ring-0 sm:px-3 md:max-h-[8lh]"
|
|
241
|
-
class:text-gray-400={disabled}
|
|
242
|
-
bind:value
|
|
243
|
-
bind:this={textareaElement}
|
|
244
|
-
onkeydown={handleKeydown}
|
|
245
|
-
oncompositionstart={() => (isCompositionOn = true)}
|
|
246
|
-
oncompositionend={() => (isCompositionOn = false)}
|
|
247
|
-
{placeholder}
|
|
248
|
-
{disabled}
|
|
249
|
-
onfocus={handleFocus}
|
|
250
|
-
onblur={handleBlur}
|
|
251
|
-
onbeforeinput={requireAuthUser}
|
|
252
|
-
></textarea>
|
|
253
|
-
|
|
254
|
-
{#if !showNoTools}
|
|
255
|
-
<div
|
|
256
|
-
class={[
|
|
257
|
-
"scrollbar-custom -ml-0.5 flex max-w-[calc(100%-40px)] flex-wrap items-center justify-start gap-2.5 px-3 pb-2.5 pt-1.5 text-gray-500 dark:text-gray-400 max-md:flex-nowrap max-md:overflow-x-auto sm:gap-2",
|
|
258
|
-
]}
|
|
259
|
-
>
|
|
260
|
-
{#if showFileUpload}
|
|
261
|
-
<div class="flex items-center">
|
|
262
|
-
<input
|
|
263
|
-
bind:this={fileInputEl}
|
|
264
|
-
disabled={loading}
|
|
265
|
-
class="absolute hidden size-0"
|
|
266
|
-
aria-label="Upload file"
|
|
267
|
-
type="file"
|
|
268
|
-
multiple
|
|
269
|
-
onchange={onFileChange}
|
|
270
|
-
onclick={(e) => {
|
|
271
|
-
if (requireAuthUser()) {
|
|
272
|
-
e.preventDefault();
|
|
273
|
-
}
|
|
274
|
-
}}
|
|
275
|
-
accept={mimeTypes.join(",")}
|
|
276
|
-
/>
|
|
277
|
-
|
|
278
|
-
<DropdownMenu.Root
|
|
279
|
-
bind:open={isDropdownOpen}
|
|
280
|
-
onOpenChange={(open) => {
|
|
281
|
-
if (open && requireAuthUser()) {
|
|
282
|
-
isDropdownOpen = false;
|
|
283
|
-
return;
|
|
284
|
-
}
|
|
285
|
-
isDropdownOpen = open;
|
|
286
|
-
}}
|
|
287
|
-
>
|
|
288
|
-
<DropdownMenu.Trigger
|
|
289
|
-
class="btn size-8 rounded-full border bg-white text-black shadow transition-none enabled:hover:bg-white enabled:hover:shadow-inner dark:border-transparent dark:bg-gray-600/50 dark:text-white dark:hover:enabled:bg-gray-600 sm:size-7"
|
|
290
|
-
disabled={loading}
|
|
291
|
-
aria-label="Add attachment"
|
|
292
|
-
>
|
|
293
|
-
<IconPlus class="text-base sm:text-sm" />
|
|
294
|
-
</DropdownMenu.Trigger>
|
|
295
|
-
<DropdownMenu.Portal>
|
|
296
|
-
<DropdownMenu.Content
|
|
297
|
-
class="z-50 rounded-xl border border-gray-200 bg-white/95 p-1 text-gray-800 shadow-lg backdrop-blur dark:border-gray-700/60 dark:bg-gray-800/95 dark:text-gray-100"
|
|
298
|
-
side="top"
|
|
299
|
-
sideOffset={8}
|
|
300
|
-
align="start"
|
|
301
|
-
trapFocus={false}
|
|
302
|
-
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
303
|
-
interactOutsideBehavior="defer-otherwise-close"
|
|
304
|
-
>
|
|
305
|
-
{#if modelIsMultimodal}
|
|
306
|
-
<DropdownMenu.Item
|
|
307
|
-
class="flex h-9 select-none items-center gap-1 rounded-md px-2 text-sm text-gray-700 data-[highlighted]:bg-gray-100 focus-visible:outline-none dark:text-gray-200 dark:data-[highlighted]:bg-white/10 sm:h-8"
|
|
308
|
-
onSelect={() => openFilePickerImage()}
|
|
309
|
-
>
|
|
310
|
-
<CarbonImage class="size-4 opacity-90 dark:opacity-80" />
|
|
311
|
-
Add image(s)
|
|
312
|
-
</DropdownMenu.Item>
|
|
313
|
-
{/if}
|
|
314
|
-
|
|
315
|
-
<DropdownMenu.Sub>
|
|
316
|
-
<DropdownMenu.SubTrigger
|
|
317
|
-
class="flex h-9 select-none items-center gap-1 rounded-md px-2 text-sm text-gray-700 data-[highlighted]:bg-gray-100 data-[state=open]:bg-gray-100 focus-visible:outline-none dark:text-gray-200 dark:data-[highlighted]:bg-white/10 dark:data-[state=open]:bg-white/10 sm:h-8"
|
|
318
|
-
>
|
|
319
|
-
<div class="flex items-center gap-1">
|
|
320
|
-
<CarbonDocument class="size-4 opacity-90 dark:opacity-80" />
|
|
321
|
-
Add text file
|
|
322
|
-
</div>
|
|
323
|
-
<div class="ml-auto flex items-center">
|
|
324
|
-
<CarbonChevronRight class="size-4 opacity-70 dark:opacity-80" />
|
|
325
|
-
</div>
|
|
326
|
-
</DropdownMenu.SubTrigger>
|
|
327
|
-
<DropdownMenu.SubContent
|
|
328
|
-
class="z-50 rounded-xl border border-gray-200 bg-white/95 p-1 text-gray-800 shadow-lg backdrop-blur dark:border-gray-700/60 dark:bg-gray-800/95 dark:text-gray-100"
|
|
329
|
-
sideOffset={10}
|
|
330
|
-
trapFocus={false}
|
|
331
|
-
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
332
|
-
interactOutsideBehavior="defer-otherwise-close"
|
|
333
|
-
>
|
|
334
|
-
<DropdownMenu.Item
|
|
335
|
-
class="flex h-9 select-none items-center gap-1 rounded-md px-2 text-sm text-gray-700 data-[highlighted]:bg-gray-100 focus-visible:outline-none dark:text-gray-200 dark:data-[highlighted]:bg-white/10 sm:h-8"
|
|
336
|
-
onSelect={() => openFilePickerText()}
|
|
337
|
-
>
|
|
338
|
-
<CarbonUpload class="size-4 opacity-90 dark:opacity-80" />
|
|
339
|
-
Upload from device
|
|
340
|
-
</DropdownMenu.Item>
|
|
341
|
-
<DropdownMenu.Item
|
|
342
|
-
class="flex h-9 select-none items-center gap-1 rounded-md px-2 text-sm text-gray-700 data-[highlighted]:bg-gray-100 focus-visible:outline-none dark:text-gray-200 dark:data-[highlighted]:bg-white/10 sm:h-8"
|
|
343
|
-
onSelect={() => (isUrlModalOpen = true)}
|
|
344
|
-
>
|
|
345
|
-
<CarbonLink class="size-4 opacity-90 dark:opacity-80" />
|
|
346
|
-
Fetch from URL
|
|
347
|
-
</DropdownMenu.Item>
|
|
348
|
-
</DropdownMenu.SubContent>
|
|
349
|
-
</DropdownMenu.Sub>
|
|
350
|
-
|
|
351
|
-
<!-- MCP Servers submenu -->
|
|
352
|
-
<DropdownMenu.Sub>
|
|
353
|
-
<DropdownMenu.SubTrigger
|
|
354
|
-
class="flex h-9 select-none items-center gap-1 rounded-md px-2 text-sm text-gray-700 data-[highlighted]:bg-gray-100 data-[state=open]:bg-gray-100 focus-visible:outline-none dark:text-gray-200 dark:data-[highlighted]:bg-white/10 dark:data-[state=open]:bg-white/10 sm:h-8"
|
|
355
|
-
>
|
|
356
|
-
<div class="flex items-center gap-1">
|
|
357
|
-
<IconMCP classNames="size-4 opacity-90 dark:opacity-80" />
|
|
358
|
-
MCP Servers
|
|
359
|
-
</div>
|
|
360
|
-
<div class="ml-auto flex items-center">
|
|
361
|
-
<CarbonChevronRight class="size-4 opacity-70 dark:opacity-80" />
|
|
362
|
-
</div>
|
|
363
|
-
</DropdownMenu.SubTrigger>
|
|
364
|
-
<DropdownMenu.SubContent
|
|
365
|
-
class="z-50 rounded-xl border border-gray-200 bg-white/95 p-1 text-gray-800 shadow-lg backdrop-blur dark:border-gray-700/60 dark:bg-gray-800/95 dark:text-gray-100"
|
|
366
|
-
sideOffset={10}
|
|
367
|
-
trapFocus={false}
|
|
368
|
-
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
369
|
-
interactOutsideBehavior="defer-otherwise-close"
|
|
370
|
-
>
|
|
371
|
-
{#each $allMcpServers as server (server.id)}
|
|
372
|
-
<DropdownMenu.CheckboxItem
|
|
373
|
-
checked={$selectedServerIds.has(server.id)}
|
|
374
|
-
onCheckedChange={() => toggleServer(server.id)}
|
|
375
|
-
closeOnSelect={false}
|
|
376
|
-
class="flex h-9 select-none items-center gap-2 rounded-md px-2 text-sm leading-none text-gray-800 data-[highlighted]:bg-gray-100 focus-visible:outline-none dark:text-gray-100 dark:data-[highlighted]:bg-white/10"
|
|
377
|
-
>
|
|
378
|
-
{#snippet children({ checked })}
|
|
379
|
-
<img
|
|
380
|
-
src={getMcpServerFaviconUrl(server.url)}
|
|
381
|
-
alt=""
|
|
382
|
-
class="size-4 flex-shrink-0 rounded"
|
|
383
|
-
/>
|
|
384
|
-
<span class="max-w-52 truncate py-1">{server.name}</span>
|
|
385
|
-
<div class="ml-auto flex items-center">
|
|
386
|
-
<!-- Toggle visual -->
|
|
387
|
-
<span
|
|
388
|
-
class={[
|
|
389
|
-
"relative mt-px flex h-4 w-7 items-center self-center rounded-full transition-colors",
|
|
390
|
-
checked ? "bg-blue-600/80" : "bg-gray-300 dark:bg-gray-700",
|
|
391
|
-
]}
|
|
392
|
-
>
|
|
393
|
-
<span
|
|
394
|
-
class={[
|
|
395
|
-
"block size-3 translate-x-0.5 rounded-full bg-white shadow transition-transform",
|
|
396
|
-
checked ? "translate-x-[14px]" : "translate-x-0.5",
|
|
397
|
-
]}
|
|
398
|
-
></span>
|
|
399
|
-
</span>
|
|
400
|
-
</div>
|
|
401
|
-
{/snippet}
|
|
402
|
-
</DropdownMenu.CheckboxItem>
|
|
403
|
-
{/each}
|
|
404
|
-
|
|
405
|
-
{#if $allMcpServers.length > 0}
|
|
406
|
-
<DropdownMenu.Separator class="my-1 h-px bg-gray-200 dark:bg-gray-700/60" />
|
|
407
|
-
{/if}
|
|
408
|
-
<DropdownMenu.Item
|
|
409
|
-
class="flex h-9 select-none items-center gap-1 rounded-md px-2 text-sm text-gray-700 data-[highlighted]:bg-gray-100 focus-visible:outline-none dark:text-gray-200 dark:data-[highlighted]:bg-white/10 sm:h-8"
|
|
410
|
-
onSelect={() => (isMcpManagerOpen = true)}
|
|
411
|
-
>
|
|
412
|
-
Manage MCP Servers
|
|
413
|
-
</DropdownMenu.Item>
|
|
414
|
-
</DropdownMenu.SubContent>
|
|
415
|
-
</DropdownMenu.Sub>
|
|
416
|
-
</DropdownMenu.Content>
|
|
417
|
-
</DropdownMenu.Portal>
|
|
418
|
-
</DropdownMenu.Root>
|
|
419
|
-
|
|
420
|
-
{#if $enabledServersCount > 0}
|
|
421
|
-
<div
|
|
422
|
-
class="ml-1.5 inline-flex h-8 items-center gap-1.5 rounded-full border border-blue-500/10 bg-blue-600/10 pl-2 pr-1 text-xs font-semibold text-blue-700 dark:bg-blue-600/20 dark:text-blue-400 sm:h-7"
|
|
423
|
-
class:grayscale={!modelSupportsTools}
|
|
424
|
-
class:opacity-60={!modelSupportsTools}
|
|
425
|
-
class:cursor-help={!modelSupportsTools}
|
|
426
|
-
title={modelSupportsTools
|
|
427
|
-
? "MCP servers enabled"
|
|
428
|
-
: "Current model doesn’t support tools"}
|
|
429
|
-
>
|
|
430
|
-
<button
|
|
431
|
-
class="inline-flex cursor-pointer select-none items-center gap-1 bg-transparent p-0 leading-none text-current focus:outline-none"
|
|
432
|
-
type="button"
|
|
433
|
-
title="Manage MCP Servers"
|
|
434
|
-
onclick={() => (isMcpManagerOpen = true)}
|
|
435
|
-
class:line-through={!modelSupportsTools}
|
|
436
|
-
>
|
|
437
|
-
{#if selectedServers.length}
|
|
438
|
-
<span class="flex items-center -space-x-1">
|
|
439
|
-
{#each selectedServers.slice(0, 3) as server (server.id)}
|
|
440
|
-
<img
|
|
441
|
-
src={getMcpServerFaviconUrl(server.url)}
|
|
442
|
-
alt=""
|
|
443
|
-
class="size-4 rounded bg-white p-px shadow-sm ring-1 ring-black/5 dark:bg-gray-900 dark:ring-white/10"
|
|
444
|
-
/>
|
|
445
|
-
{/each}
|
|
446
|
-
{#if selectedServers.length > 3}
|
|
447
|
-
<span class="ml-1 text-[10px] font-semibold text-blue-800 dark:text-blue-200">
|
|
448
|
-
+{selectedServers.length - 3}
|
|
449
|
-
</span>
|
|
450
|
-
{/if}
|
|
451
|
-
</span>
|
|
452
|
-
{/if}
|
|
453
|
-
MCP ({$enabledServersCount})
|
|
454
|
-
</button>
|
|
455
|
-
<button
|
|
456
|
-
class="grid size-5 place-items-center rounded-full bg-blue-600/15 text-blue-700 transition-colors hover:bg-blue-600/25 dark:bg-blue-600/25 dark:text-blue-300 dark:hover:bg-blue-600/35"
|
|
457
|
-
aria-label="Disable all MCP servers"
|
|
458
|
-
onclick={() => disableAllServers()}
|
|
459
|
-
type="button"
|
|
460
|
-
>
|
|
461
|
-
<CarbonClose class="size-3.5" />
|
|
462
|
-
</button>
|
|
463
|
-
</div>
|
|
464
|
-
{/if}
|
|
465
|
-
</div>
|
|
466
|
-
{/if}
|
|
467
|
-
</div>
|
|
468
|
-
{/if}
|
|
469
|
-
{@render children?.()}
|
|
470
|
-
|
|
471
|
-
<UrlFetchModal
|
|
472
|
-
bind:open={isUrlModalOpen}
|
|
473
|
-
acceptMimeTypes={mimeTypes}
|
|
474
|
-
onfiles={handleFetchedFiles}
|
|
475
|
-
/>
|
|
476
|
-
|
|
477
|
-
{#if isMcpManagerOpen}
|
|
478
|
-
<MCPServerManager onclose={() => (isMcpManagerOpen = false)} />
|
|
479
|
-
{/if}
|
|
480
|
-
</div>
|
|
481
|
-
|
|
482
|
-
<style lang="postcss">
|
|
483
|
-
:global(pre),
|
|
484
|
-
:global(textarea) {
|
|
485
|
-
font-family: inherit;
|
|
486
|
-
box-sizing: border-box;
|
|
487
|
-
line-height: 1.5;
|
|
488
|
-
font-size: 16px;
|
|
489
|
-
}
|
|
490
|
-
</style>
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { onMount, tick } from "svelte";
|
|
3
|
+
|
|
4
|
+
import { afterNavigate } from "$app/navigation";
|
|
5
|
+
|
|
6
|
+
import { DropdownMenu } from "bits-ui";
|
|
7
|
+
import IconPlus from "~icons/lucide/plus";
|
|
8
|
+
import CarbonImage from "~icons/carbon/image";
|
|
9
|
+
import CarbonDocument from "~icons/carbon/document";
|
|
10
|
+
import CarbonUpload from "~icons/carbon/upload";
|
|
11
|
+
import CarbonLink from "~icons/carbon/link";
|
|
12
|
+
import CarbonChevronRight from "~icons/carbon/chevron-right";
|
|
13
|
+
import CarbonClose from "~icons/carbon/close";
|
|
14
|
+
import UrlFetchModal from "./UrlFetchModal.svelte";
|
|
15
|
+
import { TEXT_MIME_ALLOWLIST, IMAGE_MIME_ALLOWLIST_DEFAULT } from "$lib/constants/mime";
|
|
16
|
+
import MCPServerManager from "$lib/components/mcp/MCPServerManager.svelte";
|
|
17
|
+
import IconMCP from "$lib/components/icons/IconMCP.svelte";
|
|
18
|
+
|
|
19
|
+
import { isVirtualKeyboard } from "$lib/utils/isVirtualKeyboard";
|
|
20
|
+
import { requireAuthUser } from "$lib/utils/auth";
|
|
21
|
+
import {
|
|
22
|
+
enabledServersCount,
|
|
23
|
+
selectedServerIds,
|
|
24
|
+
allMcpServers,
|
|
25
|
+
toggleServer,
|
|
26
|
+
disableAllServers,
|
|
27
|
+
} from "$lib/stores/mcpServers";
|
|
28
|
+
import { getMcpServerFaviconUrl } from "$lib/utils/favicon";
|
|
29
|
+
import { page } from "$app/state";
|
|
30
|
+
|
|
31
|
+
interface Props {
|
|
32
|
+
files?: File[];
|
|
33
|
+
mimeTypes?: string[];
|
|
34
|
+
value?: string;
|
|
35
|
+
placeholder?: string;
|
|
36
|
+
loading?: boolean;
|
|
37
|
+
disabled?: boolean;
|
|
38
|
+
// tools removed
|
|
39
|
+
modelIsMultimodal?: boolean;
|
|
40
|
+
// Whether the currently selected model supports tool calling (incl. overrides)
|
|
41
|
+
modelSupportsTools?: boolean;
|
|
42
|
+
children?: import("svelte").Snippet;
|
|
43
|
+
onPaste?: (e: ClipboardEvent) => void;
|
|
44
|
+
focused?: boolean;
|
|
45
|
+
onsubmit?: () => void;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
let {
|
|
49
|
+
files = $bindable([]),
|
|
50
|
+
mimeTypes = [],
|
|
51
|
+
value = $bindable(""),
|
|
52
|
+
placeholder = "",
|
|
53
|
+
loading = false,
|
|
54
|
+
disabled = false,
|
|
55
|
+
|
|
56
|
+
modelIsMultimodal = false,
|
|
57
|
+
modelSupportsTools = true,
|
|
58
|
+
children,
|
|
59
|
+
onPaste,
|
|
60
|
+
focused = $bindable(false),
|
|
61
|
+
onsubmit,
|
|
62
|
+
}: Props = $props();
|
|
63
|
+
|
|
64
|
+
const onFileChange = async (e: Event) => {
|
|
65
|
+
if (!e.target) return;
|
|
66
|
+
const target = e.target as HTMLInputElement;
|
|
67
|
+
const selected = Array.from(target.files ?? []);
|
|
68
|
+
if (selected.length === 0) return;
|
|
69
|
+
files = [...files, ...selected];
|
|
70
|
+
await tick();
|
|
71
|
+
void focusTextarea();
|
|
72
|
+
};
|
|
73
|
+
|
|
74
|
+
let textareaElement: HTMLTextAreaElement | undefined = $state();
|
|
75
|
+
let isCompositionOn = $state(false);
|
|
76
|
+
let blurTimeout: ReturnType<typeof setTimeout> | null = $state(null);
|
|
77
|
+
|
|
78
|
+
let fileInputEl: HTMLInputElement | undefined = $state();
|
|
79
|
+
let isUrlModalOpen = $state(false);
|
|
80
|
+
let isMcpManagerOpen = $state(false);
|
|
81
|
+
let isDropdownOpen = $state(false);
|
|
82
|
+
|
|
83
|
+
function openPickerWithAccept(accept: string) {
|
|
84
|
+
if (!fileInputEl) return;
|
|
85
|
+
const allAccept = mimeTypes.join(",");
|
|
86
|
+
fileInputEl.setAttribute("accept", accept);
|
|
87
|
+
fileInputEl.click();
|
|
88
|
+
queueMicrotask(() => fileInputEl?.setAttribute("accept", allAccept));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function openFilePickerText() {
|
|
92
|
+
const textAccept =
|
|
93
|
+
mimeTypes.filter((m) => !(m === "image/*" || m.startsWith("image/"))).join(",") ||
|
|
94
|
+
TEXT_MIME_ALLOWLIST.join(",");
|
|
95
|
+
openPickerWithAccept(textAccept);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function openFilePickerImage() {
|
|
99
|
+
const imageAccept =
|
|
100
|
+
mimeTypes.filter((m) => m === "image/*" || m.startsWith("image/")).join(",") ||
|
|
101
|
+
IMAGE_MIME_ALLOWLIST_DEFAULT.join(",");
|
|
102
|
+
openPickerWithAccept(imageAccept);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const waitForAnimationFrame = () =>
|
|
106
|
+
typeof requestAnimationFrame === "function"
|
|
107
|
+
? new Promise<void>((resolve) => {
|
|
108
|
+
requestAnimationFrame(() => resolve());
|
|
109
|
+
})
|
|
110
|
+
: Promise.resolve();
|
|
111
|
+
|
|
112
|
+
async function focusTextarea() {
|
|
113
|
+
if (page.data.shared && page.data.loginEnabled && !page.data.user) return;
|
|
114
|
+
if (!textareaElement || textareaElement.disabled || isVirtualKeyboard()) return;
|
|
115
|
+
if (typeof document !== "undefined" && document.activeElement === textareaElement) return;
|
|
116
|
+
|
|
117
|
+
await tick();
|
|
118
|
+
|
|
119
|
+
if (typeof requestAnimationFrame === "function") {
|
|
120
|
+
await waitForAnimationFrame();
|
|
121
|
+
await waitForAnimationFrame();
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!textareaElement || textareaElement.disabled || isVirtualKeyboard()) return;
|
|
125
|
+
|
|
126
|
+
try {
|
|
127
|
+
textareaElement.focus({ preventScroll: true });
|
|
128
|
+
} catch {
|
|
129
|
+
textareaElement.focus();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Retry only when focus failed due to #app being inert (modal closing transition)
|
|
133
|
+
if (
|
|
134
|
+
typeof document !== "undefined" &&
|
|
135
|
+
document.activeElement !== textareaElement &&
|
|
136
|
+
document.getElementById("app")?.hasAttribute("inert")
|
|
137
|
+
) {
|
|
138
|
+
setTimeout(() => {
|
|
139
|
+
if (!textareaElement || textareaElement.disabled || isVirtualKeyboard()) return;
|
|
140
|
+
if (document.activeElement === textareaElement) return;
|
|
141
|
+
try {
|
|
142
|
+
textareaElement.focus({ preventScroll: true });
|
|
143
|
+
} catch {
|
|
144
|
+
textareaElement.focus();
|
|
145
|
+
}
|
|
146
|
+
}, 350);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
function handleFetchedFiles(newFiles: File[]) {
|
|
151
|
+
if (!newFiles?.length) return;
|
|
152
|
+
files = [...files, ...newFiles];
|
|
153
|
+
queueMicrotask(async () => {
|
|
154
|
+
await tick();
|
|
155
|
+
void focusTextarea();
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
onMount(() => {
|
|
160
|
+
void focusTextarea();
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
afterNavigate(() => {
|
|
164
|
+
void focusTextarea();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
function adjustTextareaHeight() {
|
|
168
|
+
if (!textareaElement) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
textareaElement.style.height = "auto";
|
|
173
|
+
textareaElement.style.height = `${textareaElement.scrollHeight}px`;
|
|
174
|
+
|
|
175
|
+
if (textareaElement.selectionStart === textareaElement.value.length) {
|
|
176
|
+
textareaElement.scrollTop = textareaElement.scrollHeight;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
$effect(() => {
|
|
181
|
+
if (!textareaElement) return;
|
|
182
|
+
void value;
|
|
183
|
+
adjustTextareaHeight();
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
function handleKeydown(event: KeyboardEvent) {
|
|
187
|
+
if (
|
|
188
|
+
event.key === "Enter" &&
|
|
189
|
+
!event.shiftKey &&
|
|
190
|
+
!isCompositionOn &&
|
|
191
|
+
!isVirtualKeyboard() &&
|
|
192
|
+
value.trim() !== ""
|
|
193
|
+
) {
|
|
194
|
+
event.preventDefault();
|
|
195
|
+
tick();
|
|
196
|
+
onsubmit?.();
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function handleFocus() {
|
|
201
|
+
if (requireAuthUser()) {
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
if (blurTimeout) {
|
|
205
|
+
clearTimeout(blurTimeout);
|
|
206
|
+
blurTimeout = null;
|
|
207
|
+
}
|
|
208
|
+
focused = true;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function handleBlur() {
|
|
212
|
+
if (!isVirtualKeyboard()) {
|
|
213
|
+
focused = false;
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (blurTimeout) {
|
|
218
|
+
clearTimeout(blurTimeout);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
blurTimeout = setTimeout(() => {
|
|
222
|
+
blurTimeout = null;
|
|
223
|
+
focused = false;
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Show file upload when any mime is allowed (text always; images if multimodal)
|
|
228
|
+
let showFileUpload = $derived(mimeTypes.length > 0);
|
|
229
|
+
let showNoTools = $derived(!showFileUpload);
|
|
230
|
+
let selectedServers = $derived(
|
|
231
|
+
$allMcpServers.filter((server) => $selectedServerIds.has(server.id))
|
|
232
|
+
);
|
|
233
|
+
</script>
|
|
234
|
+
|
|
235
|
+
<div class="flex min-h-full flex-1 flex-col" onpaste={onPaste}>
|
|
236
|
+
<textarea
|
|
237
|
+
rows="1"
|
|
238
|
+
tabindex="0"
|
|
239
|
+
inputmode="text"
|
|
240
|
+
class="scrollbar-custom max-h-[4lh] w-full resize-none overflow-y-auto overflow-x-hidden border-0 bg-transparent px-2.5 py-2.5 outline-none focus:ring-0 focus-visible:ring-0 sm:px-3 md:max-h-[8lh]"
|
|
241
|
+
class:text-gray-400={disabled}
|
|
242
|
+
bind:value
|
|
243
|
+
bind:this={textareaElement}
|
|
244
|
+
onkeydown={handleKeydown}
|
|
245
|
+
oncompositionstart={() => (isCompositionOn = true)}
|
|
246
|
+
oncompositionend={() => (isCompositionOn = false)}
|
|
247
|
+
{placeholder}
|
|
248
|
+
{disabled}
|
|
249
|
+
onfocus={handleFocus}
|
|
250
|
+
onblur={handleBlur}
|
|
251
|
+
onbeforeinput={requireAuthUser}
|
|
252
|
+
></textarea>
|
|
253
|
+
|
|
254
|
+
{#if !showNoTools}
|
|
255
|
+
<div
|
|
256
|
+
class={[
|
|
257
|
+
"scrollbar-custom -ml-0.5 flex max-w-[calc(100%-40px)] flex-wrap items-center justify-start gap-2.5 px-3 pb-2.5 pt-1.5 text-gray-500 dark:text-gray-400 max-md:flex-nowrap max-md:overflow-x-auto sm:gap-2",
|
|
258
|
+
]}
|
|
259
|
+
>
|
|
260
|
+
{#if showFileUpload}
|
|
261
|
+
<div class="flex items-center">
|
|
262
|
+
<input
|
|
263
|
+
bind:this={fileInputEl}
|
|
264
|
+
disabled={loading}
|
|
265
|
+
class="absolute hidden size-0"
|
|
266
|
+
aria-label="Upload file"
|
|
267
|
+
type="file"
|
|
268
|
+
multiple
|
|
269
|
+
onchange={onFileChange}
|
|
270
|
+
onclick={(e) => {
|
|
271
|
+
if (requireAuthUser()) {
|
|
272
|
+
e.preventDefault();
|
|
273
|
+
}
|
|
274
|
+
}}
|
|
275
|
+
accept={mimeTypes.join(",")}
|
|
276
|
+
/>
|
|
277
|
+
|
|
278
|
+
<DropdownMenu.Root
|
|
279
|
+
bind:open={isDropdownOpen}
|
|
280
|
+
onOpenChange={(open) => {
|
|
281
|
+
if (open && requireAuthUser()) {
|
|
282
|
+
isDropdownOpen = false;
|
|
283
|
+
return;
|
|
284
|
+
}
|
|
285
|
+
isDropdownOpen = open;
|
|
286
|
+
}}
|
|
287
|
+
>
|
|
288
|
+
<DropdownMenu.Trigger
|
|
289
|
+
class="btn size-8 rounded-full border bg-white text-black shadow transition-none enabled:hover:bg-white enabled:hover:shadow-inner dark:border-transparent dark:bg-gray-600/50 dark:text-white dark:hover:enabled:bg-gray-600 sm:size-7"
|
|
290
|
+
disabled={loading}
|
|
291
|
+
aria-label="Add attachment"
|
|
292
|
+
>
|
|
293
|
+
<IconPlus class="text-base sm:text-sm" />
|
|
294
|
+
</DropdownMenu.Trigger>
|
|
295
|
+
<DropdownMenu.Portal>
|
|
296
|
+
<DropdownMenu.Content
|
|
297
|
+
class="z-50 rounded-xl border border-gray-200 bg-white/95 p-1 text-gray-800 shadow-lg backdrop-blur dark:border-gray-700/60 dark:bg-gray-800/95 dark:text-gray-100"
|
|
298
|
+
side="top"
|
|
299
|
+
sideOffset={8}
|
|
300
|
+
align="start"
|
|
301
|
+
trapFocus={false}
|
|
302
|
+
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
303
|
+
interactOutsideBehavior="defer-otherwise-close"
|
|
304
|
+
>
|
|
305
|
+
{#if modelIsMultimodal}
|
|
306
|
+
<DropdownMenu.Item
|
|
307
|
+
class="flex h-9 select-none items-center gap-1 rounded-md px-2 text-sm text-gray-700 data-[highlighted]:bg-gray-100 focus-visible:outline-none dark:text-gray-200 dark:data-[highlighted]:bg-white/10 sm:h-8"
|
|
308
|
+
onSelect={() => openFilePickerImage()}
|
|
309
|
+
>
|
|
310
|
+
<CarbonImage class="size-4 opacity-90 dark:opacity-80" />
|
|
311
|
+
Add image(s)
|
|
312
|
+
</DropdownMenu.Item>
|
|
313
|
+
{/if}
|
|
314
|
+
|
|
315
|
+
<DropdownMenu.Sub>
|
|
316
|
+
<DropdownMenu.SubTrigger
|
|
317
|
+
class="flex h-9 select-none items-center gap-1 rounded-md px-2 text-sm text-gray-700 data-[highlighted]:bg-gray-100 data-[state=open]:bg-gray-100 focus-visible:outline-none dark:text-gray-200 dark:data-[highlighted]:bg-white/10 dark:data-[state=open]:bg-white/10 sm:h-8"
|
|
318
|
+
>
|
|
319
|
+
<div class="flex items-center gap-1">
|
|
320
|
+
<CarbonDocument class="size-4 opacity-90 dark:opacity-80" />
|
|
321
|
+
Add text file
|
|
322
|
+
</div>
|
|
323
|
+
<div class="ml-auto flex items-center">
|
|
324
|
+
<CarbonChevronRight class="size-4 opacity-70 dark:opacity-80" />
|
|
325
|
+
</div>
|
|
326
|
+
</DropdownMenu.SubTrigger>
|
|
327
|
+
<DropdownMenu.SubContent
|
|
328
|
+
class="z-50 rounded-xl border border-gray-200 bg-white/95 p-1 text-gray-800 shadow-lg backdrop-blur dark:border-gray-700/60 dark:bg-gray-800/95 dark:text-gray-100"
|
|
329
|
+
sideOffset={10}
|
|
330
|
+
trapFocus={false}
|
|
331
|
+
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
332
|
+
interactOutsideBehavior="defer-otherwise-close"
|
|
333
|
+
>
|
|
334
|
+
<DropdownMenu.Item
|
|
335
|
+
class="flex h-9 select-none items-center gap-1 rounded-md px-2 text-sm text-gray-700 data-[highlighted]:bg-gray-100 focus-visible:outline-none dark:text-gray-200 dark:data-[highlighted]:bg-white/10 sm:h-8"
|
|
336
|
+
onSelect={() => openFilePickerText()}
|
|
337
|
+
>
|
|
338
|
+
<CarbonUpload class="size-4 opacity-90 dark:opacity-80" />
|
|
339
|
+
Upload from device
|
|
340
|
+
</DropdownMenu.Item>
|
|
341
|
+
<DropdownMenu.Item
|
|
342
|
+
class="flex h-9 select-none items-center gap-1 rounded-md px-2 text-sm text-gray-700 data-[highlighted]:bg-gray-100 focus-visible:outline-none dark:text-gray-200 dark:data-[highlighted]:bg-white/10 sm:h-8"
|
|
343
|
+
onSelect={() => (isUrlModalOpen = true)}
|
|
344
|
+
>
|
|
345
|
+
<CarbonLink class="size-4 opacity-90 dark:opacity-80" />
|
|
346
|
+
Fetch from URL
|
|
347
|
+
</DropdownMenu.Item>
|
|
348
|
+
</DropdownMenu.SubContent>
|
|
349
|
+
</DropdownMenu.Sub>
|
|
350
|
+
|
|
351
|
+
<!-- MCP Servers submenu -->
|
|
352
|
+
<DropdownMenu.Sub>
|
|
353
|
+
<DropdownMenu.SubTrigger
|
|
354
|
+
class="flex h-9 select-none items-center gap-1 rounded-md px-2 text-sm text-gray-700 data-[highlighted]:bg-gray-100 data-[state=open]:bg-gray-100 focus-visible:outline-none dark:text-gray-200 dark:data-[highlighted]:bg-white/10 dark:data-[state=open]:bg-white/10 sm:h-8"
|
|
355
|
+
>
|
|
356
|
+
<div class="flex items-center gap-1">
|
|
357
|
+
<IconMCP classNames="size-4 opacity-90 dark:opacity-80" />
|
|
358
|
+
MCP Servers
|
|
359
|
+
</div>
|
|
360
|
+
<div class="ml-auto flex items-center">
|
|
361
|
+
<CarbonChevronRight class="size-4 opacity-70 dark:opacity-80" />
|
|
362
|
+
</div>
|
|
363
|
+
</DropdownMenu.SubTrigger>
|
|
364
|
+
<DropdownMenu.SubContent
|
|
365
|
+
class="z-50 rounded-xl border border-gray-200 bg-white/95 p-1 text-gray-800 shadow-lg backdrop-blur dark:border-gray-700/60 dark:bg-gray-800/95 dark:text-gray-100"
|
|
366
|
+
sideOffset={10}
|
|
367
|
+
trapFocus={false}
|
|
368
|
+
onCloseAutoFocus={(e) => e.preventDefault()}
|
|
369
|
+
interactOutsideBehavior="defer-otherwise-close"
|
|
370
|
+
>
|
|
371
|
+
{#each $allMcpServers as server (server.id)}
|
|
372
|
+
<DropdownMenu.CheckboxItem
|
|
373
|
+
checked={$selectedServerIds.has(server.id)}
|
|
374
|
+
onCheckedChange={() => toggleServer(server.id)}
|
|
375
|
+
closeOnSelect={false}
|
|
376
|
+
class="flex h-9 select-none items-center gap-2 rounded-md px-2 text-sm leading-none text-gray-800 data-[highlighted]:bg-gray-100 focus-visible:outline-none dark:text-gray-100 dark:data-[highlighted]:bg-white/10"
|
|
377
|
+
>
|
|
378
|
+
{#snippet children({ checked })}
|
|
379
|
+
<img
|
|
380
|
+
src={getMcpServerFaviconUrl(server.url)}
|
|
381
|
+
alt=""
|
|
382
|
+
class="size-4 flex-shrink-0 rounded"
|
|
383
|
+
/>
|
|
384
|
+
<span class="max-w-52 truncate py-1">{server.name}</span>
|
|
385
|
+
<div class="ml-auto flex items-center">
|
|
386
|
+
<!-- Toggle visual -->
|
|
387
|
+
<span
|
|
388
|
+
class={[
|
|
389
|
+
"relative mt-px flex h-4 w-7 items-center self-center rounded-full transition-colors",
|
|
390
|
+
checked ? "bg-blue-600/80" : "bg-gray-300 dark:bg-gray-700",
|
|
391
|
+
]}
|
|
392
|
+
>
|
|
393
|
+
<span
|
|
394
|
+
class={[
|
|
395
|
+
"block size-3 translate-x-0.5 rounded-full bg-white shadow transition-transform",
|
|
396
|
+
checked ? "translate-x-[14px]" : "translate-x-0.5",
|
|
397
|
+
]}
|
|
398
|
+
></span>
|
|
399
|
+
</span>
|
|
400
|
+
</div>
|
|
401
|
+
{/snippet}
|
|
402
|
+
</DropdownMenu.CheckboxItem>
|
|
403
|
+
{/each}
|
|
404
|
+
|
|
405
|
+
{#if $allMcpServers.length > 0}
|
|
406
|
+
<DropdownMenu.Separator class="my-1 h-px bg-gray-200 dark:bg-gray-700/60" />
|
|
407
|
+
{/if}
|
|
408
|
+
<DropdownMenu.Item
|
|
409
|
+
class="flex h-9 select-none items-center gap-1 rounded-md px-2 text-sm text-gray-700 data-[highlighted]:bg-gray-100 focus-visible:outline-none dark:text-gray-200 dark:data-[highlighted]:bg-white/10 sm:h-8"
|
|
410
|
+
onSelect={() => (isMcpManagerOpen = true)}
|
|
411
|
+
>
|
|
412
|
+
Manage MCP Servers
|
|
413
|
+
</DropdownMenu.Item>
|
|
414
|
+
</DropdownMenu.SubContent>
|
|
415
|
+
</DropdownMenu.Sub>
|
|
416
|
+
</DropdownMenu.Content>
|
|
417
|
+
</DropdownMenu.Portal>
|
|
418
|
+
</DropdownMenu.Root>
|
|
419
|
+
|
|
420
|
+
{#if $enabledServersCount > 0}
|
|
421
|
+
<div
|
|
422
|
+
class="ml-1.5 inline-flex h-8 items-center gap-1.5 rounded-full border border-blue-500/10 bg-blue-600/10 pl-2 pr-1 text-xs font-semibold text-blue-700 dark:bg-blue-600/20 dark:text-blue-400 sm:h-7"
|
|
423
|
+
class:grayscale={!modelSupportsTools}
|
|
424
|
+
class:opacity-60={!modelSupportsTools}
|
|
425
|
+
class:cursor-help={!modelSupportsTools}
|
|
426
|
+
title={modelSupportsTools
|
|
427
|
+
? "MCP servers enabled"
|
|
428
|
+
: "Current model doesn’t support tools"}
|
|
429
|
+
>
|
|
430
|
+
<button
|
|
431
|
+
class="inline-flex cursor-pointer select-none items-center gap-1 bg-transparent p-0 leading-none text-current focus:outline-none"
|
|
432
|
+
type="button"
|
|
433
|
+
title="Manage MCP Servers"
|
|
434
|
+
onclick={() => (isMcpManagerOpen = true)}
|
|
435
|
+
class:line-through={!modelSupportsTools}
|
|
436
|
+
>
|
|
437
|
+
{#if selectedServers.length}
|
|
438
|
+
<span class="flex items-center -space-x-1">
|
|
439
|
+
{#each selectedServers.slice(0, 3) as server (server.id)}
|
|
440
|
+
<img
|
|
441
|
+
src={getMcpServerFaviconUrl(server.url)}
|
|
442
|
+
alt=""
|
|
443
|
+
class="size-4 rounded bg-white p-px shadow-sm ring-1 ring-black/5 dark:bg-gray-900 dark:ring-white/10"
|
|
444
|
+
/>
|
|
445
|
+
{/each}
|
|
446
|
+
{#if selectedServers.length > 3}
|
|
447
|
+
<span class="ml-1 text-[10px] font-semibold text-blue-800 dark:text-blue-200">
|
|
448
|
+
+{selectedServers.length - 3}
|
|
449
|
+
</span>
|
|
450
|
+
{/if}
|
|
451
|
+
</span>
|
|
452
|
+
{/if}
|
|
453
|
+
MCP ({$enabledServersCount})
|
|
454
|
+
</button>
|
|
455
|
+
<button
|
|
456
|
+
class="grid size-5 place-items-center rounded-full bg-blue-600/15 text-blue-700 transition-colors hover:bg-blue-600/25 dark:bg-blue-600/25 dark:text-blue-300 dark:hover:bg-blue-600/35"
|
|
457
|
+
aria-label="Disable all MCP servers"
|
|
458
|
+
onclick={() => disableAllServers()}
|
|
459
|
+
type="button"
|
|
460
|
+
>
|
|
461
|
+
<CarbonClose class="size-3.5" />
|
|
462
|
+
</button>
|
|
463
|
+
</div>
|
|
464
|
+
{/if}
|
|
465
|
+
</div>
|
|
466
|
+
{/if}
|
|
467
|
+
</div>
|
|
468
|
+
{/if}
|
|
469
|
+
{@render children?.()}
|
|
470
|
+
|
|
471
|
+
<UrlFetchModal
|
|
472
|
+
bind:open={isUrlModalOpen}
|
|
473
|
+
acceptMimeTypes={mimeTypes}
|
|
474
|
+
onfiles={handleFetchedFiles}
|
|
475
|
+
/>
|
|
476
|
+
|
|
477
|
+
{#if isMcpManagerOpen}
|
|
478
|
+
<MCPServerManager onclose={() => (isMcpManagerOpen = false)} />
|
|
479
|
+
{/if}
|
|
480
|
+
</div>
|
|
481
|
+
|
|
482
|
+
<style lang="postcss">
|
|
483
|
+
:global(pre),
|
|
484
|
+
:global(textarea) {
|
|
485
|
+
font-family: inherit;
|
|
486
|
+
box-sizing: border-box;
|
|
487
|
+
line-height: 1.5;
|
|
488
|
+
font-size: 16px;
|
|
489
|
+
}
|
|
490
|
+
</style>
|