From 92d1bb0c996f49bb09561f755ba784565e1aa51c Mon Sep 17 00:00:00 2001 From: Aleksander Grygier Date: Mon, 10 Aug 2026 08:38:37 +0200 Subject: [PATCH] ui: Linting & Formatting scripts (#26819) --- tools/ui/eslint.config.js | 64 +- tools/ui/package-lock.json | 232 ++++ tools/ui/package.json | 8 +- tools/ui/playwright.config.ts | 28 +- tools/ui/pwa-assets-dark.config.ts | 18 +- tools/ui/pwa-assets.config.ts | 49 +- tools/ui/scripts/favicon-colorize.ts | 26 +- tools/ui/scripts/make-icons-circular.js | 36 +- tools/ui/scripts/vite-plugin-build-info.ts | 12 +- tools/ui/scripts/vite-plugin-nerdamer.ts | 26 +- .../ui/scripts/vite-plugin-relativize-base.ts | 13 +- tools/ui/scripts/vite-plugin-splash-screen.ts | 36 +- tools/ui/src/app.d.ts | 31 +- .../components/app/actions/ActionIcon.svelte | 20 +- .../actions/ActionIconCopyToClipboard.svelte | 6 +- .../app/badges/BadgesModality.svelte | 2 +- .../ChatAttachmentsList.svelte | 14 +- .../ChatAttachmentsListItem.svelte | 18 +- .../ChatAttachmentsListItemMcpPrompt.svelte | 4 +- .../ChatAttachmentsListItemMcpResource.svelte | 11 +- ...hatAttachmentsListItemThumbnailFile.svelte | 14 +- ...atAttachmentsListItemThumbnailImage.svelte | 4 +- .../ChatAttachmentsPreview.svelte | 17 +- .../ChatAttachmentsPreviewCurrentItem.svelte | 28 +- ...tAttachmentsPreviewCurrentItemAudio.svelte | 2 +- ...hatAttachmentsPreviewCurrentItemPdf.svelte | 17 +- .../ChatAttachmentsPreviewNavButtons.svelte | 2 +- ...hatAttachmentsPreviewThumbnailStrip.svelte | 8 +- .../app/chat/ChatForm/ChatForm.svelte | 118 +- .../ChatFormActionAddButton.svelte | 2 +- .../ChatFormActionAddDropdown.svelte | 32 +- .../ChatFormActionAddMcpServersSubmenu.svelte | 17 +- .../ChatFormActionAddReasoningSubmenu.svelte | 4 +- .../ChatFormActionAddSheet.svelte | 50 +- .../ChatFormActionAddToolsSubmenu.svelte | 8 +- .../ChatFormActionsAdd.svelte | 6 +- .../ChatFormActionModels.svelte | 11 +- .../ChatFormActionRecord.svelte | 2 +- .../ChatFormActions/ChatFormActions.svelte | 42 +- .../ChatForm/ChatFormContenteditable.svelte | 90 +- .../ChatFormContextGauge.svelte | 12 +- .../ContextGaugeDetailRow.svelte | 2 +- .../ContextGaugeDetails.svelte | 18 +- .../ContextGaugeDial.svelte | 2 +- .../ContextGaugeLoadModel.svelte | 2 +- .../ContextGaugePopup.svelte | 13 +- .../ChatFormContextGauge/context-gauge.ts | 3 + .../ChatForm/ChatFormMcpResourcesList.svelte | 10 +- .../ChatFormCommandPicker.svelte | 27 +- .../ChatFormMentionPicker.svelte | 51 +- .../ChatFormPickerItemHeader.svelte | 6 +- .../ChatFormPicker/ChatFormPickerList.svelte | 38 +- .../ChatFormPickerListItem.svelte | 10 +- .../ChatFormPickerListItemSkeleton.svelte | 2 +- .../ChatFormPickerPopover.svelte | 6 +- .../ChatFormPickerMcpPrompts.svelte | 45 +- .../ChatFormPromptPickerArgumentForm.svelte | 18 +- .../ChatFormPromptPickerArgumentInput.svelte | 20 +- .../ChatFormPickers/ChatFormPickers.svelte | 22 +- .../app/chat/ChatForm/ChatFormTextarea.svelte | 1 + .../ChatForm/ChatFormWorkingDirectory.svelte | 84 +- .../ChatFormWorkingDirectoryChip.svelte | 12 +- ...ChatFormWorkingDirectoryResultsList.svelte | 16 +- .../ChatMessage/ChatMessage.svelte | 89 +- .../ChatMessageAssistant.svelte | 20 +- .../ChatMessageAssistantModel.svelte | 7 +- .../ChatMessageAssistantProcessingInfo.svelte | 4 +- .../ChatMessageAssistantRawOutput.svelte | 3 +- .../ChatMessageAssistantStatistics.svelte | 2 +- .../ChatMessage/ChatMessageCwdChange.svelte | 2 +- .../ChatMessageMcpPrompt.svelte | 18 +- .../ChatMessageMcpPromptContent.svelte | 29 +- .../ChatMessage/ChatMessageSynthetic.svelte | 4 +- .../ChatMessageSystem.svelte | 12 +- .../ChatMessageToolCallBlock.svelte | 18 +- .../ChatMessageToolCallBlockDefault.svelte | 14 +- .../ChatMessageToolCallBlockEditFile.svelte | 10 +- ...essageToolCallBlockExecShellCommand.svelte | 30 +- ...tMessageToolCallBlockFileGlobSearch.svelte | 8 +- ...ChatMessageToolCallBlockGetDatetime.svelte | 5 +- .../ChatMessageToolCallBlockGetInfo.svelte | 11 +- .../ChatMessageToolCallBlockGrepSearch.svelte | 8 +- .../ChatMessageToolCallBlockReadFile.svelte | 6 +- ...atMessageToolCallBlockRunJavascript.svelte | 12 +- ...atMessageToolCallBlockSearchResults.svelte | 18 +- .../ChatMessageToolCallBlockWriteFile.svelte | 8 +- .../ChatMessageToolCall/ToolCallBlock.svelte | 25 +- .../ChatMessageToolCall/parsers/_shared.ts | 5 +- .../ChatMessageToolCall/parsers/edit-file.ts | 27 +- .../parsers/exec-shell-command.ts | 5 +- .../parsers/file-glob-search.ts | 13 +- .../parsers/grep-search.ts | 36 +- .../ChatMessageToolCall/parsers/read-file.ts | 18 +- .../parsers/run-javascript.ts | 12 +- .../ChatMessageToolCall/parsers/write-file.ts | 17 +- .../ChatMessageUser/ChatMessageUser.svelte | 21 +- .../ChatMessageUserBubble.svelte | 11 +- .../ChatMessageUserPending.svelte | 6 +- .../ChatMessageActionCard.svelte | 4 +- ...hatMessageActionCardContinueRequest.svelte | 2 +- ...tMessageActionCardPermissionRequest.svelte | 8 +- .../ChatMessageActionIcons.svelte | 18 +- ...MessageActionIconsBranchingControls.svelte | 2 +- .../ChatMessageAgenticContent.svelte | 50 +- .../ChatMessages/ChatMessageEditForm.svelte | 8 +- .../ChatMessageReasoningBlock.svelte | 27 +- .../ChatMessageStatistics.svelte | 38 +- .../ChatMessageStatisticsBadge.svelte | 2 +- .../app/chat/ChatMessages/ChatMessages.svelte | 80 +- .../app/chat/ChatScreen/ChatScreen.svelte | 78 +- .../ChatScreenActionScrollDown.svelte | 2 +- .../ChatScreenDialogsAndAlerts.svelte | 12 +- .../app/chat/ChatScreen/ChatScreenForm.svelte | 15 +- .../ChatScreen/ChatScreenServerError.svelte | 2 +- .../ChatScreenStreamResumeStatus.svelte | 4 +- .../content/CollapsibleContentBlock.svelte | 14 +- .../content/CollapsibleTerminalBlock.svelte | 12 +- .../MarkdownContent/MarkdownContent.svelte | 229 ++-- .../MarkdownContent/markdown-handlers.ts | 26 +- .../content/MarkdownContent/markdown-utils.ts | 5 +- .../plugins/rehype/code-block-utils.ts | 82 +- .../plugins/rehype/enhance-code-blocks.ts | 9 +- .../plugins/rehype/enhance-links.ts | 2 +- .../plugins/rehype/enhance-mermaid-blocks.ts | 42 +- .../plugins/rehype/enhance-svg-blocks.ts | 42 +- .../plugins/rehype/file-badge.ts | 30 +- .../plugins/rehype/pre-transform.ts | 17 +- .../plugins/rehype/rehype-rtl-support.ts | 2 +- .../rehype/resolve-attachment-images.ts | 4 +- .../MarkdownContent/plugins/rehype/svg-pre.ts | 2 +- .../plugins/rehype/table-html-restorer.ts | 25 +- .../plugins/remark/literal-html.ts | 12 +- .../app/content/MermaidPreview.svelte | 5 +- .../app/content/MermaidPreviewControls.svelte | 8 +- .../app/content/SyntaxHighlightedCode.svelte | 26 +- .../DialogChatAttachmentsPreview.svelte | 13 +- .../app/dialogs/DialogChatError.svelte | 4 +- .../app/dialogs/DialogCodePreview.svelte | 4 +- .../app/dialogs/DialogConfirmation.svelte | 21 +- .../dialogs/DialogConversationRename.svelte | 10 +- .../DialogConversationSelection.svelte | 2 +- .../app/dialogs/DialogEmptyFileAlert.svelte | 4 +- .../app/dialogs/DialogExportSettings.svelte | 6 +- .../app/dialogs/DialogFileUploadError.svelte | 2 +- .../dialogs/DialogMcpResourcePreview.svelte | 18 +- .../dialogs/DialogMcpResourcesBrowser.svelte | 42 +- .../app/dialogs/DialogMcpServerAddNew.svelte | 30 +- .../app/dialogs/DialogMermaidPreview.svelte | 4 +- .../app/dialogs/DialogModelInformation.svelte | 11 +- .../dialogs/DialogModelNotAvailable.svelte | 9 +- .../app/forms/HighlightedMatch.svelte | 4 +- .../app/forms/InputWithSuggestions.svelte | 20 +- .../components/app/forms/KeyValuePairs.svelte | 21 +- .../components/app/forms/SearchInput.svelte | 16 +- .../app/mcp/McpActiveServersAvatars.svelte | 9 +- .../app/mcp/McpCapabilitiesBadges.svelte | 4 +- .../app/mcp/McpConnectionLogs.svelte | 4 +- .../app/mcp/McpResourcePreview.svelte | 31 +- .../app/mcp/McpResourceTemplateForm.svelte | 8 +- .../McpResourcesBrowser.svelte | 28 +- .../McpResourcesBrowserHeader.svelte | 6 +- .../McpResourcesBrowserServerItem.svelte | 50 +- .../mcp-resources-browser.ts | 26 +- .../mcp/McpServerCard/McpServerCard.svelte | 20 +- .../McpServerCard/McpServerCardActions.svelte | 4 +- .../McpServerCard/McpServerCardCompact.svelte | 5 +- .../McpServerCardDeleteDialog.svelte | 2 +- .../McpServerCardEditForm.svelte | 14 +- .../McpServerCard/McpServerCardHeader.svelte | 16 +- .../McpServerCardToolsList.svelte | 2 +- .../components/app/mcp/McpServerForm.svelte | 35 +- .../app/mcp/McpServerIdentity.svelte | 10 +- .../components/app/mcp/McpServerInfo.svelte | 2 +- .../app/misc/CodeBlockActions.svelte | 2 +- .../app/misc/ConversationSelection.svelte | 16 +- .../app/misc/HorizontalScrollCarousel.svelte | 12 +- .../app/misc/KeyboardShortcutInfo.svelte | 2 +- .../components/app/misc/TruncatedText.svelte | 3 +- .../components/app/models/ModelBadge.svelte | 6 +- .../lib/components/app/models/ModelId.svelte | 10 +- .../app/models/ModelsSelectorDropdown.svelte | 26 +- .../app/models/ModelsSelectorList.svelte | 16 +- .../app/models/ModelsSelectorOption.svelte | 21 +- .../app/models/ModelsSelectorSheet.svelte | 12 +- .../ui/src/lib/components/app/models/utils.ts | 19 +- .../app/navigation/DropdownMenuActions.svelte | 10 +- .../navigation/DropdownMenuSearchable.svelte | 16 +- .../SidebarNavigation.svelte | 70 +- .../SidebarNavigationActions.svelte | 24 +- .../SidebarNavigationConversationItem.svelte | 42 +- .../SidebarNavigationConversationList.svelte | 50 +- .../SidebarNavigationSearch.svelte | 6 +- .../SidebarNavigationSearchResults.svelte | 24 +- .../SidebarNavigationSelectionBar.svelte | 20 +- .../app/server/ServerErrorSplash.svelte | 29 +- .../components/app/server/ServerStatus.svelte | 8 +- .../settings/SettingsChat/SettingsChat.svelte | 34 +- .../SettingsChat/SettingsChatFields.svelte | 14 +- .../SettingsChatImportExportSection.svelte | 14 +- .../SettingsChatImportExportTab.svelte | 37 +- .../SettingsChat/SettingsChatToolsTab.svelte | 10 +- .../SettingsChatDesktopSidebar.svelte | 4 +- .../settings/SettingsChatMobileHeader.svelte | 10 +- .../app/settings/SettingsFooter.svelte | 6 +- .../app/settings/SettingsGroup.svelte | 2 +- .../app/settings/SettingsMcpServers.svelte | 30 +- .../src/lib/components/pwa/PwaMetaTags.svelte | 2 +- .../lib/components/pwa/PwaRefreshAlert.svelte | 4 +- .../alert-dialog/alert-dialog-action.svelte | 4 +- .../alert-dialog/alert-dialog-cancel.svelte | 4 +- .../alert-dialog/alert-dialog-content.svelte | 4 +- .../alert-dialog-description.svelte | 4 +- .../alert-dialog/alert-dialog-footer.svelte | 4 +- .../alert-dialog/alert-dialog-header.svelte | 6 +- .../alert-dialog/alert-dialog-overlay.svelte | 4 +- .../ui/alert-dialog/alert-dialog-title.svelte | 4 +- .../lib/components/ui/alert-dialog/index.ts | 10 +- .../ui/alert/alert-description.svelte | 6 +- .../components/ui/alert/alert-title.svelte | 6 +- .../src/lib/components/ui/alert/alert.svelte | 16 +- .../src/lib/components/ui/badge/badge.svelte | 26 +- .../ui/button-group/button-group-root.svelte | 2 +- .../lib/components/ui/button/button.svelte | 50 +- .../lib/components/ui/card/card-action.svelte | 4 +- .../components/ui/card/card-content.svelte | 6 +- .../ui/card/card-description.svelte | 6 +- .../lib/components/ui/card/card-footer.svelte | 4 +- .../lib/components/ui/card/card-header.svelte | 4 +- .../lib/components/ui/card/card-title.svelte | 6 +- .../ui/src/lib/components/ui/card/card.svelte | 6 +- tools/ui/src/lib/components/ui/card/index.ts | 2 +- .../components/ui/checkbox/checkbox.svelte | 6 +- .../ui/collapsible/collapsible.svelte | 2 +- .../lib/components/ui/collapsible/index.ts | 2 +- .../ui/dialog/dialog-content.svelte | 10 +- .../ui/dialog/dialog-description.svelte | 4 +- .../components/ui/dialog/dialog-footer.svelte | 4 +- .../components/ui/dialog/dialog-header.svelte | 6 +- .../ui/dialog/dialog-overlay.svelte | 4 +- .../components/ui/dialog/dialog-title.svelte | 4 +- .../ui/src/lib/components/ui/dialog/index.ts | 11 +- .../dropdown-menu-checkbox-item.svelte | 8 +- .../dropdown-menu-content.svelte | 4 +- .../dropdown-menu-group-heading.svelte | 4 +- .../dropdown-menu/dropdown-menu-item.svelte | 2 +- .../dropdown-menu/dropdown-menu-label.svelte | 4 +- .../dropdown-menu-radio-item.svelte | 6 +- .../dropdown-menu-separator.svelte | 4 +- .../dropdown-menu-shortcut.svelte | 6 +- .../dropdown-menu-sub-content.svelte | 4 +- .../dropdown-menu-sub-trigger.svelte | 6 +- .../lib/components/ui/dropdown-menu/index.ts | 6 +- .../components/ui/empty/empty-content.svelte | 4 +- .../ui/empty/empty-description.svelte | 4 +- .../components/ui/empty/empty-header.svelte | 4 +- .../components/ui/empty/empty-media.svelte | 10 +- .../components/ui/empty/empty-title.svelte | 4 +- .../src/lib/components/ui/empty/empty.svelte | 4 +- tools/ui/src/lib/components/ui/empty/index.ts | 4 +- .../ui/hover-card/hover-card-content.svelte | 10 +- .../src/lib/components/ui/hover-card/index.ts | 2 +- .../src/lib/components/ui/input/input.svelte | 10 +- .../src/lib/components/ui/label/label.svelte | 4 +- .../ui/src/lib/components/ui/popover/index.ts | 2 +- .../ui/popover/popover-content.svelte | 12 +- .../ui/popover/popover-trigger.svelte | 2 +- .../ui/radio-group/radio-group-item.svelte | 4 +- .../ui/radio-group/radio-group.svelte | 4 +- .../lib/components/ui/scroll-area/index.ts | 2 +- .../scroll-area/scroll-area-scrollbar.svelte | 6 +- .../ui/scroll-area/scroll-area.svelte | 6 +- .../ui/src/lib/components/ui/select/index.ts | 15 +- .../ui/select/select-content.svelte | 17 +- .../ui/select/select-group-heading.svelte | 6 +- .../components/ui/select/select-item.svelte | 14 +- .../components/ui/select/select-label.svelte | 4 +- .../select/select-scroll-down-button.svelte | 4 +- .../ui/select/select-scroll-up-button.svelte | 4 +- .../ui/select/select-separator.svelte | 4 +- .../ui/select/select-trigger.svelte | 6 +- .../components/ui/separator/separator.svelte | 4 +- tools/ui/src/lib/components/ui/sheet/index.ts | 12 +- .../components/ui/sheet/sheet-content.svelte | 24 +- .../ui/sheet/sheet-description.svelte | 4 +- .../components/ui/sheet/sheet-footer.svelte | 4 +- .../components/ui/sheet/sheet-header.svelte | 6 +- .../components/ui/sheet/sheet-overlay.svelte | 4 +- .../components/ui/sheet/sheet-title.svelte | 4 +- .../components/ui/skeleton/skeleton.svelte | 2 +- .../lib/components/ui/switch/switch.svelte | 6 +- .../lib/components/ui/table/table-body.svelte | 4 +- .../components/ui/table/table-caption.svelte | 4 +- .../lib/components/ui/table/table-cell.svelte | 4 +- .../components/ui/table/table-footer.svelte | 4 +- .../lib/components/ui/table/table-head.svelte | 4 +- .../components/ui/table/table-header.svelte | 4 +- .../lib/components/ui/table/table-row.svelte | 4 +- .../src/lib/components/ui/table/table.svelte | 6 +- .../components/ui/textarea/textarea.svelte | 2 +- .../ui/src/lib/components/ui/tooltip/index.ts | 4 +- .../ui/tooltip/tooltip-content.svelte | 12 +- tools/ui/src/lib/components/ui/utils.ts | 2 +- tools/ui/src/lib/constants/agentic.ts | 30 +- tools/ui/src/lib/constants/api-endpoints.ts | 8 +- tools/ui/src/lib/constants/attachment-menu.ts | 72 +- .../ui/src/lib/constants/binary-detection.ts | 4 +- tools/ui/src/lib/constants/built-in-tools.ts | 33 +- tools/ui/src/lib/constants/chat-commands.ts | 20 +- tools/ui/src/lib/constants/error.ts | 14 +- tools/ui/src/lib/constants/icons.ts | 16 +- tools/ui/src/lib/constants/mcp-resource.ts | 2 +- tools/ui/src/lib/constants/mcp.ts | 18 +- tools/ui/src/lib/constants/mention-badge.ts | 8 +- tools/ui/src/lib/constants/model-loading.ts | 4 +- tools/ui/src/lib/constants/pwa.ts | 168 +-- .../lib/constants/reasoning-effort-tokens.ts | 6 +- .../ui/src/lib/constants/reasoning-effort.ts | 20 +- .../lib/constants/recommended-mcp-servers.ts | 28 +- tools/ui/src/lib/constants/routes.ts | 34 +- tools/ui/src/lib/constants/sandbox.ts | 18 +- tools/ui/src/lib/constants/settings-keys.ts | 120 +- .../ui/src/lib/constants/settings-registry.ts | 1151 ++++++++--------- .../src/lib/constants/supported-file-types.ts | 240 ++-- tools/ui/src/lib/constants/svg-blocks.ts | 4 +- .../ui/src/lib/constants/title-generation.ts | 4 +- tools/ui/src/lib/constants/ui.ts | 14 +- tools/ui/src/lib/constants/uri-template.ts | 32 +- tools/ui/src/lib/constants/url.ts | 61 +- .../src/lib/contexts/chat-actions.context.ts | 2 +- .../contexts/chat-settings-config.context.ts | 2 +- .../src/lib/contexts/message-edit.context.ts | 2 +- .../lib/hooks/use-attachment-menu.svelte.ts | 12 +- .../src/lib/hooks/use-auto-scroll.svelte.ts | 15 +- .../lib/hooks/use-chat-form-pickers.svelte.ts | 120 +- .../use-chat-screen-active-model.svelte.ts | 27 +- .../use-chat-screen-drag-and-drop.svelte.ts | 10 +- .../use-chat-screen-file-upload.svelte.ts | 31 +- .../hooks/use-chat-screen-scroll.svelte.ts | 3 +- .../src/lib/hooks/use-context-gauge.svelte.ts | 137 +- .../lib/hooks/use-debounced-search.svelte.ts | 13 +- .../lib/hooks/use-draft-messages.svelte.ts | 5 +- .../hooks/use-keyboard-shortcuts.svelte.ts | 2 +- .../lib/hooks/use-marquee-selection.svelte.ts | 35 +- .../hooks/use-message-edit-context.svelte.ts | 47 +- .../lib/hooks/use-models-selector.svelte.ts | 110 +- .../lib/hooks/use-picker-navigation.svelte.ts | 19 +- .../lib/hooks/use-processing-state.svelte.ts | 49 +- tools/ui/src/lib/hooks/use-pwa.svelte.ts | 15 +- .../lib/hooks/use-reasoning-menu.svelte.ts | 60 +- .../lib/hooks/use-scroll-active-row.svelte.ts | 6 + .../lib/hooks/use-scroll-carousel.svelte.ts | 14 +- .../hooks/use-settings-navigation.svelte.ts | 15 +- .../src/lib/hooks/use-tools-panel.svelte.ts | 34 +- tools/ui/src/lib/services/chat.service.ts | 326 +++-- tools/ui/src/lib/services/database.service.ts | 121 +- tools/ui/src/lib/services/mcp.service.ts | 256 ++-- .../ui/src/lib/services/migration.service.ts | 174 ++- tools/ui/src/lib/services/models.service.ts | 34 +- .../services/parameter-sync.service.spec.ts | 138 +- .../lib/services/parameter-sync.service.ts | 19 +- tools/ui/src/lib/services/props.service.ts | 2 + tools/ui/src/lib/services/sandbox-harness.ts | 2 +- tools/ui/src/lib/services/sandbox.service.ts | 17 +- tools/ui/src/lib/services/tools.service.ts | 32 +- tools/ui/src/lib/stores/agentic.svelte.ts | 299 +++-- tools/ui/src/lib/stores/build-info.svelte.ts | 3 + tools/ui/src/lib/stores/chat.svelte.ts | 1069 +++++++++------ .../lib/stores/context-gauge-popup.svelte.ts | 11 +- .../ui/src/lib/stores/conversations.svelte.ts | 149 ++- tools/ui/src/lib/stores/device.svelte.ts | 9 +- .../src/lib/stores/draft-messages.svelte.ts | 7 +- .../ui/src/lib/stores/mcp-resources.svelte.ts | 125 +- tools/ui/src/lib/stores/mcp.svelte.ts | 373 ++++-- tools/ui/src/lib/stores/models.svelte.ts | 141 +- tools/ui/src/lib/stores/permissions.svelte.ts | 2 +- tools/ui/src/lib/stores/persisted.svelte.ts | 1 + tools/ui/src/lib/stores/server.svelte.ts | 8 +- tools/ui/src/lib/stores/settings.svelte.ts | 25 +- tools/ui/src/lib/stores/tools.svelte.ts | 125 +- tools/ui/src/lib/stores/version.svelte.ts | 3 + tools/ui/src/lib/types/agentic.d.ts | 6 +- tools/ui/src/lib/types/api.d.ts | 4 +- tools/ui/src/lib/types/chat.d.ts | 2 +- tools/ui/src/lib/types/database.d.ts | 2 +- tools/ui/src/lib/types/mcp.d.ts | 16 +- tools/ui/src/lib/types/settings.d.ts | 10 +- tools/ui/src/lib/types/tools.d.ts | 2 +- tools/ui/src/lib/utils/abort.ts | 6 +- tools/ui/src/lib/utils/agentic.ts | 66 +- tools/ui/src/lib/utils/api-fetch.ts | 19 +- tools/ui/src/lib/utils/api-headers.ts | 4 +- tools/ui/src/lib/utils/api-key-validation.ts | 3 +- tools/ui/src/lib/utils/attachment-display.ts | 26 +- tools/ui/src/lib/utils/audio-recording.ts | 20 +- tools/ui/src/lib/utils/branching.ts | 23 +- tools/ui/src/lib/utils/cache-ttl.ts | 29 +- tools/ui/src/lib/utils/cap-img-size.ts | 5 +- .../utils/chat-template-thinking-detector.ts | 18 +- tools/ui/src/lib/utils/clipboard.ts | 65 +- tools/ui/src/lib/utils/code.ts | 24 +- tools/ui/src/lib/utils/command-token.ts | 6 +- tools/ui/src/lib/utils/compute-line-diff.ts | 29 +- tools/ui/src/lib/utils/config-helpers.ts | 2 + .../lib/utils/contenteditable-tokenizer.ts | 166 ++- .../src/lib/utils/convert-files-to-extra.ts | 67 +- tools/ui/src/lib/utils/file-preview.ts | 3 + tools/ui/src/lib/utils/file-type.ts | 10 +- tools/ui/src/lib/utils/formatters.ts | 12 +- tools/ui/src/lib/utils/glob-search.ts | 48 +- tools/ui/src/lib/utils/headers.ts | 1 + tools/ui/src/lib/utils/heic-to-jpeg.ts | 7 +- tools/ui/src/lib/utils/jpeg-orientation.ts | 14 +- tools/ui/src/lib/utils/latex-protection.ts | 23 +- tools/ui/src/lib/utils/mcp.ts | 72 +- tools/ui/src/lib/utils/mention-badge.ts | 9 +- tools/ui/src/lib/utils/mention-token.ts | 14 +- .../src/lib/utils/modality-file-validation.ts | 18 +- .../src/lib/utils/parse-exec-shell-error.ts | 3 + .../src/lib/utils/parse-exec-shell-status.ts | 6 +- .../src/lib/utils/parse-partial-json-args.ts | 21 +- tools/ui/src/lib/utils/path-display.ts | 24 +- tools/ui/src/lib/utils/pdf-processing.ts | 10 +- tools/ui/src/lib/utils/portal-to-body.ts | 1 + .../src/lib/utils/process-uploaded-files.ts | 24 +- tools/ui/src/lib/utils/progress.ts | 3 +- tools/ui/src/lib/utils/sanitize-svg.ts | 2 +- tools/ui/src/lib/utils/sanitize.ts | 4 +- tools/ui/src/lib/utils/search-results.ts | 41 +- tools/ui/src/lib/utils/source-history.ts | 8 + tools/ui/src/lib/utils/sse.ts | 9 + tools/ui/src/lib/utils/stream-identity.ts | 2 + tools/ui/src/lib/utils/svg-shadow.ts | 1 + tools/ui/src/lib/utils/svg-to-png.ts | 3 + tools/ui/src/lib/utils/text-files.ts | 2 +- tools/ui/src/lib/utils/text.ts | 1 + tools/ui/src/lib/utils/tool-call-meta.ts | 3 + tools/ui/src/lib/utils/uri-template.ts | 7 +- tools/ui/src/lib/utils/url.ts | 2 +- tools/ui/src/lib/utils/webp-to-png.ts | 3 + tools/ui/src/lib/utils/working-directory.ts | 49 +- tools/ui/src/routes/(chat)/+page.svelte | 8 +- .../src/routes/(chat)/chat/[id]/+page.svelte | 19 +- tools/ui/src/routes/+error.svelte | 4 +- tools/ui/src/routes/+layout.svelte | 50 +- tools/ui/src/routes/search/+page.svelte | 11 +- tools/ui/src/routes/settings/+layout.svelte | 4 +- .../routes/settings/[[section]]/+page.svelte | 6 +- tools/ui/svelte.config.js | 27 +- .../client/agentic-stream.perf.svelte.test.ts | 118 +- .../tests/client/apikey-splash.svelte.test.ts | 8 +- .../tests/client/cap-img-size.svelte.test.ts | 11 +- ...form-contenteditable-blocks.svelte.test.ts | 29 +- ...t-form-contenteditable-undo.svelte.test.ts | 43 +- .../chat-form-contenteditable.svelte.test.ts | 157 ++- .../chat-form-enter-code-block.svelte.test.ts | 22 +- ...at-form-mention-picker-gate.svelte.test.ts | 19 +- .../chat-form-slash-commands.svelte.test.ts | 23 +- .../collapsible-lazy-body.svelte.test.ts | 13 +- .../components/AgenticPerfWrapper.svelte | 4 +- .../ChatFormContenteditableHarness.svelte | 2 +- .../components/ChatFormPickersHarness.svelte | 26 +- .../components/ChatFormTestWrapper.svelte | 2 +- .../components/ChatMessagesPerfWrapper.svelte | 2 +- .../CollapsibleLazyBodyHarness.svelte | 2 +- .../components/McpServerFormWrapper.svelte | 2 +- .../client/components/TestWrapper.svelte | 2 +- .../components/agentic-perf-state.svelte.ts | 4 +- .../conversation-import-db.svelte.test.ts | 19 +- .../client/mcp-display-name.svelte.test.ts | 37 +- .../client/mcp-server-form.svelte.test.ts | 15 +- tools/ui/tests/client/page.svelte.test.ts | 4 +- .../client/picker-list-scroll.svelte.test.ts | 9 +- .../client/sandbox.service.svelte.test.ts | 11 +- ...settings-raw-text-migration.svelte.test.ts | 7 +- ...ettings-registry-invariants.svelte.test.ts | 18 +- .../client/ui-settings-sync.svelte.test.ts | 13 +- .../update-message-in-place.svelte.test.ts | 16 +- tools/ui/tests/e2e/pwa.e2e.ts | 8 + .../tests/stories/ChatMessage.stories.svelte | 87 +- .../stories/ChatScreenForm.stories.svelte | 27 +- .../stories/MarkdownContent.stories.svelte | 39 +- .../stories/ModelsSelector.stories.svelte | 79 +- .../stories/PwaRefreshAlert.stories.svelte | 11 +- .../stories/SidebarNavigation.stories.svelte | 27 +- .../a11y/ActionIcon.a11y.stories.svelte | 6 +- .../ChatMessageStatistics.a11y.stories.svelte | 18 +- .../a11y/ChatScreenForm.a11y.stories.svelte | 7 +- ...rizontalScrollCarousel.a11y.stories.svelte | 4 +- ...gationConversationItem.a11y.stories.svelte | 12 +- .../tests/stories/fixtures/storybook-mocks.ts | 33 +- tools/ui/tests/unit/abort.test.ts | 4 +- tools/ui/tests/unit/agentic-hotpath.bench.ts | 39 +- tools/ui/tests/unit/agentic-sections.test.ts | 108 +- tools/ui/tests/unit/agentic-strip.test.ts | 9 +- .../tests/unit/assistant-raw-output.test.ts | 31 +- .../tests/unit/classify-tool-result.test.ts | 8 +- tools/ui/tests/unit/clipboard.test.ts | 98 +- tools/ui/tests/unit/code.test.ts | 15 +- tools/ui/tests/unit/command-token.test.ts | 20 +- tools/ui/tests/unit/compute-line-diff.test.ts | 70 +- .../unit/contenteditable-tokenizer.test.ts | 12 +- .../unit/contenteditable-word-jump.test.ts | 5 +- tools/ui/tests/unit/continue-intent.test.ts | 31 +- .../ui/tests/unit/conversation-import.test.ts | 32 +- tools/ui/tests/unit/favicon-colorize.test.ts | 47 +- .../tests/unit/glob-search-children.test.ts | 11 +- tools/ui/tests/unit/headers.test.ts | 7 +- tools/ui/tests/unit/jpeg-orientation.test.ts | 5 +- tools/ui/tests/unit/latex-protection.test.ts | 6 +- .../unit/mcp-default-overrides-merge.test.ts | 45 +- .../tests/unit/mcp-override-fallback.test.ts | 45 +- .../ui/tests/unit/mcp-servers-default.test.ts | 2 +- tools/ui/tests/unit/mcp-service.test.ts | 112 +- tools/ui/tests/unit/mention-badge.test.ts | 38 +- tools/ui/tests/unit/mention-token.test.ts | 26 +- tools/ui/tests/unit/model-id-parser.test.ts | 6 +- tools/ui/tests/unit/model-names.test.ts | 2 +- .../unit/parse-exec-shell-status.test.ts | 12 +- .../unit/parse-mcp-server-settings.test.ts | 23 +- .../tests/unit/parse-toolcalls-memo.test.ts | 56 +- .../unit/partial-tool-call-cleanup.test.ts | 36 +- tools/ui/tests/unit/pwa.spec.ts | 10 +- tools/ui/tests/unit/reasoning-context.test.ts | 37 +- tools/ui/tests/unit/redact.test.ts | 2 +- tools/ui/tests/unit/request-helpers.test.ts | 21 +- tools/ui/tests/unit/sanitize-headers.test.ts | 24 +- .../tests/unit/search-results-fixture.test.ts | 6 +- tools/ui/tests/unit/search-results.test.ts | 7 +- tools/ui/tests/unit/source-history.test.ts | 69 +- tools/ui/tests/unit/sse.test.ts | 17 +- tools/ui/tests/unit/stream-discovery.test.ts | 13 +- tools/ui/tests/unit/stream-resume.test.ts | 11 +- tools/ui/tests/unit/tool-call-meta.test.ts | 6 +- tools/ui/tests/unit/tool-calls.test.ts | 124 +- tools/ui/tests/unit/uri-template.test.ts | 25 +- tools/ui/tests/unit/working-directory.test.ts | 53 +- tools/ui/vite.config.ts | 87 +- tools/ui/vitest-setup-client.ts | 32 +- 538 files changed, 8806 insertions(+), 6036 deletions(-) diff --git a/tools/ui/eslint.config.js b/tools/ui/eslint.config.js index fcbf7ee95..c65484048 100644 --- a/tools/ui/eslint.config.js +++ b/tools/ui/eslint.config.js @@ -1,14 +1,15 @@ // For more info, see https://github.com/storybookjs/eslint-plugin-storybook#configuration-flat-config-format -import storybook from 'eslint-plugin-storybook'; - -import prettier from 'eslint-config-prettier'; +import svelteConfig from './svelte.config.js'; import { includeIgnoreFile } from '@eslint/compat'; import js from '@eslint/js'; +import prettier from 'eslint-config-prettier'; +import perfectionist from 'eslint-plugin-perfectionist'; +import simpleImportSort from 'eslint-plugin-simple-import-sort'; +import storybook from 'eslint-plugin-storybook'; import svelte from 'eslint-plugin-svelte'; import globals from 'globals'; import { fileURLToPath } from 'node:url'; import ts from 'typescript-eslint'; -import svelteConfig from './svelte.config.js'; const gitignorePath = fileURLToPath(new URL('./.gitignore', import.meta.url)); @@ -21,32 +22,67 @@ export default ts.config( ...svelte.configs.prettier, { languageOptions: { globals: { ...globals.browser, ...globals.node } }, + plugins: { perfectionist, 'simple-import-sort': simpleImportSort }, rules: { - // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. - // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors - 'no-undef': 'off', - 'svelte/no-at-html-tags': 'off', - // This app uses hash-based routing (#/) where resolve() from $app/paths does not apply - 'svelte/no-navigation-without-resolve': 'off', - // Snippet bodies often ignore one or more of the parent's params // (e.g. `{#snippet children(_meta, ctx)}` when only ctx is read). '@typescript-eslint/no-unused-vars': [ 'error', { argsIgnorePattern: '^_', varsIgnorePattern: '^_' } ], - // Enforce empty line at end of file - 'eol-last': 'error' + 'eol-last': 'error', + // typescript-eslint strongly recommend that you do not use the no-undef lint rule on TypeScript projects. + // see: https://typescript-eslint.io/troubleshooting/faqs/eslint/#i-get-errors-from-the-no-undef-rule-about-global-variables-not-being-defined-even-though-there-are-no-typescript-errors + 'no-undef': 'off', + + 'padding-line-between-statements': [ + 'error', + // Blank line between function/class declarations. + { blankLine: 'always', next: ['function', 'class'], prev: ['function', 'class'] }, + // Blank line around if blocks (if/else and else if stay one statement). + { blankLine: 'always', next: '*', prev: 'if' }, + { blankLine: 'always', next: 'if', prev: '*' }, + // Blank line after the last declaration in a group. Because the 'never' + // rules below are scoped per declaration kind, a const group and a let + // group get separated by a blank line, while same-kind declarations stay + // together. + { blankLine: 'always', next: '*', prev: ['const', 'let', 'var'] }, + // No blank line between consecutive declarations of the same kind (kept + // last so each takes precedence over the always rule above for matching + // declaration pairs). + { blankLine: 'never', next: 'const', prev: 'const' }, + { blankLine: 'never', next: 'let', prev: 'let' }, + { blankLine: 'never', next: 'var', prev: 'var' }, + // Blank line before a statement that follows another statement in the block + // (works for return/throw/break/continue). A blank line for a terminal + // statement that opens a block body can't be enforced here: Prettier removes + // the leading blank line of a block, so the two formatters would fight. + { blankLine: 'always', next: ['return', 'throw', 'break', 'continue'], prev: '*' } + ], + + 'perfectionist/sort-objects': ['error', { type: 'natural' }], + + // Alphabetical order for variable declarations and object keys + 'perfectionist/sort-variable-declarations': ['error', { type: 'natural' }], + + // Sort imports alphabetically by module path, and sort named members within + // each statement. A single catch-all group keeps the list flat (no blank-line + // grouping); Prettier normalizes comma spacing afterwards. + 'simple-import-sort/imports': ['error', { groups: [['.*']] }], + 'svelte/no-at-html-tags': 'off', + + // This app uses hash-based routing (#/) where resolve() from $app/paths does not apply + 'svelte/no-navigation-without-resolve': 'off' } }, { files: ['**/*.svelte', '**/*.svelte.ts', '**/*.svelte.js'], languageOptions: { parserOptions: { - projectService: true, extraFileExtensions: ['.svelte'], parser: ts.parser, + projectService: true, svelteConfig } } diff --git a/tools/ui/package-lock.json b/tools/ui/package-lock.json index 976ed050f..f9b793b29 100644 --- a/tools/ui/package-lock.json +++ b/tools/ui/package-lock.json @@ -39,6 +39,8 @@ "dompurify": "3.4.13", "eslint": "9.39.4", "eslint-config-prettier": "10.1.8", + "eslint-plugin-perfectionist": "^5.10.1", + "eslint-plugin-simple-import-sort": "^14.0.0", "eslint-plugin-storybook": "10.5.6", "eslint-plugin-svelte": "3.19.0", "fflate": "0.8.3", @@ -9281,6 +9283,226 @@ "eslint": ">=7.0.0" } }, + "node_modules/eslint-plugin-perfectionist": { + "version": "5.10.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-5.10.1.tgz", + "integrity": "sha512-Kprsp9Us0GqAesYaAIzUViw57xYp5WBqzXrcE0Mtww++E5fexWXYBipMuuD7yvyH4vvpBH0+oJ+OMAmZ0oYXkw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/utils": "^8.65.0", + "natural-orderby": "^5.0.0" + }, + "engines": { + "node": "^20.0.0 || >=22.0.0" + }, + "peerDependencies": { + "eslint": "^8.45.0 || ^9.0.0 || ^10.0.0" + } + }, + "node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/project-service": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.66.0.tgz", + "integrity": "sha512-7MthGPTt4BP69lSryqpqq8HQqxuzynssckL/jyDyk3+TNMQ3y2jFWkptCrktWvBrP+EH787Nl5N5Qpw7WZg+5g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/tsconfig-utils": "^8.66.0", + "@typescript-eslint/types": "^8.66.0", + "debug": "^4.4.3" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/scope-manager": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.66.0.tgz", + "integrity": "sha512-8TGcH25j9zqJ/IULB/ppyhRvxA8QYfFEZ7nfbg6/BN9spDgb8fPWQXlE5l8TWBL50EtUx007uZ1o9VOwrq2/9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/tsconfig-utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.66.0.tgz", + "integrity": "sha512-9D5gLYZG4rOjcoag8MQ/fWI8WqA9wcPDyOGyWtWFhvM1lHRbliqUSPIY5J3zqCU1tvSwzXxnnjhQhz5Ne7mJ4g==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/types": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.66.0.tgz", + "integrity": "sha512-H6gcYaSDOyvL3AD/jHUtUFo2jqGgn/F6nuyuZSu0QTesxL+cP4dQoIMrODRofuJC09g64+WgZ6tE19Y1N2YIFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/typescript-estree": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.66.0.tgz", + "integrity": "sha512-8/x4INiiQb10jGgXYD7116/zQ+OL84ZIFn0za68wwFHCanT/VLbBEroWht8RV8fn0/ZCAoazHLQgwUC0UQcDfg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/project-service": "8.66.0", + "@typescript-eslint/tsconfig-utils": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/visitor-keys": "8.66.0", + "debug": "^4.4.3", + "minimatch": "^10.2.2", + "semver": "^7.7.3", + "tinyglobby": "^0.2.15", + "ts-api-utils": "^2.5.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/utils": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.66.0.tgz", + "integrity": "sha512-jasearZPolBw5NJNYGMwxzHMF83niVWmMU1VdHzG1CyfI2VS7f7nZltnKtHcg20hW+7Uo5GfK4MeDPoU3qI8EA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@eslint-community/eslint-utils": "^4.9.1", + "@typescript-eslint/scope-manager": "8.66.0", + "@typescript-eslint/types": "8.66.0", + "@typescript-eslint/typescript-estree": "8.66.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", + "typescript": ">=4.8.4 <6.1.0" + } + }, + "node_modules/eslint-plugin-perfectionist/node_modules/@typescript-eslint/visitor-keys": { + "version": "8.66.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.66.0.tgz", + "integrity": "sha512-dkKR8q+lKciskj1Y3vthHktl+3cMLWGyVUP23bRiPZ5O9BRT++4EqDDV+TVeIKBL1VXVEqrJlz8MYbcnvJcAlg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@typescript-eslint/types": "8.66.0", + "eslint-visitor-keys": "^5.0.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/eslint-plugin-perfectionist/node_modules/balanced-match": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", + "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "18 || 20 || >=22" + } + }, + "node_modules/eslint-plugin-perfectionist/node_modules/brace-expansion": { + "version": "5.0.9", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.9.tgz", + "integrity": "sha512-ScQ4IuvIEF1TMlP7Zt+vjJ//9zlPb2SDcxWxM3bk8s6t6GGdJ7KO1dCcTidOPJKePW30LE/2cT7wCyPho9/Wxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^4.0.2" + }, + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/eslint-plugin-perfectionist/node_modules/eslint-visitor-keys": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-5.0.1.tgz", + "integrity": "sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": "^20.19.0 || ^22.13.0 || >=24" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-perfectionist/node_modules/minimatch": { + "version": "10.2.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.6.tgz", + "integrity": "sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==", + "dev": true, + "license": "BlueOak-1.0.0", + "dependencies": { + "brace-expansion": "^5.0.8" + }, + "engines": { + "node": "18 || 20 || >=22" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/eslint-plugin-simple-import-sort": { + "version": "14.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-14.0.0.tgz", + "integrity": "sha512-NUJO0+XFCkk+o5EsAJruTgnfMEpeWrPWeJS15UVF60GgXmqz1BJ9/3hzlvG7lkL8Bubzos5cCLptThbFfPnSMQ==", + "dev": true, + "license": "MIT", + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, "node_modules/eslint-plugin-storybook": { "version": "10.5.6", "resolved": "https://registry.npmjs.org/eslint-plugin-storybook/-/eslint-plugin-storybook-10.5.6.tgz", @@ -13196,6 +13418,16 @@ "dev": true, "license": "MIT" }, + "node_modules/natural-orderby": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/natural-orderby/-/natural-orderby-5.0.0.tgz", + "integrity": "sha512-kKHJhxwpR/Okycz4HhQKKlhWe4ASEfPgkSWNmKFHd7+ezuQlxkA5cM3+XkBPvm1gmHen3w53qsYAv+8GwRrBlg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", diff --git a/tools/ui/package.json b/tools/ui/package.json index 7f042f415..f6d6880d7 100644 --- a/tools/ui/package.json +++ b/tools/ui/package.json @@ -12,7 +12,7 @@ "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "reset": "rm -rf .svelte-kit node_modules", - "format": "prettier --write .", + "format": "eslint --fix . && prettier --write .", "lint": "prettier --check . && eslint .", "test": "npm run test:ui -- --run && npm run test:client -- --run && npm run test:unit -- --run && npm run test:e2e", "test:e2e": "playwright test", @@ -36,6 +36,7 @@ "@playwright/test": "1.56.1", "@storybook/addon-a11y": "10.5.6", "@storybook/addon-docs": "10.5.6", + "@storybook/addon-mcp": "0.7.0", "@storybook/addon-svelte-csf": "5.1.2", "@storybook/addon-vitest": "10.5.6", "@storybook/sveltekit": "10.5.6", @@ -57,6 +58,8 @@ "dompurify": "3.4.13", "eslint": "9.39.4", "eslint-config-prettier": "10.1.8", + "eslint-plugin-perfectionist": "^5.10.1", + "eslint-plugin-simple-import-sort": "^14.0.0", "eslint-plugin-storybook": "10.5.6", "eslint-plugin-svelte": "3.19.0", "fflate": "0.8.3", @@ -99,8 +102,7 @@ "vite-plugin-devtools-json": "0.2.1", "vitest": "4.1.10", "vitest-browser-svelte": "2.1.1", - "workbox-window": "7.4.1", - "@storybook/addon-mcp": "0.7.0" + "workbox-window": "7.4.1" }, "overrides": { "cookie": "1.1.1", diff --git a/tools/ui/playwright.config.ts b/tools/ui/playwright.config.ts index 55bf38514..057ed416d 100644 --- a/tools/ui/playwright.config.ts +++ b/tools/ui/playwright.config.ts @@ -1,31 +1,31 @@ import { defineConfig, devices } from '@playwright/test'; export default defineConfig({ - testDir: 'tests/e2e', - testMatch: ['**/*.e2e.ts'], - timeout: 30000, expect: { timeout: 5000 }, - fullyParallel: true, forbidOnly: !!process.env.CI, - retries: process.env.CI ? 2 : 0, - workers: process.env.CI ? 1 : undefined, - reporter: 'line', - use: { - baseURL: 'http://localhost:8181', - trace: 'on-first-retry' - }, + fullyParallel: true, projects: [ { name: 'chromium', use: { ...devices['Desktop Chrome'] } } ], + reporter: 'line', + retries: process.env.CI ? 2 : 0, + testDir: 'tests/e2e', + testMatch: ['**/*.e2e.ts'], + timeout: 30000, + use: { + baseURL: 'http://localhost:8181', + trace: 'on-first-retry' + }, webServer: { command: 'npm run build && npx http-server ./dist -p 8181', port: 8181, - timeout: 120000, - reuseExistingServer: !process.env.CI - } + reuseExistingServer: !process.env.CI, + timeout: 120000 + }, + workers: process.env.CI ? 1 : undefined }); diff --git a/tools/ui/pwa-assets-dark.config.ts b/tools/ui/pwa-assets-dark.config.ts index 358c0ebc0..1446b47a8 100644 --- a/tools/ui/pwa-assets-dark.config.ts +++ b/tools/ui/pwa-assets-dark.config.ts @@ -1,6 +1,6 @@ -import { defineConfig } from '@vite-pwa/assets-generator/config'; -import { FAVICON_COLORS, PWA_ASSET_GENERATOR } from './src/lib/constants/pwa'; import { writeThemeFavicons } from './scripts/favicon-colorize'; +import { FAVICON_COLORS, PWA_ASSET_GENERATOR } from './src/lib/constants/pwa'; +import { defineConfig } from '@vite-pwa/assets-generator/config'; writeThemeFavicons(FAVICON_COLORS.LIGHT, FAVICON_COLORS.DARK, { padding: PWA_ASSET_GENERATOR.FAVICON_PADDING @@ -10,18 +10,18 @@ export default defineConfig({ headLinkOptions: { preset: '2023' }, + images: ['static/favicon-dark.svg'], preset: { - transparent: { - sizes: [], - favicons: [[48, 'favicon-dark.ico']], - padding: PWA_ASSET_GENERATOR.FAVICON_PADDING + apple: { + sizes: [] }, maskable: { sizes: [] }, - apple: { + transparent: { + favicons: [[48, 'favicon-dark.ico']], + padding: PWA_ASSET_GENERATOR.FAVICON_PADDING, sizes: [] } - }, - images: ['static/favicon-dark.svg'] + } }); diff --git a/tools/ui/pwa-assets.config.ts b/tools/ui/pwa-assets.config.ts index b69884d94..5fed0a595 100644 --- a/tools/ui/pwa-assets.config.ts +++ b/tools/ui/pwa-assets.config.ts @@ -1,3 +1,11 @@ +import { writeThemeFavicons } from './scripts/favicon-colorize'; +import { + FAVICON_COLORS, + PWA_ASSET_GENERATOR, + PWA_GENERATOR_DEVICES, + THEME_COLORS +} from './src/lib/constants/pwa'; +import { SplashOrientation } from './src/lib/enums/splash.enums'; import { combinePresetAndAppleSplashScreens, defineConfig, @@ -5,14 +13,6 @@ import { } from '@vite-pwa/assets-generator/config'; import { readFileSync } from 'node:fs'; import { resolve } from 'node:path'; -import { - THEME_COLORS, - PWA_GENERATOR_DEVICES, - PWA_ASSET_GENERATOR, - FAVICON_COLORS -} from './src/lib/constants/pwa'; -import { SplashOrientation } from './src/lib/enums/splash.enums'; -import { writeThemeFavicons } from './scripts/favicon-colorize'; writeThemeFavicons(FAVICON_COLORS.LIGHT, FAVICON_COLORS.DARK, { padding: PWA_ASSET_GENERATOR.FAVICON_PADDING @@ -22,6 +22,7 @@ export default defineConfig({ headLinkOptions: { preset: PWA_ASSET_GENERATOR.LINK_PRESET }, + images: ['static/favicon.svg'], preset: combinePresetAndAppleSplashScreens( { ...minimal2023Preset, @@ -32,37 +33,37 @@ export default defineConfig({ } }, { - padding: PWA_ASSET_GENERATOR.SPLASH_PADDING, - resizeOptions: { - background: THEME_COLORS.BACKGROUND_LIGHT, - fit: PWA_ASSET_GENERATOR.FIT_MODE - }, - darkResizeOptions: { - background: THEME_COLORS.BACKGROUND_DARK, - fit: PWA_ASSET_GENERATOR.FIT_MODE - }, darkImageResolver: async (imageName: string) => { if (imageName.endsWith('favicon.svg')) { return readFileSync(resolve('static/favicon-dark.svg')); } }, + darkResizeOptions: { + background: THEME_COLORS.BACKGROUND_DARK, + fit: PWA_ASSET_GENERATOR.FIT_MODE + }, linkMediaOptions: { - log: true, addMediaScreen: PWA_ASSET_GENERATOR.ADD_MEDIA_SCREEN, basePath: PWA_ASSET_GENERATOR.BASE_PATH, + log: true, xhtml: PWA_ASSET_GENERATOR.XHTML }, - png: { - compressionLevel: PWA_ASSET_GENERATOR.PNG_COMPRESSION_LEVEL, - quality: PWA_ASSET_GENERATOR.PNG_QUALITY - }, name: (landscape, size, dark) => { const orientation = landscape ? SplashOrientation.LANDSCAPE : SplashOrientation.PORTRAIT; const darkPrefix = dark ? PWA_ASSET_GENERATOR.DARK_PREFIX : ''; + return `apple-splash-${orientation}-${darkPrefix}${size.width}x${size.height}.png`; + }, + padding: PWA_ASSET_GENERATOR.SPLASH_PADDING, + png: { + compressionLevel: PWA_ASSET_GENERATOR.PNG_COMPRESSION_LEVEL, + quality: PWA_ASSET_GENERATOR.PNG_QUALITY + }, + resizeOptions: { + background: THEME_COLORS.BACKGROUND_LIGHT, + fit: PWA_ASSET_GENERATOR.FIT_MODE } }, PWA_GENERATOR_DEVICES - ), - images: ['static/favicon.svg'] + ) }); diff --git a/tools/ui/scripts/favicon-colorize.ts b/tools/ui/scripts/favicon-colorize.ts index e1872b777..54a951296 100644 --- a/tools/ui/scripts/favicon-colorize.ts +++ b/tools/ui/scripts/favicon-colorize.ts @@ -4,12 +4,10 @@ import { fileURLToPath } from 'node:url'; const HERE = dirname(fileURLToPath(import.meta.url)); const PROJECT_ROOT = resolve(HERE, '..'); - const DEFAULT_LOGO = resolve(PROJECT_ROOT, 'src/lib/assets/logo.svg'); const DEFAULT_OUT_DIR = resolve(PROJECT_ROOT, 'static'); const DEFAULT_OUT_LIGHT = resolve(DEFAULT_OUT_DIR, 'favicon.svg'); const DEFAULT_OUT_DARK = resolve(DEFAULT_OUT_DIR, 'favicon-dark.svg'); - const CURRENT_COLOR = 'currentColor'; export interface ColorizedFavicon { @@ -39,8 +37,8 @@ export function colorizeFaviconSvg( darkColor: string ): ColorizedFavicon { return { - light: svg.replaceAll(CURRENT_COLOR, lightColor), - dark: svg.replaceAll(CURRENT_COLOR, darkColor) + dark: svg.replaceAll(CURRENT_COLOR, darkColor), + light: svg.replaceAll(CURRENT_COLOR, lightColor) }; } @@ -54,33 +52,40 @@ export function padFaviconSvg(svg: string, padding: number): string { if (!(padding > 0) || padding >= 1) return svg; const viewBoxMatch = svg.match(/viewBox\s*=\s*["']([^"']+)["']/i); + if (!viewBoxMatch) return svg; const parts = viewBoxMatch[1] .trim() .split(/[\s,]+/) .map(Number); + if (parts.length !== 4 || parts.some((n) => !Number.isFinite(n))) return svg; const [, , width, height] = parts; + if (width <= 0 || height <= 0) return svg; const scale = 1 - padding; const translateX = (padding * width) / 2; const translateY = (padding * height) / 2; - const openTagStart = svg.search(/', openTagStart); + if (openTagEnd === -1) return svg; + const closeStart = svg.lastIndexOf('`; + return `${openTag}${group}${inner}${closeTag}`; } @@ -93,14 +98,15 @@ export function writeThemeFavicons( lightColor: string, darkColor: string, { - sourcePath = DEFAULT_LOGO, - lightOutPath = DEFAULT_OUT_LIGHT, darkOutPath = DEFAULT_OUT_DARK, - padding = 0 + lightOutPath = DEFAULT_OUT_LIGHT, + padding = 0, + sourcePath = DEFAULT_LOGO }: WriteThemeFaviconsOptions = {} ): void { const source = readFileSync(sourcePath, 'utf-8'); - const { light, dark } = colorizeFaviconSvg(source, lightColor, darkColor); + const { dark, light } = colorizeFaviconSvg(source, lightColor, darkColor); + mkdirSync(dirname(lightOutPath), { recursive: true }); writeFileSync(lightOutPath, padFaviconSvg(light, padding)); writeFileSync(darkOutPath, padFaviconSvg(dark, padding)); diff --git a/tools/ui/scripts/make-icons-circular.js b/tools/ui/scripts/make-icons-circular.js index 7dfd6521e..b4763c625 100644 --- a/tools/ui/scripts/make-icons-circular.js +++ b/tools/ui/scripts/make-icons-circular.js @@ -13,31 +13,28 @@ * maskable-icon and apple-touch-icon are left untouched. */ -import sharp from 'sharp'; import fs from 'fs'; import path from 'path'; +import sharp from 'sharp'; import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); - const STATIC_DIR = path.resolve(__dirname, '..', 'static'); - const paddingPct = process.argv.reduce((acc, arg, i, args) => { if (arg === '--padding-pct' && args[i + 1]) return parseFloat(args[i + 1]); + return acc; }, 0); - // Scale down the source image before cropping to circle const scalePct = process.argv.reduce((acc, arg, i, args) => { if (arg === '--scale-pct' && args[i + 1]) return parseFloat(args[i + 1]); + return acc; }, 85); // default 85% - icon fills 85% of the circular area - // Source for circular icons: the maskable icon (white bg, full logo) const sourceIcon = 'maskable-icon-512x512.png'; const targetIcons = ['pwa-64x64.png', 'pwa-192x192.png', 'pwa-512x512.png']; - // maskable-icon and apple-touch-icon stay square const untouchedIcons = ['maskable-icon-512x512.png', 'apple-touch-icon-180x180.png']; @@ -47,10 +44,13 @@ async function makeCircle(targetFilename) { if (!fs.existsSync(sourcePath)) { console.log(`⏭️ ${sourceIcon} not found, skipping`); + return; } + if (!fs.existsSync(targetPath)) { console.log(`⏭️ ${targetFilename} not found, skipping`); + return; } @@ -58,16 +58,18 @@ async function makeCircle(targetFilename) { const size = Math.max(metadata.width, metadata.height); const radius = Math.floor((size * (1 - paddingPct / 100)) / 2); const center = Math.floor(size / 2); - // Build circular mask as RGBA buffer: white opaque circle on transparent bg const maskBuf = Buffer.alloc(size * size * 4, 0); + for (let y = 0; y < size; y++) { for (let x = 0; x < size; x++) { const dx = x - center; const dy = y - center; const dist = Math.sqrt(dx * dx + dy * dy); + if (dist < radius) { const i = (y * size + x) * 4; + maskBuf[i] = 255; maskBuf[i + 1] = 255; maskBuf[i + 2] = 255; @@ -77,8 +79,9 @@ async function makeCircle(targetFilename) { } const tmpMask = path.join(STATIC_DIR, '.mask-tmp.png'); + await sharp(maskBuf, { - raw: { width: size, height: size, channels: 4 } + raw: { channels: 4, height: size, width: size } }) .png() .toFile(tmpMask); @@ -87,28 +90,26 @@ async function makeCircle(targetFilename) { const circleDiameter = Math.floor(size * (1 - paddingPct / 100)); const scaledSize = Math.floor((circleDiameter * scalePct) / 100); const offset = Math.floor((size - scaledSize) / 2); - const scaledBuf = await sharp(sourcePath) .resize(scaledSize, scaledSize, { - fit: 'cover', - background: { r: 255, g: 255, b: 255, alpha: 1 } + background: { alpha: 1, b: 255, g: 255, r: 255 }, + fit: 'cover' }) .ensureAlpha() .png() .toBuffer(); - // Step 2: Composite scaled image onto white background, then apply circular mask const output = await sharp({ create: { - width: size, - height: size, + background: { alpha: 1, b: 255, g: 255, r: 255 }, channels: 4, - background: { r: 255, g: 255, b: 255, alpha: 1 } + height: size, + width: size } }) .composite([ - { input: scaledBuf, top: offset, left: offset }, - { input: tmpMask, top: 0, left: 0, blend: 'dest-in' } + { input: scaledBuf, left: offset, top: offset }, + { blend: 'dest-in', input: tmpMask, left: 0, top: 0 } ]) .png() .toBuffer(); @@ -130,6 +131,7 @@ async function main() { console.log('\nUnchanged:'); for (const icon of untouchedIcons) { const fp = path.join(STATIC_DIR, icon); + console.log(` ${icon} (${fs.existsSync(fp) ? fs.statSync(fp).size + ' bytes' : 'missing'})`); } } diff --git a/tools/ui/scripts/vite-plugin-build-info.ts b/tools/ui/scripts/vite-plugin-build-info.ts index 972ba3b66..800238630 100644 --- a/tools/ui/scripts/vite-plugin-build-info.ts +++ b/tools/ui/scripts/vite-plugin-build-info.ts @@ -1,7 +1,7 @@ -import { writeFileSync, existsSync } from 'node:fs'; +import { BUILD_CONFIG } from '../src/lib/constants/pwa'; +import { existsSync, writeFileSync } from 'node:fs'; import { resolve } from 'path'; import type { Plugin } from 'vite'; -import { BUILD_CONFIG } from '../src/lib/constants/pwa'; let processed = false; @@ -15,27 +15,29 @@ const OUTPUT_DIR = process.env.LLAMA_UI_OUT_DIR ?? BUILD_CONFIG.OUTPUT_DIR; */ export function buildInfoPlugin(): Plugin { return { - name: 'llamacpp:build-info', apply: 'build', closeBundle() { setTimeout(() => { try { if (processed) return; + processed = true; const buildNumber = process.env.LLAMA_BUILD_NUMBER || 'b0000'; - const outDir = resolve(OUTPUT_DIR); const indexPath = resolve(outDir, 'index.html'); + if (!existsSync(indexPath)) return; const buildJsonPath = resolve(outDir, 'build.json'); + writeFileSync(buildJsonPath, JSON.stringify({ version: buildNumber }), 'utf-8'); console.log(`Created build.json (version: ${buildNumber})`); } catch (error) { console.error('Failed to write build.json:', error); } }, 100); - } + }, + name: 'llamacpp:build-info' }; } diff --git a/tools/ui/scripts/vite-plugin-nerdamer.ts b/tools/ui/scripts/vite-plugin-nerdamer.ts index 218c2fa23..84e463c6d 100644 --- a/tools/ui/scripts/vite-plugin-nerdamer.ts +++ b/tools/ui/scripts/vite-plugin-nerdamer.ts @@ -4,7 +4,6 @@ import { fileURLToPath } from 'url'; import type { Plugin } from 'vite'; const __dirname = dirname(fileURLToPath(import.meta.url)); - const VENDORS_DIR = resolve(__dirname, '../src/lib/vendors'); const VIRTUAL_ID = 'virtual:nerdamer'; const RESOLVED_ID = '\0' + VIRTUAL_ID; @@ -21,29 +20,32 @@ export function nerdamerPlugin(): Plugin { let bundled: string | null = null; return { - name: 'llamacpp:nerdamer', - resolveId(id) { - return id === VIRTUAL_ID ? RESOLVED_ID : undefined; - }, async load(id) { if (id !== RESOLVED_ID) return undefined; + if (bundled === null) { const result = await build({ - entryPoints: [resolve(VENDORS_DIR, 'nerdamer-prime/all.js')], - bundle: true, - minify: true, - format: 'iife', - globalName: 'nerdamer', alias: { 'big-integer': resolve(VENDORS_DIR, 'big-integer/BigInteger.js'), 'decimal.js': resolve(VENDORS_DIR, 'decimal.js/decimal.js') }, - write: false, - logLevel: 'silent' + bundle: true, + entryPoints: [resolve(VENDORS_DIR, 'nerdamer-prime/all.js')], + format: 'iife', + globalName: 'nerdamer', + logLevel: 'silent', + minify: true, + write: false }); + bundled = result.outputFiles[0].text; } + return `export default ${JSON.stringify(bundled)};`; + }, + name: 'llamacpp:nerdamer', + resolveId(id) { + return id === VIRTUAL_ID ? RESOLVED_ID : undefined; } }; } diff --git a/tools/ui/scripts/vite-plugin-relativize-base.ts b/tools/ui/scripts/vite-plugin-relativize-base.ts index ce2f1b6e9..f8eac1d66 100644 --- a/tools/ui/scripts/vite-plugin-relativize-base.ts +++ b/tools/ui/scripts/vite-plugin-relativize-base.ts @@ -1,7 +1,7 @@ -import { readFileSync, writeFileSync, existsSync } from 'node:fs'; +import { BUILD_CONFIG } from '../src/lib/constants/pwa'; +import { existsSync, readFileSync, writeFileSync } from 'node:fs'; import { resolve } from 'path'; import type { Plugin } from 'vite'; -import { BUILD_CONFIG } from '../src/lib/constants/pwa'; let processed = false; @@ -11,11 +11,15 @@ function rewrite(path: string, pairs: [string, string][]): void { if (!existsSync(path)) { return; } + const text = readFileSync(path, 'utf-8'); + let out = text; + for (const [from, to] of pairs) { out = out.split(from).join(to); } + if (out !== text) { writeFileSync(path, out, 'utf-8'); } @@ -32,12 +36,12 @@ function rewrite(path: string, pairs: [string, string][]): void { */ export function relativizeBasePlugin(): Plugin { return { - name: 'llamacpp:relativize-base', apply: 'build', closeBundle() { setTimeout(() => { try { if (processed) return; + processed = true; const outDir = resolve(OUTPUT_DIR); @@ -56,6 +60,7 @@ export function relativizeBasePlugin(): Plugin { console.error('Failed to relativize base refs:', error); } }, 100); - } + }, + name: 'llamacpp:relativize-base' }; } diff --git a/tools/ui/scripts/vite-plugin-splash-screen.ts b/tools/ui/scripts/vite-plugin-splash-screen.ts index 059ce4920..45d931bab 100644 --- a/tools/ui/scripts/vite-plugin-splash-screen.ts +++ b/tools/ui/scripts/vite-plugin-splash-screen.ts @@ -1,10 +1,10 @@ -import { readdirSync, readFileSync, writeFileSync, existsSync } from 'node:fs'; +import { NEWLINE, TAB } from '../src/lib/constants/code'; +import { APPLE_DEVICES, BUILD_CONFIG, REGEX_PATTERNS, SPLASH_LINK } from '../src/lib/constants/pwa'; +import { SplashOrientation } from '../src/lib/enums/splash.enums'; +import type { SplashDimensions } from '../src/lib/types'; +import { existsSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; import { resolve } from 'path'; import type { Plugin } from 'vite'; -import { TAB, NEWLINE } from '../src/lib/constants/code'; -import { APPLE_DEVICES, BUILD_CONFIG, REGEX_PATTERNS, SPLASH_LINK } from '../src/lib/constants/pwa'; -import type { SplashDimensions } from '../src/lib/types'; -import { SplashOrientation } from '../src/lib/enums/splash.enums'; let processed = false; @@ -16,23 +16,26 @@ const OUTPUT_DIR = process.env.LLAMA_UI_OUT_DIR ?? BUILD_CONFIG.OUTPUT_DIR; */ export function generateSplashScreenLinks(outDir: string): string[] { const files = readdirSync(outDir).filter((f) => f.match(REGEX_PATTERNS.SPLASH_FILE)); + if (files.length === 0) return []; const dimMap = new Map(); + for (const [dims, spec] of Object.entries(APPLE_DEVICES)) { const [w, h] = dims.split('x').map(Number); + // logical-point dimensions - dimMap.set(`${w}x${h}`, { deviceW: spec.width, deviceH: spec.height, dpr: spec.dpr }); - dimMap.set(`${h}x${w}`, { deviceW: spec.width, deviceH: spec.height, dpr: spec.dpr }); + dimMap.set(`${w}x${h}`, { deviceH: spec.height, deviceW: spec.width, dpr: spec.dpr }); + dimMap.set(`${h}x${w}`, { deviceH: spec.height, deviceW: spec.width, dpr: spec.dpr }); // pixel dimensions (used by actual generated splash files) dimMap.set(`${w * spec.dpr}x${h * spec.dpr}`, { - deviceW: spec.width, deviceH: spec.height, + deviceW: spec.width, dpr: spec.dpr }); dimMap.set(`${h * spec.dpr}x${w * spec.dpr}`, { - deviceW: spec.width, deviceH: spec.height, + deviceW: spec.width, dpr: spec.dpr }); } @@ -42,20 +45,23 @@ export function generateSplashScreenLinks(outDir: string): string[] { for (const file of files) { const match = file.match(REGEX_PATTERNS.SPLASH_FILE); + if (!match) continue; + const orientation = match[1] as SplashOrientation; const isDark = !!match[2]; const pixelW = parseInt(match[3]); const pixelH = parseInt(match[4]); - const key = `${pixelW}x${pixelH}`; const spec = dimMap.get(key); + if (!spec) { console.warn(`Unknown splash screen dimensions: ${key} (${file})`); + continue; } - const { deviceW, deviceH, dpr } = spec; + const { deviceH, deviceW, dpr } = spec; const media = `screen and (device-width: ${deviceW}px) and (device-height: ${deviceH}px) and (-webkit-device-pixel-ratio: ${dpr}) and (orientation: ${orientation})`; const href = `./${file}`; @@ -73,16 +79,17 @@ export function generateSplashScreenLinks(outDir: string): string[] { export function splashScreenPlugin(): Plugin { return { - name: 'llamacpp:splash-screen', apply: 'build', closeBundle() { setTimeout(() => { try { if (processed) return; + processed = true; const outDir = resolve(OUTPUT_DIR); const indexPath = resolve(outDir, 'index.html'); + if (!existsSync(indexPath)) return; let content = readFileSync(indexPath, 'utf-8'); @@ -91,9 +98,11 @@ export function splashScreenPlugin(): Plugin { // The @vite-pwa/assets-generator generates apple-splash-*.png files; // this scans them and creates the tags SvelteKit needs. const splashLinks = generateSplashScreenLinks(outDir); + if (splashLinks.length > 0) { console.log(`Generated ${splashLinks.length} apple-splash link tags`); const splashHtml = splashLinks.map((l) => TAB + TAB + l).join(NEWLINE); + content = content.replace( REGEX_PATTERNS.HEAD_CLOSE, splashHtml + NEWLINE + TAB + TAB + '' @@ -110,6 +119,7 @@ export function splashScreenPlugin(): Plugin { console.error('Failed to process build output:', error); } }, 100); - } + }, + name: 'llamacpp:splash-screen' }; } diff --git a/tools/ui/src/app.d.ts b/tools/ui/src/app.d.ts index 814fcf523..6f26214e1 100644 --- a/tools/ui/src/app.d.ts +++ b/tools/ui/src/app.d.ts @@ -3,9 +3,8 @@ import 'vite-plugin-pwa/pwa-assets'; import 'vite-plugin-pwa/svelte'; - +import { ModelModality, ServerModelStatus, ServerRole } from '$lib/enums'; // Import chat types from dedicated module - import type { // API types ApiChatCompletionRequest, @@ -13,59 +12,57 @@ import type { ApiChatCompletionStreamChunk, ApiChatCompletionToolCall, ApiChatCompletionToolCallDelta, - ApiChatMessageData, ApiChatMessageContentPart, + ApiChatMessageData, ApiContextSizeError, ApiErrorResponse, ApiLlamaCppServerProps, ApiModelDataEntry, + ApiModelListResponse, ApiModelLoadStage, - ApiModelsSseProgress, ApiModelsSseData, ApiModelsSseEvent, - ApiModelListResponse, + ApiModelsSseProgress, ApiProcessingState, ApiRouterModelMeta, + ApiRouterModelsListResponse, ApiRouterModelsLoadRequest, ApiRouterModelsLoadResponse, ApiRouterModelsStatusRequest, ApiRouterModelsStatusResponse, - ApiRouterModelsListResponse, ApiRouterModelsUnloadRequest, ApiRouterModelsUnloadResponse, // Chat types ChatAttachmentDisplayItem, + ChatMessagePromptProgress, + ChatMessageSiblingInfo, + ChatMessageTimings, ChatMessageType, ChatRole, ChatUploadedFile, - ChatMessageSiblingInfo, - ChatMessagePromptProgress, - ChatMessageTimings, // Database types DatabaseConversation, DatabaseMessage, DatabaseMessageExtra, DatabaseMessageExtraAudioFile, - DatabaseMessageExtraVideoFile, DatabaseMessageExtraImageFile, - DatabaseMessageExtraTextFile, - DatabaseMessageExtraPdfFile, DatabaseMessageExtraLegacyContext, + DatabaseMessageExtraPdfFile, + DatabaseMessageExtraTextFile, + DatabaseMessageExtraVideoFile, ExportedConversation, ExportedConversations, + ModelLoadProgress, // Model types ModelModalities, ModelOption, - ModelLoadProgress, // Settings types SettingsChatServiceOptions, + SettingsConfigType, SettingsConfigValue, - SettingsFieldConfig, - SettingsConfigType + SettingsFieldConfig } from '$lib/types'; -import { ServerRole, ServerModelStatus, ModelModality } from '$lib/enums'; - declare global { // namespace App { // interface Error {} diff --git a/tools/ui/src/lib/components/app/actions/ActionIcon.svelte b/tools/ui/src/lib/components/app/actions/ActionIcon.svelte index 608ff6fab..e29b5ad67 100644 --- a/tools/ui/src/lib/components/app/actions/ActionIcon.svelte +++ b/tools/ui/src/lib/components/app/actions/ActionIcon.svelte @@ -1,8 +1,8 @@ {#each modalities as modality (modality)} diff --git a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsList.svelte b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsList.svelte index e74bd8456..36895c8e7 100644 --- a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsList.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsList.svelte @@ -28,18 +28,18 @@ } let { - class: className = '', - style = '', + activeModelId, attachments = [], - readonly = false, - onFileRemove, - uploadedFiles = $bindable([]), + class: className = '', // Default to small size for form previews imageClass = '', imageHeight = 'h-24', imageWidth = 'w-auto', limitToSingleRow = false, - activeModelId + onFileRemove, + readonly = false, + style = '', + uploadedFiles = $bindable([]) }: Props = $props(); let carouselRef: HorizontalScrollCarousel | undefined = $state(); @@ -48,7 +48,7 @@ let previewFocusIndex = $state(0); let viewAllDialogOpen = $state(false); - let displayItems = $derived(getAttachmentDisplayItems({ uploadedFiles, attachments })); + let displayItems = $derived(getAttachmentDisplayItems({ attachments, uploadedFiles })); function openPreview(item: ChatAttachmentDisplayItem, event?: MouseEvent) { event?.stopPropagation(); diff --git a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItem.svelte b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItem.svelte index 143621cd9..ba06e1815 100644 --- a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItem.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItem.svelte @@ -2,8 +2,8 @@ import { ChatAttachmentsListItemMcpPrompt, ChatAttachmentsListItemMcpResource, - ChatAttachmentsListItemThumbnailImage, - ChatAttachmentsListItemThumbnailFile + ChatAttachmentsListItemThumbnailFile, + ChatAttachmentsListItemThumbnailImage } from '$lib/components/app'; import { AttachmentType } from '$lib/enums'; import type { @@ -49,10 +49,10 @@ return { id, resource: { - uri: extra.uri, name: extra.name, + serverName: extra.serverName, title: extra.name, - serverName: extra.serverName + uri: extra.uri } }; } @@ -64,12 +64,12 @@ ? (item.attachment as DatabaseMessageExtraMcpPrompt) : item.uploadedFile?.mcpPrompt ? { - type: AttachmentType.MCP_PROMPT as const, - name: item.name, - serverName: item.uploadedFile.mcpPrompt.serverName, - promptName: item.uploadedFile.mcpPrompt.promptName, + arguments: item.uploadedFile.mcpPrompt.arguments, content: item.textContent ?? '', - arguments: item.uploadedFile.mcpPrompt.arguments + name: item.name, + promptName: item.uploadedFile.mcpPrompt.promptName, + serverName: item.uploadedFile.mcpPrompt.serverName, + type: AttachmentType.MCP_PROMPT as const } : null} {#if mcpPrompt} diff --git a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItemMcpPrompt.svelte b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItemMcpPrompt.svelte index 636e93f22..f5452aade 100644 --- a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItemMcpPrompt.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsList/ChatAttachmentsListItem/ChatAttachmentsListItemMcpPrompt.svelte @@ -1,8 +1,8 @@
diff --git a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreviewCurrentItem/ChatAttachmentsPreviewCurrentItemPdf.svelte b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreviewCurrentItem/ChatAttachmentsPreviewCurrentItemPdf.svelte index 7c7cf5120..69b1724fb 100644 --- a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreviewCurrentItem/ChatAttachmentsPreviewCurrentItemPdf.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreviewCurrentItem/ChatAttachmentsPreviewCurrentItemPdf.svelte @@ -1,13 +1,13 @@ {#if show} diff --git a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreviewThumbnailStrip.svelte b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreviewThumbnailStrip.svelte index 8a85df7d0..994a35f20 100644 --- a/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreviewThumbnailStrip.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatAttachments/ChatAttachmentsPreview/ChatAttachmentsPreviewThumbnailStrip.svelte @@ -1,7 +1,7 @@ diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte index 2d70c302c..bc1befd63 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatForm.svelte @@ -1,4 +1,5 @@ diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContenteditable.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContenteditable.svelte index 18fd24302..98b082c89 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContenteditable.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContenteditable.svelte @@ -1,11 +1,8 @@
diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContextGauge/ContextGaugeDetails.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContextGauge/ContextGaugeDetails.svelte index fdec5aca5..ab36e4a43 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContextGauge/ContextGaugeDetails.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContextGauge/ContextGaugeDetails.svelte @@ -1,8 +1,8 @@ {#if modelId !== null && !isLoading} diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContextGauge/ContextGaugePopup.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContextGauge/ContextGaugePopup.svelte index af9ad010e..db4479b65 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContextGauge/ContextGaugePopup.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormContextGauge/ContextGaugePopup.svelte @@ -1,15 +1,15 @@ diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerList.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerList.svelte index 04b85b24b..3ebb29b9b 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerList.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerList.svelte @@ -1,9 +1,9 @@ diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerListItem.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerListItem.svelte index 0fb0ea0c0..51a51f808 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerListItem.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerListItem.svelte @@ -12,13 +12,13 @@ } let { + children, class: className = '', - isSelected = false, - disabled = false, - onclick, - onmouseenter, dataIndex, - children + disabled = false, + isSelected = false, + onclick, + onmouseenter }: Props = $props(); diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerListItemSkeleton.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerListItemSkeleton.svelte index 5a2ab26fc..cbf7b972e 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerListItemSkeleton.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerListItemSkeleton.svelte @@ -4,7 +4,7 @@ showBadge?: boolean; } - let { titleWidth = 'w-48', showBadge = false }: Props = $props(); + let { showBadge = false, titleWidth = 'w-48' }: Props = $props();
diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerPopover.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerPopover.svelte index 24ea8619f..b09d346f1 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerPopover.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPicker/ChatFormPickerPopover.svelte @@ -1,6 +1,6 @@ diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMcpPrompts/ChatFormPickerMcpPrompts.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMcpPrompts/ChatFormPickerMcpPrompts.svelte index 22ec53119..126d08744 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMcpPrompts/ChatFormPickerMcpPrompts.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMcpPrompts/ChatFormPickerMcpPrompts.svelte @@ -1,19 +1,19 @@ diff --git a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMcpPrompts/ChatFormPromptPickerArgumentInput.svelte b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMcpPrompts/ChatFormPromptPickerArgumentInput.svelte index 638d10eef..074c69b84 100644 --- a/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMcpPrompts/ChatFormPromptPickerArgumentInput.svelte +++ b/tools/ui/src/lib/components/app/chat/ChatForm/ChatFormPickers/ChatFormPickerMcpPrompts/ChatFormPromptPickerArgumentInput.svelte @@ -1,8 +1,8 @@ @@ -66,7 +66,7 @@ {#if isAutocompleteActive && suggestions.length > 0}
{#each suggestions as suggestion, i (suggestion)}