From 28ee117b6650dc6caaa50350ddb60cdb230b6275 Mon Sep 17 00:00:00 2001 From: John Mai Date: Sat, 1 Mar 2025 12:48:32 +0800 Subject: [PATCH 1/3] Refactoring --- ChatMLX.xcodeproj/project.pbxproj | 733 +-- .../xcshareddata/swiftpm/Package.resolved | 137 +- .../xcschemes/xcschememanagement.plist | 7 +- .../AccentColor.colorset/Contents.json | 27 - .../Contents.json | 2 +- ChatMLX/Assets.xcassets/MCP.imageset/MCP.png | Bin 0 -> 21265 bytes .../MLX.imageset/Contents.json | 2 +- .../MLX.imageset/{1028322432.png => MLX.png} | Bin .../clear.imageset/clear-l.svg | 1 - .../huggingface.imageset/Contents.json | 2 +- .../huggingface.imageset/hf-logo-pirate.svg | 15 - .../huggingface.imageset/huggingface.png | Bin 0 -> 23251 bytes .../markdown.imageset/Contents.json | 21 - .../markdown.imageset/markdown (2).svg | 1 - .../Contents.json | 4 +- .../menubarIcon.imageset/menubarIcon@1x.png | Bin 0 -> 3433 bytes .../menubarIcon.imageset/menubarIcon@2x.png | Bin 0 -> 4168 bytes .../menubarIcon.imageset/menubarIcon@3x.png | Bin 0 -> 4747 bytes .../plaintext.imageset/doc-plaintext (1).svg | 1 - ChatMLX/ChatMLX.entitlements | 2 +- ChatMLX/ChatMLXApp.swift | 62 +- ChatMLX/ChatMLXRelease.entitlements | 14 - .../AppleIntelligenceEffectController.swift | 57 - .../AppleIntelligenceEffectManager.swift | 36 - .../AppleIntelligenceEffectView.swift | 60 - .../AppleIntelligenceEffect/ColorWheel.metal | 25 - ChatMLX/Components/EffectView.swift | 73 - ChatMLX/Components/ErrorAlertModifier.swift | 42 - ChatMLX/Components/NoneInteractWindow.swift | 48 - .../SplashCodeSyntaxHighlighter.swift | 33 - .../SyntaxHighlighter/TextOutputFormat.swift | 53 - .../UltramanMinimalistWindowModifier.swift | 69 - .../UltramanNavigationSplitView.swift | 188 - ChatMLX/Components/UltramanSecureField.swift | 100 - .../UltramanSidebarButtonStyle.swift | 30 - .../UltramanTextEditorWithPlaceholder.swift | 115 - ChatMLX/Components/UltramanTextField.swift | 100 - ChatMLX/Components/UltramanWindow.swift | 51 - ChatMLX/Extensions/Date+Extensions.swift | 31 - ChatMLX/Extensions/Defaults+Extensions.swift | 44 - .../MarkdownUI+Theme+Extensions.swift | 59 - ChatMLX/Extensions/NSWindow+Extensions.swift | 56 - ChatMLX/Extensions/String+Extensions.swift | 13 - .../Extensions/TimeInterval+Extensions.swift | 29 - ChatMLX/Extensions/View+Extensions.swift | 52 - .../Conversation/ConversationDetailView.swift | 376 -- .../ConversationSidebarItem.swift | 67 - .../ConversationSidebarView.swift | 93 - .../Conversation/ConversationView.swift | 60 - .../Conversation/ConversationViewModel.swift | 36 - .../Conversation/EmptyConversation.swift | 35 - .../Conversation/MessageBubbleView.swift | 184 - .../Conversation/RightSidebarView.swift | 218 - ChatMLX/Features/Settings/AboutView.swift | 58 - .../Settings/DefaultConversationView.swift | 268 - .../DownloadManager/DownloadManagerView.swift | 59 - .../DownloadManager/DownloadTaskView.swift | 86 - .../Settings/ExperimentalFeaturesView.swift | 70 - ChatMLX/Features/Settings/GeneralView.swift | 157 - .../Features/Settings/HuggingFaceView.swift | 157 - .../LocalModels/LocalModelItemView.swift | 39 - .../LocalModels/LocalModelsView.swift | 136 - .../MLXCommunity/MLXCommunityItemView.swift | 85 - .../MLXCommunity/MLXCommunityView.swift | 198 - .../Settings/SettingsSidebarItemView.swift | 78 - .../Settings/SettingsSidebarView.swift | 62 - ChatMLX/Features/Settings/SettingsView.swift | 43 - .../Features/Settings/SettingsViewModel.swift | 27 - ChatMLX/Localizable.xcstrings | 5419 +++++++++-------- .../AppleIntelligenceEffectDisplay.swift | 18 - .../ChatMLX.xcdatamodel/contents | 34 - .../Models/Conversation+CoreDataClass.swift | 15 - .../Conversation+CoreDataProperties.swift | 115 - ChatMLX/Models/DisplayStyle.swift | 13 - ChatMLX/Models/DownloadTask.swift | 100 - ChatMLX/Models/Language.swift | 98 - ChatMLX/Models/LocalModel.swift | 20 - ChatMLX/Models/LocalModelGroup.swift | 14 - ChatMLX/Models/Message+CoreDataClass.swift | 41 - .../Models/Message+CoreDataProperties.swift | 46 - ChatMLX/Models/RemoteModel.swift | 63 - ChatMLX/Models/Role.swift | 16 - ChatMLX/Models/SettingsTab.swift | 55 - ChatMLX/Models/SettingsTabGroup.swift | 25 - ChatMLX/Models/Styles.swift | 12 - .../Utilities/Huggingface/Downloader.swift | 142 - ChatMLX/Utilities/Huggingface/Hub.swift | 222 - ChatMLX/Utilities/Huggingface/HubApi.swift | 359 -- ChatMLX/Utilities/LLMRunner.swift | 219 - ChatMLX/Utilities/Logger.swift | 11 - ChatMLX/Utilities/PersistenceController.swift | 74 - Packages/Common/.gitignore | 8 + Packages/Common/Package.swift | 36 + Packages/Common/Sources/Common/AppStore.swift | 15 + .../Extensions/Binding+Extensions.swift | 7 +- .../Common/Extensions/Date+Extensions.swift | 32 + .../Extensions/Defaults+Extensions.swift | 23 + .../Services/HuggingfaceHubService.swift | 54 + .../Common/Utilities/MarkdownMetadata.swift | 81 + .../Tests/CommonTests/CommonTests.swift | 7 + Packages/Conversation/.gitignore | 8 + Packages/Conversation/Package.swift | 36 + .../Sources/Conversation/ChatView.swift | 164 + .../ConversationSidebarView.swift | 77 + .../Conversation/ConversationStore.swift | 25 + .../Conversation/ConversationView.swift | 71 + .../Conversation/PromptEditorView.swift | 146 + .../ConversationTests/ConversationTests.swift | 7 + Packages/Models/.gitignore | 8 + Packages/Models/Package.swift | 32 + .../Models/Sources/Models/Conversation.swift | 52 + .../Sources/Models/HuggingFaceEndpoint.swift | 19 + Packages/Models/Sources/Models/Language.swift | 107 + Packages/Models/Sources/Models/Message.swift | 27 + Packages/Models/Sources/Models/Model.swift | 43 + Packages/Models/Sources/Models/Role.swift | 13 + .../Models/Sources/Models/SettingsTab.swift | 48 + .../Tests/ModelsTests/LanguageTests.swift | 21 + .../Tests/ModelsTests/ModelsTests.swift | 7 + Packages/Settings/.gitignore | 8 + Packages/Settings/Package.swift | 36 + .../Settings/Sources/Settings/AboutView.swift | 14 + .../Settings/DefaultConversationView.swift | 14 + .../Settings/DownloadManagerView.swift | 14 + .../Settings/ExperimentalFeaturesView.swift | 14 + .../Sources/Settings/GeneralView.swift | 50 + .../Sources/Settings/HuggingFaceView.swift | 127 + .../Sources/Settings/MCPServersView.swift | 14 + .../Sources/Settings/MLXCommunityView.swift | 14 + .../Sources/Settings/ModelsView.swift | 14 + .../Sources/Settings/ProvidersView.swift | 14 + .../Sources/Settings/SearchView.swift | 14 + .../Settings/SettingsSidebarView.swift | 77 + .../Sources/Settings/SettingsStore.swift | 14 + .../Sources/Settings/SettingsView.swift | 74 + .../Tests/SettingsTests/SettingsTests.swift | 7 + Packages/UltraUI/.gitignore | 8 + Packages/UltraUI/Package.swift | 34 + .../UltraUI/Sources/UltraUI/Divided.swift | 32 + .../EnvironmentValues+Extensions.swift | 26 + .../Extensions/NSWindow+Extensions.swift | 35 + .../UltraUI/Extensions/View+Extensions.swift | 19 + .../Sources/UltraUI/GreetingView.swift | 46 + .../Modifiers/SidebarViewModifier.swift | 111 + .../UltraWindowStyleViewModifier.swift | 34 + .../ButtonStyles/UltraButtonStyle.swift | 133 + .../UltraDynamicButtonStyle.swift | 168 + .../ButtonStyles/UltraIconButtonStyle.swift | 126 + .../ButtonStyles/UltraPlainButtonStyle.swift | 125 + .../UltraSidebarButtonStyle.swift | 144 + .../HorizontalLabeledContentStyle.swift | 24 + .../Sources/UltraUI/TextareaView.swift | 164 + .../UltraUI/UltraNavigationSplitView.swift | 306 + .../UltraPicker/UltraOptionsPanel.swift | 18 + .../UltraOptionsWindowController.swift | 144 + .../UltraUI/UltraPicker/UltraPicker.swift | 83 + .../Sources/UltraUI/UltraSection.swift | 161 + .../UltraUI/UltraSecureTextField.swift | 88 + .../UltraUI/UltraSidebarBackgroundView.swift | 51 + .../Sources/UltraUI/UltraTextField.swift | 88 + .../UltraUI/UltraWindowBackgroundView.swift | 68 + .../Tests/UltraUITests/UltraUITests.swift | 7 + 162 files changed, 6791 insertions(+), 9263 deletions(-) rename ChatMLX/Assets.xcassets/{clear.imageset => MCP.imageset}/Contents.json (88%) create mode 100644 ChatMLX/Assets.xcassets/MCP.imageset/MCP.png rename ChatMLX/Assets.xcassets/MLX.imageset/{1028322432.png => MLX.png} (100%) delete mode 100644 ChatMLX/Assets.xcassets/clear.imageset/clear-l.svg delete mode 100644 ChatMLX/Assets.xcassets/huggingface.imageset/hf-logo-pirate.svg create mode 100644 ChatMLX/Assets.xcassets/huggingface.imageset/huggingface.png delete mode 100644 ChatMLX/Assets.xcassets/markdown.imageset/Contents.json delete mode 100644 ChatMLX/Assets.xcassets/markdown.imageset/markdown (2).svg rename ChatMLX/Assets.xcassets/{plaintext.imageset => menubarIcon.imageset}/Contents.json (68%) create mode 100644 ChatMLX/Assets.xcassets/menubarIcon.imageset/menubarIcon@1x.png create mode 100644 ChatMLX/Assets.xcassets/menubarIcon.imageset/menubarIcon@2x.png create mode 100644 ChatMLX/Assets.xcassets/menubarIcon.imageset/menubarIcon@3x.png delete mode 100644 ChatMLX/Assets.xcassets/plaintext.imageset/doc-plaintext (1).svg delete mode 100644 ChatMLX/ChatMLXRelease.entitlements delete mode 100644 ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectController.swift delete mode 100644 ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectManager.swift delete mode 100644 ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectView.swift delete mode 100644 ChatMLX/Components/AppleIntelligenceEffect/ColorWheel.metal delete mode 100644 ChatMLX/Components/EffectView.swift delete mode 100644 ChatMLX/Components/ErrorAlertModifier.swift delete mode 100644 ChatMLX/Components/NoneInteractWindow.swift delete mode 100644 ChatMLX/Components/SyntaxHighlighter/SplashCodeSyntaxHighlighter.swift delete mode 100644 ChatMLX/Components/SyntaxHighlighter/TextOutputFormat.swift delete mode 100644 ChatMLX/Components/UltramanMinimalistWindowModifier.swift delete mode 100644 ChatMLX/Components/UltramanNavigationSplitView.swift delete mode 100644 ChatMLX/Components/UltramanSecureField.swift delete mode 100644 ChatMLX/Components/UltramanSidebarButtonStyle.swift delete mode 100644 ChatMLX/Components/UltramanTextEditorWithPlaceholder.swift delete mode 100644 ChatMLX/Components/UltramanTextField.swift delete mode 100644 ChatMLX/Components/UltramanWindow.swift delete mode 100644 ChatMLX/Extensions/Date+Extensions.swift delete mode 100644 ChatMLX/Extensions/Defaults+Extensions.swift delete mode 100644 ChatMLX/Extensions/MarkdownUI+Theme+Extensions.swift delete mode 100644 ChatMLX/Extensions/NSWindow+Extensions.swift delete mode 100644 ChatMLX/Extensions/String+Extensions.swift delete mode 100644 ChatMLX/Extensions/TimeInterval+Extensions.swift delete mode 100644 ChatMLX/Extensions/View+Extensions.swift delete mode 100644 ChatMLX/Features/Conversation/ConversationDetailView.swift delete mode 100644 ChatMLX/Features/Conversation/ConversationSidebarItem.swift delete mode 100644 ChatMLX/Features/Conversation/ConversationSidebarView.swift delete mode 100644 ChatMLX/Features/Conversation/ConversationView.swift delete mode 100644 ChatMLX/Features/Conversation/ConversationViewModel.swift delete mode 100644 ChatMLX/Features/Conversation/EmptyConversation.swift delete mode 100644 ChatMLX/Features/Conversation/MessageBubbleView.swift delete mode 100644 ChatMLX/Features/Conversation/RightSidebarView.swift delete mode 100644 ChatMLX/Features/Settings/AboutView.swift delete mode 100644 ChatMLX/Features/Settings/DefaultConversationView.swift delete mode 100644 ChatMLX/Features/Settings/DownloadManager/DownloadManagerView.swift delete mode 100644 ChatMLX/Features/Settings/DownloadManager/DownloadTaskView.swift delete mode 100644 ChatMLX/Features/Settings/ExperimentalFeaturesView.swift delete mode 100644 ChatMLX/Features/Settings/GeneralView.swift delete mode 100644 ChatMLX/Features/Settings/HuggingFaceView.swift delete mode 100644 ChatMLX/Features/Settings/LocalModels/LocalModelItemView.swift delete mode 100644 ChatMLX/Features/Settings/LocalModels/LocalModelsView.swift delete mode 100644 ChatMLX/Features/Settings/MLXCommunity/MLXCommunityItemView.swift delete mode 100644 ChatMLX/Features/Settings/MLXCommunity/MLXCommunityView.swift delete mode 100644 ChatMLX/Features/Settings/SettingsSidebarItemView.swift delete mode 100644 ChatMLX/Features/Settings/SettingsSidebarView.swift delete mode 100644 ChatMLX/Features/Settings/SettingsView.swift delete mode 100644 ChatMLX/Features/Settings/SettingsViewModel.swift delete mode 100644 ChatMLX/Models/AppleIntelligenceEffectDisplay.swift delete mode 100644 ChatMLX/Models/ChatMLX.xcdatamodeld/ChatMLX.xcdatamodel/contents delete mode 100644 ChatMLX/Models/Conversation+CoreDataClass.swift delete mode 100644 ChatMLX/Models/Conversation+CoreDataProperties.swift delete mode 100644 ChatMLX/Models/DisplayStyle.swift delete mode 100644 ChatMLX/Models/DownloadTask.swift delete mode 100644 ChatMLX/Models/Language.swift delete mode 100644 ChatMLX/Models/LocalModel.swift delete mode 100644 ChatMLX/Models/LocalModelGroup.swift delete mode 100644 ChatMLX/Models/Message+CoreDataClass.swift delete mode 100644 ChatMLX/Models/Message+CoreDataProperties.swift delete mode 100644 ChatMLX/Models/RemoteModel.swift delete mode 100644 ChatMLX/Models/Role.swift delete mode 100644 ChatMLX/Models/SettingsTab.swift delete mode 100644 ChatMLX/Models/SettingsTabGroup.swift delete mode 100644 ChatMLX/Models/Styles.swift delete mode 100644 ChatMLX/Utilities/Huggingface/Downloader.swift delete mode 100644 ChatMLX/Utilities/Huggingface/Hub.swift delete mode 100644 ChatMLX/Utilities/Huggingface/HubApi.swift delete mode 100644 ChatMLX/Utilities/LLMRunner.swift delete mode 100644 ChatMLX/Utilities/Logger.swift delete mode 100644 ChatMLX/Utilities/PersistenceController.swift create mode 100644 Packages/Common/.gitignore create mode 100644 Packages/Common/Package.swift create mode 100644 Packages/Common/Sources/Common/AppStore.swift rename {ChatMLX => Packages/Common/Sources/Common}/Extensions/Binding+Extensions.swift (55%) create mode 100644 Packages/Common/Sources/Common/Extensions/Date+Extensions.swift create mode 100644 Packages/Common/Sources/Common/Extensions/Defaults+Extensions.swift create mode 100644 Packages/Common/Sources/Common/Services/HuggingfaceHubService.swift create mode 100644 Packages/Common/Sources/Common/Utilities/MarkdownMetadata.swift create mode 100644 Packages/Common/Tests/CommonTests/CommonTests.swift create mode 100644 Packages/Conversation/.gitignore create mode 100644 Packages/Conversation/Package.swift create mode 100644 Packages/Conversation/Sources/Conversation/ChatView.swift create mode 100644 Packages/Conversation/Sources/Conversation/ConversationSidebarView.swift create mode 100644 Packages/Conversation/Sources/Conversation/ConversationStore.swift create mode 100644 Packages/Conversation/Sources/Conversation/ConversationView.swift create mode 100644 Packages/Conversation/Sources/Conversation/PromptEditorView.swift create mode 100644 Packages/Conversation/Tests/ConversationTests/ConversationTests.swift create mode 100644 Packages/Models/.gitignore create mode 100644 Packages/Models/Package.swift create mode 100644 Packages/Models/Sources/Models/Conversation.swift create mode 100644 Packages/Models/Sources/Models/HuggingFaceEndpoint.swift create mode 100644 Packages/Models/Sources/Models/Language.swift create mode 100644 Packages/Models/Sources/Models/Message.swift create mode 100644 Packages/Models/Sources/Models/Model.swift create mode 100644 Packages/Models/Sources/Models/Role.swift create mode 100644 Packages/Models/Sources/Models/SettingsTab.swift create mode 100644 Packages/Models/Tests/ModelsTests/LanguageTests.swift create mode 100644 Packages/Models/Tests/ModelsTests/ModelsTests.swift create mode 100644 Packages/Settings/.gitignore create mode 100644 Packages/Settings/Package.swift create mode 100644 Packages/Settings/Sources/Settings/AboutView.swift create mode 100644 Packages/Settings/Sources/Settings/DefaultConversationView.swift create mode 100644 Packages/Settings/Sources/Settings/DownloadManagerView.swift create mode 100644 Packages/Settings/Sources/Settings/ExperimentalFeaturesView.swift create mode 100644 Packages/Settings/Sources/Settings/GeneralView.swift create mode 100644 Packages/Settings/Sources/Settings/HuggingFaceView.swift create mode 100644 Packages/Settings/Sources/Settings/MCPServersView.swift create mode 100644 Packages/Settings/Sources/Settings/MLXCommunityView.swift create mode 100644 Packages/Settings/Sources/Settings/ModelsView.swift create mode 100644 Packages/Settings/Sources/Settings/ProvidersView.swift create mode 100644 Packages/Settings/Sources/Settings/SearchView.swift create mode 100644 Packages/Settings/Sources/Settings/SettingsSidebarView.swift create mode 100644 Packages/Settings/Sources/Settings/SettingsStore.swift create mode 100644 Packages/Settings/Sources/Settings/SettingsView.swift create mode 100644 Packages/Settings/Tests/SettingsTests/SettingsTests.swift create mode 100644 Packages/UltraUI/.gitignore create mode 100644 Packages/UltraUI/Package.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/Divided.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/Extensions/EnvironmentValues+Extensions.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/Extensions/NSWindow+Extensions.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/Extensions/View+Extensions.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/GreetingView.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/Modifiers/SidebarViewModifier.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/Modifiers/UltraWindowStyleViewModifier.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraButtonStyle.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraDynamicButtonStyle.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraIconButtonStyle.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraPlainButtonStyle.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraSidebarButtonStyle.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/Styles/HorizontalLabeledContentStyle.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/TextareaView.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/UltraNavigationSplitView.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsPanel.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsWindowController.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraPicker.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/UltraSection.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/UltraSecureTextField.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/UltraSidebarBackgroundView.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/UltraTextField.swift create mode 100644 Packages/UltraUI/Sources/UltraUI/UltraWindowBackgroundView.swift create mode 100644 Packages/UltraUI/Tests/UltraUITests/UltraUITests.swift diff --git a/ChatMLX.xcodeproj/project.pbxproj b/ChatMLX.xcodeproj/project.pbxproj index 2e9f4fa..3ecd5b0 100644 --- a/ChatMLX.xcodeproj/project.pbxproj +++ b/ChatMLX.xcodeproj/project.pbxproj @@ -3,426 +3,71 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 70; objects = { /* Begin PBXBuildFile section */ - 523D8C512C9C7FCC0092791C /* Transformers in Frameworks */ = {isa = PBXBuildFile; productRef = 523D8C502C9C7FCC0092791C /* Transformers */; }; - 526675462C85EDCB001EF113 /* ChatMLXApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526675452C85EDCB001EF113 /* ChatMLXApp.swift */; }; - 5266754D2C85EDCC001EF113 /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5266754C2C85EDCC001EF113 /* Preview Assets.xcassets */; }; - 526675622C85EFB3001EF113 /* Alamofire in Frameworks */ = {isa = PBXBuildFile; productRef = 526675612C85EFB3001EF113 /* Alamofire */; }; - 526675652C85EFC7001EF113 /* AlertToast in Frameworks */ = {isa = PBXBuildFile; productRef = 526675642C85EFC7001EF113 /* AlertToast */; }; - 526675682C85EFDF001EF113 /* CompactSlider in Frameworks */ = {isa = PBXBuildFile; productRef = 526675672C85EFDF001EF113 /* CompactSlider */; }; - 5266756B2C85F0E8001EF113 /* Defaults in Frameworks */ = {isa = PBXBuildFile; productRef = 5266756A2C85F0E8001EF113 /* Defaults */; }; - 5266756E2C85F0FF001EF113 /* Luminare in Frameworks */ = {isa = PBXBuildFile; productRef = 5266756D2C85F0FF001EF113 /* Luminare */; }; - 526675742C85F1F9001EF113 /* Splash in Frameworks */ = {isa = PBXBuildFile; productRef = 526675732C85F1F9001EF113 /* Splash */; }; - 526675772C85F26B001EF113 /* Logging in Frameworks */ = {isa = PBXBuildFile; productRef = 526675762C85F26B001EF113 /* Logging */; }; - 5266757A2C85F487001EF113 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = 526675792C85F487001EF113 /* MarkdownUI */; }; - 5266757D2C85F54D001EF113 /* SwiftUIIntrospect in Frameworks */ = {isa = PBXBuildFile; productRef = 5266757C2C85F54D001EF113 /* SwiftUIIntrospect */; }; - 526676402C85F903001EF113 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 526675FD2C85F903001EF113 /* Assets.xcassets */; }; - 526676412C85F903001EF113 /* SplashCodeSyntaxHighlighter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526675FE2C85F903001EF113 /* SplashCodeSyntaxHighlighter.swift */; }; - 526676422C85F903001EF113 /* TextOutputFormat.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526675FF2C85F903001EF113 /* TextOutputFormat.swift */; }; - 526676432C85F903001EF113 /* EffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676012C85F903001EF113 /* EffectView.swift */; }; - 526676442C85F903001EF113 /* UltramanMinimalistWindowModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676022C85F903001EF113 /* UltramanMinimalistWindowModifier.swift */; }; - 526676452C85F903001EF113 /* UltramanNavigationSplitView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676032C85F903001EF113 /* UltramanNavigationSplitView.swift */; }; - 526676462C85F903001EF113 /* UltramanSecureField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676042C85F903001EF113 /* UltramanSecureField.swift */; }; - 526676472C85F903001EF113 /* UltramanSidebarButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676052C85F903001EF113 /* UltramanSidebarButtonStyle.swift */; }; - 526676482C85F903001EF113 /* UltramanTextEditorWithPlaceholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676062C85F903001EF113 /* UltramanTextEditorWithPlaceholder.swift */; }; - 526676492C85F903001EF113 /* UltramanTextField.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676072C85F903001EF113 /* UltramanTextField.swift */; }; - 5266764A2C85F903001EF113 /* UltramanWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676082C85F903001EF113 /* UltramanWindow.swift */; }; - 5266764B2C85F903001EF113 /* ConversationDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266760A2C85F903001EF113 /* ConversationDetailView.swift */; }; - 5266764C2C85F903001EF113 /* ConversationSidebarItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266760B2C85F903001EF113 /* ConversationSidebarItem.swift */; }; - 5266764D2C85F903001EF113 /* ConversationSidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266760C2C85F903001EF113 /* ConversationSidebarView.swift */; }; - 5266764E2C85F903001EF113 /* ConversationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266760D2C85F903001EF113 /* ConversationView.swift */; }; - 5266764F2C85F903001EF113 /* EmptyConversation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266760E2C85F903001EF113 /* EmptyConversation.swift */; }; - 526676502C85F903001EF113 /* MessageBubbleView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266760F2C85F903001EF113 /* MessageBubbleView.swift */; }; - 526676512C85F903001EF113 /* RightSidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676102C85F903001EF113 /* RightSidebarView.swift */; }; - 526676522C85F903001EF113 /* DownloadManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676122C85F903001EF113 /* DownloadManagerView.swift */; }; - 526676532C85F903001EF113 /* DownloadTaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676132C85F903001EF113 /* DownloadTaskView.swift */; }; - 526676542C85F903001EF113 /* LocalModelItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676152C85F903001EF113 /* LocalModelItemView.swift */; }; - 526676552C85F903001EF113 /* LocalModelsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676162C85F903001EF113 /* LocalModelsView.swift */; }; - 526676562C85F903001EF113 /* MLXCommunityItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676182C85F903001EF113 /* MLXCommunityItemView.swift */; }; - 526676572C85F903001EF113 /* MLXCommunityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676192C85F903001EF113 /* MLXCommunityView.swift */; }; - 526676582C85F903001EF113 /* AboutView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266761B2C85F903001EF113 /* AboutView.swift */; }; - 526676592C85F903001EF113 /* DefaultConversationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266761C2C85F903001EF113 /* DefaultConversationView.swift */; }; - 5266765A2C85F903001EF113 /* GeneralView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266761D2C85F903001EF113 /* GeneralView.swift */; }; - 5266765B2C85F903001EF113 /* HuggingFaceView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266761E2C85F903001EF113 /* HuggingFaceView.swift */; }; - 5266765C2C85F903001EF113 /* SettingsSidebarItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266761F2C85F903001EF113 /* SettingsSidebarItemView.swift */; }; - 5266765D2C85F903001EF113 /* SettingsSidebarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676202C85F903001EF113 /* SettingsSidebarView.swift */; }; - 5266765E2C85F903001EF113 /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676212C85F903001EF113 /* SettingsView.swift */; }; - 526676602C85F903001EF113 /* DisplayStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676252C85F903001EF113 /* DisplayStyle.swift */; }; - 526676612C85F903001EF113 /* DownloadTask.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676262C85F903001EF113 /* DownloadTask.swift */; }; - 526676622C85F903001EF113 /* Language.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676272C85F903001EF113 /* Language.swift */; }; - 526676632C85F903001EF113 /* LocalModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676282C85F903001EF113 /* LocalModel.swift */; }; - 526676642C85F903001EF113 /* LocalModelGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676292C85F903001EF113 /* LocalModelGroup.swift */; }; - 526676662C85F903001EF113 /* RemoteModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266762B2C85F903001EF113 /* RemoteModel.swift */; }; - 526676672C85F903001EF113 /* SettingsTab.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266762C2C85F903001EF113 /* SettingsTab.swift */; }; - 526676682C85F903001EF113 /* SettingsTabGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266762D2C85F903001EF113 /* SettingsTabGroup.swift */; }; - 526676692C85F903001EF113 /* Styles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266762E2C85F903001EF113 /* Styles.swift */; }; - 5266766A2C85F903001EF113 /* Downloader.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676302C85F903001EF113 /* Downloader.swift */; }; - 5266766B2C85F903001EF113 /* Hub.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676312C85F903001EF113 /* Hub.swift */; }; - 5266766C2C85F903001EF113 /* HubApi.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676322C85F903001EF113 /* HubApi.swift */; }; - 5266766D2C85F903001EF113 /* LLMRunner.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676342C85F903001EF113 /* LLMRunner.swift */; }; - 5266766E2C85F903001EF113 /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676352C85F903001EF113 /* Logger.swift */; }; - 526676702C85F903001EF113 /* Defaults+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676382C85F903001EF113 /* Defaults+Extensions.swift */; }; - 526676712C85F903001EF113 /* MarkdownUI+Theme+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 526676392C85F903001EF113 /* MarkdownUI+Theme+Extensions.swift */; }; - 526676722C85F903001EF113 /* NSWindow+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266763A2C85F903001EF113 /* NSWindow+Extensions.swift */; }; - 526676732C85F903001EF113 /* String+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266763B2C85F903001EF113 /* String+Extensions.swift */; }; - 526676742C85F903001EF113 /* View+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266763C2C85F903001EF113 /* View+Extensions.swift */; }; - 526676782C85F9DA001EF113 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 526676772C85F9DA001EF113 /* Localizable.xcstrings */; }; - 527F48152C9EFD5D006AF9FA /* LLM in Frameworks */ = {isa = PBXBuildFile; productRef = 527F48142C9EFD5D006AF9FA /* LLM */; }; - 528D82262CABE19900163AAB /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528D82252CABE19000163AAB /* Date+Extensions.swift */; }; - 528D83192CAD491900163AAB /* ChatMLX.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 528D83172CAD491900163AAB /* ChatMLX.xcdatamodeld */; }; - 528D831C2CAD49E600163AAB /* PersistenceController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528D831B2CAD49E600163AAB /* PersistenceController.swift */; }; - 528D83292CAD5C9100163AAB /* Conversation+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528D83252CAD5C9100163AAB /* Conversation+CoreDataClass.swift */; }; - 528D832A2CAD5C9100163AAB /* Conversation+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528D83262CAD5C9100163AAB /* Conversation+CoreDataProperties.swift */; }; - 528D832B2CAD5C9100163AAB /* Message+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528D83272CAD5C9100163AAB /* Message+CoreDataClass.swift */; }; - 528D832C2CAD5C9100163AAB /* Message+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528D83282CAD5C9100163AAB /* Message+CoreDataProperties.swift */; }; - 528D83372CADB64600163AAB /* ConversationViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528D83362CADB64300163AAB /* ConversationViewModel.swift */; }; - 528D83392CAE51EC00163AAB /* Role.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528D83382CAE51EC00163AAB /* Role.swift */; }; - 528DBE2F2C9C86FB004CDD88 /* Transformers in Frameworks */ = {isa = PBXBuildFile; productRef = 528DBE2E2C9C86FB004CDD88 /* Transformers */; }; - 52A689F62CAE8AAB0078CDF9 /* TimeInterval+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A689F52CAE8AAB0078CDF9 /* TimeInterval+Extensions.swift */; }; - 52A689F82CAE8DA30078CDF9 /* SettingsViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A689F72CAE8DA00078CDF9 /* SettingsViewModel.swift */; }; - 52A689FA2CAECFE00078CDF9 /* ErrorAlertModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A689F92CAECFDE0078CDF9 /* ErrorAlertModifier.swift */; }; - 52B053932CB2B0D500E8DDBA /* ColorWheel.metal in Sources */ = {isa = PBXBuildFile; fileRef = 52B053922CB2B0D300E8DDBA /* ColorWheel.metal */; }; - 52B053952CB2B64500E8DDBA /* NoneInteractWindow.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B053942CB2B64500E8DDBA /* NoneInteractWindow.swift */; }; - 52B0539D2CB2BF0D00E8DDBA /* AppleIntelligenceEffectView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B0539A2CB2BF0D00E8DDBA /* AppleIntelligenceEffectView.swift */; }; - 52B0539E2CB2BF0D00E8DDBA /* AppleIntelligenceEffectController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B053992CB2BF0D00E8DDBA /* AppleIntelligenceEffectController.swift */; }; - 52B053A02CB2C2CD00E8DDBA /* AppleIntelligenceEffectManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B0539F2CB2C2CD00E8DDBA /* AppleIntelligenceEffectManager.swift */; }; - 52B053A22CB2F6F900E8DDBA /* AppleIntelligenceEffectDisplay.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B053A12CB2F6F900E8DDBA /* AppleIntelligenceEffectDisplay.swift */; }; - 52B053A62CB38E8700E8DDBA /* ExperimentalFeaturesView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B053A52CB38E7F00E8DDBA /* ExperimentalFeaturesView.swift */; }; - 52E50B1D2C8D6E81005A89DE /* LLM in Frameworks */ = {isa = PBXBuildFile; productRef = 52E50B1C2C8D6E81005A89DE /* LLM */; }; - 52E50B202C8D719B005A89DE /* LLM in Frameworks */ = {isa = PBXBuildFile; productRef = 52E50B1F2C8D719B005A89DE /* LLM */; }; - 52E50B222C8D719B005A89DE /* MNIST in Frameworks */ = {isa = PBXBuildFile; productRef = 52E50B212C8D719B005A89DE /* MNIST */; }; - 52E50B292C8DF111005A89DE /* LLM in Frameworks */ = {isa = PBXBuildFile; productRef = 52E50B282C8DF111005A89DE /* LLM */; }; - 52E50B2B2C8DF111005A89DE /* MNIST in Frameworks */ = {isa = PBXBuildFile; productRef = 52E50B2A2C8DF111005A89DE /* MNIST */; }; - 52FD80BE2C8F21C2006C50F1 /* LLM in Frameworks */ = {isa = PBXBuildFile; productRef = 52FD80BD2C8F21C2006C50F1 /* LLM */; }; - 52FD80C02C8F21C2006C50F1 /* MNIST in Frameworks */ = {isa = PBXBuildFile; productRef = 52FD80BF2C8F21C2006C50F1 /* MNIST */; }; - 52FD80C32C8F288A006C50F1 /* LLM in Frameworks */ = {isa = PBXBuildFile; productRef = 52FD80C22C8F288A006C50F1 /* LLM */; }; - 52FD80C52C8F288A006C50F1 /* MNIST in Frameworks */ = {isa = PBXBuildFile; productRef = 52FD80C42C8F288A006C50F1 /* MNIST */; }; - 52FD80C82C8F5E42006C50F1 /* LLM in Frameworks */ = {isa = PBXBuildFile; productRef = 52FD80C72C8F5E42006C50F1 /* LLM */; }; - 52FD80CA2C8F5E42006C50F1 /* MNIST in Frameworks */ = {isa = PBXBuildFile; productRef = 52FD80C92C8F5E42006C50F1 /* MNIST */; }; + 523068232D71E6540069F093 /* Conversation in Frameworks */ = {isa = PBXBuildFile; productRef = 523068222D71E6540069F093 /* Conversation */; }; + 523068982D71F0FC0069F093 /* Settings in Frameworks */ = {isa = PBXBuildFile; productRef = 523068972D71F0FC0069F093 /* Settings */; }; + 52996F0C2D6F7787003AD246 /* HuggingfaceHub in Frameworks */ = {isa = PBXBuildFile; productRef = 52996F0B2D6F7787003AD246 /* HuggingfaceHub */; }; + 52996F0F2D6F77A2003AD246 /* HuggingfaceHub in Frameworks */ = {isa = PBXBuildFile; productRef = 52996F0E2D6F77A2003AD246 /* HuggingfaceHub */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 523067CF2D71DEE70069F093 /* UltraUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = UltraUI; path = Packages/UltraUI; sourceTree = ""; }; + 523067D42D71E5040069F093 /* Common */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Common; path = Packages/Common; sourceTree = SOURCE_ROOT; }; + 523068212D71E6390069F093 /* Conversation */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Conversation; path = Packages/Conversation; sourceTree = ""; }; + 523068242D71E68D0069F093 /* Models */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Models; path = Packages/Models; sourceTree = SOURCE_ROOT; }; + 523068672D71F09C0069F093 /* Settings */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Settings; path = Packages/Settings; sourceTree = SOURCE_ROOT; }; 526675422C85EDCB001EF113 /* ChatMLX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ChatMLX.app; sourceTree = BUILT_PRODUCTS_DIR; }; - 526675452C85EDCB001EF113 /* ChatMLXApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMLXApp.swift; sourceTree = ""; }; - 5266754C2C85EDCC001EF113 /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; - 5266754E2C85EDCC001EF113 /* ChatMLX.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ChatMLX.entitlements; sourceTree = ""; }; - 526675FD2C85F903001EF113 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; - 526675FE2C85F903001EF113 /* SplashCodeSyntaxHighlighter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SplashCodeSyntaxHighlighter.swift; sourceTree = ""; }; - 526675FF2C85F903001EF113 /* TextOutputFormat.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextOutputFormat.swift; sourceTree = ""; }; - 526676012C85F903001EF113 /* EffectView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EffectView.swift; sourceTree = ""; }; - 526676022C85F903001EF113 /* UltramanMinimalistWindowModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UltramanMinimalistWindowModifier.swift; sourceTree = ""; }; - 526676032C85F903001EF113 /* UltramanNavigationSplitView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UltramanNavigationSplitView.swift; sourceTree = ""; }; - 526676042C85F903001EF113 /* UltramanSecureField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UltramanSecureField.swift; sourceTree = ""; }; - 526676052C85F903001EF113 /* UltramanSidebarButtonStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UltramanSidebarButtonStyle.swift; sourceTree = ""; }; - 526676062C85F903001EF113 /* UltramanTextEditorWithPlaceholder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UltramanTextEditorWithPlaceholder.swift; sourceTree = ""; }; - 526676072C85F903001EF113 /* UltramanTextField.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UltramanTextField.swift; sourceTree = ""; }; - 526676082C85F903001EF113 /* UltramanWindow.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = UltramanWindow.swift; sourceTree = ""; }; - 5266760A2C85F903001EF113 /* ConversationDetailView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationDetailView.swift; sourceTree = ""; }; - 5266760B2C85F903001EF113 /* ConversationSidebarItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationSidebarItem.swift; sourceTree = ""; }; - 5266760C2C85F903001EF113 /* ConversationSidebarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationSidebarView.swift; sourceTree = ""; }; - 5266760D2C85F903001EF113 /* ConversationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationView.swift; sourceTree = ""; }; - 5266760E2C85F903001EF113 /* EmptyConversation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = EmptyConversation.swift; sourceTree = ""; }; - 5266760F2C85F903001EF113 /* MessageBubbleView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MessageBubbleView.swift; sourceTree = ""; }; - 526676102C85F903001EF113 /* RightSidebarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RightSidebarView.swift; sourceTree = ""; }; - 526676122C85F903001EF113 /* DownloadManagerView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadManagerView.swift; sourceTree = ""; }; - 526676132C85F903001EF113 /* DownloadTaskView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadTaskView.swift; sourceTree = ""; }; - 526676152C85F903001EF113 /* LocalModelItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalModelItemView.swift; sourceTree = ""; }; - 526676162C85F903001EF113 /* LocalModelsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalModelsView.swift; sourceTree = ""; }; - 526676182C85F903001EF113 /* MLXCommunityItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MLXCommunityItemView.swift; sourceTree = ""; }; - 526676192C85F903001EF113 /* MLXCommunityView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MLXCommunityView.swift; sourceTree = ""; }; - 5266761B2C85F903001EF113 /* AboutView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AboutView.swift; sourceTree = ""; }; - 5266761C2C85F903001EF113 /* DefaultConversationView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DefaultConversationView.swift; sourceTree = ""; }; - 5266761D2C85F903001EF113 /* GeneralView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GeneralView.swift; sourceTree = ""; }; - 5266761E2C85F903001EF113 /* HuggingFaceView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HuggingFaceView.swift; sourceTree = ""; }; - 5266761F2C85F903001EF113 /* SettingsSidebarItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsSidebarItemView.swift; sourceTree = ""; }; - 526676202C85F903001EF113 /* SettingsSidebarView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsSidebarView.swift; sourceTree = ""; }; - 526676212C85F903001EF113 /* SettingsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = ""; }; - 526676252C85F903001EF113 /* DisplayStyle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayStyle.swift; sourceTree = ""; }; - 526676262C85F903001EF113 /* DownloadTask.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DownloadTask.swift; sourceTree = ""; }; - 526676272C85F903001EF113 /* Language.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Language.swift; sourceTree = ""; }; - 526676282C85F903001EF113 /* LocalModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalModel.swift; sourceTree = ""; }; - 526676292C85F903001EF113 /* LocalModelGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LocalModelGroup.swift; sourceTree = ""; }; - 5266762B2C85F903001EF113 /* RemoteModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RemoteModel.swift; sourceTree = ""; }; - 5266762C2C85F903001EF113 /* SettingsTab.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTab.swift; sourceTree = ""; }; - 5266762D2C85F903001EF113 /* SettingsTabGroup.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SettingsTabGroup.swift; sourceTree = ""; }; - 5266762E2C85F903001EF113 /* Styles.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Styles.swift; sourceTree = ""; }; - 526676302C85F903001EF113 /* Downloader.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Downloader.swift; sourceTree = ""; }; - 526676312C85F903001EF113 /* Hub.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Hub.swift; sourceTree = ""; }; - 526676322C85F903001EF113 /* HubApi.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = HubApi.swift; sourceTree = ""; }; - 526676342C85F903001EF113 /* LLMRunner.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LLMRunner.swift; sourceTree = ""; }; - 526676352C85F903001EF113 /* Logger.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; - 526676382C85F903001EF113 /* Defaults+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Defaults+Extensions.swift"; sourceTree = ""; }; - 526676392C85F903001EF113 /* MarkdownUI+Theme+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MarkdownUI+Theme+Extensions.swift"; sourceTree = ""; }; - 5266763A2C85F903001EF113 /* NSWindow+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSWindow+Extensions.swift"; sourceTree = ""; }; - 5266763B2C85F903001EF113 /* String+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "String+Extensions.swift"; sourceTree = ""; }; - 5266763C2C85F903001EF113 /* View+Extensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View+Extensions.swift"; sourceTree = ""; }; - 526676762C85F952001EF113 /* ChatMLXRelease.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = ChatMLXRelease.entitlements; sourceTree = ""; }; - 526676772C85F9DA001EF113 /* Localizable.xcstrings */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = ""; }; - 528D82252CABE19000163AAB /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = ""; }; - 528D83182CAD491900163AAB /* ChatMLX.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ChatMLX.xcdatamodel; sourceTree = ""; }; - 528D831B2CAD49E600163AAB /* PersistenceController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PersistenceController.swift; sourceTree = ""; }; - 528D83252CAD5C9100163AAB /* Conversation+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Conversation+CoreDataClass.swift"; sourceTree = ""; }; - 528D83262CAD5C9100163AAB /* Conversation+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Conversation+CoreDataProperties.swift"; sourceTree = ""; }; - 528D83272CAD5C9100163AAB /* Message+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+CoreDataClass.swift"; sourceTree = ""; }; - 528D83282CAD5C9100163AAB /* Message+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+CoreDataProperties.swift"; sourceTree = ""; }; - 528D83362CADB64300163AAB /* ConversationViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationViewModel.swift; sourceTree = ""; }; - 528D83382CAE51EC00163AAB /* Role.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Role.swift; sourceTree = ""; }; - 52A689F52CAE8AAB0078CDF9 /* TimeInterval+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TimeInterval+Extensions.swift"; sourceTree = ""; }; - 52A689F72CAE8DA00078CDF9 /* SettingsViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsViewModel.swift; sourceTree = ""; }; - 52A689F92CAECFDE0078CDF9 /* ErrorAlertModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlertModifier.swift; sourceTree = ""; }; - 52B053922CB2B0D300E8DDBA /* ColorWheel.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = ColorWheel.metal; sourceTree = ""; }; - 52B053942CB2B64500E8DDBA /* NoneInteractWindow.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NoneInteractWindow.swift; sourceTree = ""; }; - 52B053992CB2BF0D00E8DDBA /* AppleIntelligenceEffectController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleIntelligenceEffectController.swift; sourceTree = ""; }; - 52B0539A2CB2BF0D00E8DDBA /* AppleIntelligenceEffectView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleIntelligenceEffectView.swift; sourceTree = ""; }; - 52B0539F2CB2C2CD00E8DDBA /* AppleIntelligenceEffectManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleIntelligenceEffectManager.swift; sourceTree = ""; }; - 52B053A12CB2F6F900E8DDBA /* AppleIntelligenceEffectDisplay.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleIntelligenceEffectDisplay.swift; sourceTree = ""; }; - 52B053A52CB38E7F00E8DDBA /* ExperimentalFeaturesView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExperimentalFeaturesView.swift; sourceTree = ""; }; /* End PBXFileReference section */ +/* Begin PBXFileSystemSynchronizedRootGroup section */ + 52996ED72D6F7437003AD246 /* ChatMLX */ = {isa = PBXFileSystemSynchronizedRootGroup; explicitFileTypes = {}; explicitFolders = (); path = ChatMLX; sourceTree = ""; }; +/* End PBXFileSystemSynchronizedRootGroup section */ + /* Begin PBXFrameworksBuildPhase section */ 5266753F2C85EDCB001EF113 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 523D8C512C9C7FCC0092791C /* Transformers in Frameworks */, - 527F48152C9EFD5D006AF9FA /* LLM in Frameworks */, - 52FD80C02C8F21C2006C50F1 /* MNIST in Frameworks */, - 52E50B222C8D719B005A89DE /* MNIST in Frameworks */, - 5266756B2C85F0E8001EF113 /* Defaults in Frameworks */, - 526675622C85EFB3001EF113 /* Alamofire in Frameworks */, - 52FD80CA2C8F5E42006C50F1 /* MNIST in Frameworks */, - 52FD80BE2C8F21C2006C50F1 /* LLM in Frameworks */, - 5266757D2C85F54D001EF113 /* SwiftUIIntrospect in Frameworks */, - 52FD80C52C8F288A006C50F1 /* MNIST in Frameworks */, - 52E50B2B2C8DF111005A89DE /* MNIST in Frameworks */, - 5266757A2C85F487001EF113 /* MarkdownUI in Frameworks */, - 526675742C85F1F9001EF113 /* Splash in Frameworks */, - 52FD80C82C8F5E42006C50F1 /* LLM in Frameworks */, - 526675682C85EFDF001EF113 /* CompactSlider in Frameworks */, - 5266756E2C85F0FF001EF113 /* Luminare in Frameworks */, - 526675772C85F26B001EF113 /* Logging in Frameworks */, - 52E50B202C8D719B005A89DE /* LLM in Frameworks */, - 52E50B1D2C8D6E81005A89DE /* LLM in Frameworks */, - 528DBE2F2C9C86FB004CDD88 /* Transformers in Frameworks */, - 526675652C85EFC7001EF113 /* AlertToast in Frameworks */, - 52FD80C32C8F288A006C50F1 /* LLM in Frameworks */, - 52E50B292C8DF111005A89DE /* LLM in Frameworks */, + 52996F0F2D6F77A2003AD246 /* HuggingfaceHub in Frameworks */, + 52996F0C2D6F7787003AD246 /* HuggingfaceHub in Frameworks */, + 523068982D71F0FC0069F093 /* Settings in Frameworks */, + 523068232D71E6540069F093 /* Conversation in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ - 526675392C85EDCB001EF113 = { - isa = PBXGroup; - children = ( - 526675442C85EDCB001EF113 /* ChatMLX */, - 526675432C85EDCB001EF113 /* Products */, - ); - sourceTree = ""; - }; - 526675432C85EDCB001EF113 /* Products */ = { - isa = PBXGroup; - children = ( - 526675422C85EDCB001EF113 /* ChatMLX.app */, - ); - name = Products; - sourceTree = ""; - }; - 526675442C85EDCB001EF113 /* ChatMLX */ = { - isa = PBXGroup; - children = ( - 5266754E2C85EDCC001EF113 /* ChatMLX.entitlements */, - 526676762C85F952001EF113 /* ChatMLXRelease.entitlements */, - 526675452C85EDCB001EF113 /* ChatMLXApp.swift */, - 526675FD2C85F903001EF113 /* Assets.xcassets */, - 526676772C85F9DA001EF113 /* Localizable.xcstrings */, - 526676092C85F903001EF113 /* Components */, - 5266763D2C85F903001EF113 /* Extensions */, - 526676232C85F903001EF113 /* Features */, - 5266762F2C85F903001EF113 /* Models */, - 5266754B2C85EDCC001EF113 /* Preview Content */, - 526676372C85F903001EF113 /* Utilities */, - ); - path = ChatMLX; - sourceTree = ""; - }; - 5266754B2C85EDCC001EF113 /* Preview Content */ = { - isa = PBXGroup; - children = ( - 5266754C2C85EDCC001EF113 /* Preview Assets.xcassets */, - ); - path = "Preview Content"; - sourceTree = ""; - }; - 526676002C85F903001EF113 /* SyntaxHighlighter */ = { - isa = PBXGroup; - children = ( - 526675FE2C85F903001EF113 /* SplashCodeSyntaxHighlighter.swift */, - 526675FF2C85F903001EF113 /* TextOutputFormat.swift */, - ); - path = SyntaxHighlighter; - sourceTree = ""; - }; - 526676092C85F903001EF113 /* Components */ = { - isa = PBXGroup; - children = ( - 52B053942CB2B64500E8DDBA /* NoneInteractWindow.swift */, - 52B0538B2CB2B03200E8DDBA /* AppleIntelligenceEffect */, - 52A689F92CAECFDE0078CDF9 /* ErrorAlertModifier.swift */, - 526676002C85F903001EF113 /* SyntaxHighlighter */, - 526676012C85F903001EF113 /* EffectView.swift */, - 526676022C85F903001EF113 /* UltramanMinimalistWindowModifier.swift */, - 526676032C85F903001EF113 /* UltramanNavigationSplitView.swift */, - 526676042C85F903001EF113 /* UltramanSecureField.swift */, - 526676052C85F903001EF113 /* UltramanSidebarButtonStyle.swift */, - 526676062C85F903001EF113 /* UltramanTextEditorWithPlaceholder.swift */, - 526676072C85F903001EF113 /* UltramanTextField.swift */, - 526676082C85F903001EF113 /* UltramanWindow.swift */, - ); - path = Components; - sourceTree = ""; - }; - 526676112C85F903001EF113 /* Conversation */ = { - isa = PBXGroup; - children = ( - 528D83362CADB64300163AAB /* ConversationViewModel.swift */, - 5266760A2C85F903001EF113 /* ConversationDetailView.swift */, - 5266760B2C85F903001EF113 /* ConversationSidebarItem.swift */, - 5266760C2C85F903001EF113 /* ConversationSidebarView.swift */, - 5266760D2C85F903001EF113 /* ConversationView.swift */, - 5266760E2C85F903001EF113 /* EmptyConversation.swift */, - 5266760F2C85F903001EF113 /* MessageBubbleView.swift */, - 526676102C85F903001EF113 /* RightSidebarView.swift */, - ); - path = Conversation; - sourceTree = ""; - }; - 526676142C85F903001EF113 /* DownloadManager */ = { - isa = PBXGroup; - children = ( - 526676122C85F903001EF113 /* DownloadManagerView.swift */, - 526676132C85F903001EF113 /* DownloadTaskView.swift */, - ); - path = DownloadManager; - sourceTree = ""; - }; - 526676172C85F903001EF113 /* LocalModels */ = { - isa = PBXGroup; - children = ( - 526676152C85F903001EF113 /* LocalModelItemView.swift */, - 526676162C85F903001EF113 /* LocalModelsView.swift */, - ); - path = LocalModels; - sourceTree = ""; - }; - 5266761A2C85F903001EF113 /* MLXCommunity */ = { - isa = PBXGroup; - children = ( - 526676182C85F903001EF113 /* MLXCommunityItemView.swift */, - 526676192C85F903001EF113 /* MLXCommunityView.swift */, - ); - path = MLXCommunity; - sourceTree = ""; - }; - 526676222C85F903001EF113 /* Settings */ = { + 523067D02D71DF3A0069F093 /* Frameworks */ = { isa = PBXGroup; children = ( - 52B053A52CB38E7F00E8DDBA /* ExperimentalFeaturesView.swift */, - 52A689F72CAE8DA00078CDF9 /* SettingsViewModel.swift */, - 526676142C85F903001EF113 /* DownloadManager */, - 526676172C85F903001EF113 /* LocalModels */, - 5266761A2C85F903001EF113 /* MLXCommunity */, - 5266761B2C85F903001EF113 /* AboutView.swift */, - 5266761C2C85F903001EF113 /* DefaultConversationView.swift */, - 5266761D2C85F903001EF113 /* GeneralView.swift */, - 5266761E2C85F903001EF113 /* HuggingFaceView.swift */, - 5266761F2C85F903001EF113 /* SettingsSidebarItemView.swift */, - 526676202C85F903001EF113 /* SettingsSidebarView.swift */, - 526676212C85F903001EF113 /* SettingsView.swift */, ); - path = Settings; + name = Frameworks; sourceTree = ""; }; - 526676232C85F903001EF113 /* Features */ = { - isa = PBXGroup; - children = ( - 526676112C85F903001EF113 /* Conversation */, - 526676222C85F903001EF113 /* Settings */, - ); - path = Features; - sourceTree = ""; - }; - 5266762F2C85F903001EF113 /* Models */ = { - isa = PBXGroup; - children = ( - 526676252C85F903001EF113 /* DisplayStyle.swift */, - 526676262C85F903001EF113 /* DownloadTask.swift */, - 526676272C85F903001EF113 /* Language.swift */, - 52B053A12CB2F6F900E8DDBA /* AppleIntelligenceEffectDisplay.swift */, - 526676282C85F903001EF113 /* LocalModel.swift */, - 526676292C85F903001EF113 /* LocalModelGroup.swift */, - 528D83382CAE51EC00163AAB /* Role.swift */, - 5266762B2C85F903001EF113 /* RemoteModel.swift */, - 5266762C2C85F903001EF113 /* SettingsTab.swift */, - 5266762D2C85F903001EF113 /* SettingsTabGroup.swift */, - 5266762E2C85F903001EF113 /* Styles.swift */, - 528D83252CAD5C9100163AAB /* Conversation+CoreDataClass.swift */, - 528D83262CAD5C9100163AAB /* Conversation+CoreDataProperties.swift */, - 528D83272CAD5C9100163AAB /* Message+CoreDataClass.swift */, - 528D83282CAD5C9100163AAB /* Message+CoreDataProperties.swift */, - 528D83172CAD491900163AAB /* ChatMLX.xcdatamodeld */, - ); - path = Models; - sourceTree = ""; - }; - 526676332C85F903001EF113 /* Huggingface */ = { - isa = PBXGroup; - children = ( - 526676302C85F903001EF113 /* Downloader.swift */, - 526676312C85F903001EF113 /* Hub.swift */, - 526676322C85F903001EF113 /* HubApi.swift */, - ); - path = Huggingface; - sourceTree = ""; - }; - 526676372C85F903001EF113 /* Utilities */ = { - isa = PBXGroup; - children = ( - 526676342C85F903001EF113 /* LLMRunner.swift */, - 526676352C85F903001EF113 /* Logger.swift */, - 528D831B2CAD49E600163AAB /* PersistenceController.swift */, - 526676332C85F903001EF113 /* Huggingface */, - ); - path = Utilities; - sourceTree = ""; - }; - 5266763D2C85F903001EF113 /* Extensions */ = { + 526675392C85EDCB001EF113 = { isa = PBXGroup; children = ( - 528D82252CABE19000163AAB /* Date+Extensions.swift */, - 52A689F52CAE8AAB0078CDF9 /* TimeInterval+Extensions.swift */, - 526676382C85F903001EF113 /* Defaults+Extensions.swift */, - 526676392C85F903001EF113 /* MarkdownUI+Theme+Extensions.swift */, - 5266763A2C85F903001EF113 /* NSWindow+Extensions.swift */, - 5266763B2C85F903001EF113 /* String+Extensions.swift */, - 5266763C2C85F903001EF113 /* View+Extensions.swift */, + 523067D42D71E5040069F093 /* Common */, + 523068212D71E6390069F093 /* Conversation */, + 523068242D71E68D0069F093 /* Models */, + 523068672D71F09C0069F093 /* Settings */, + 523067CF2D71DEE70069F093 /* UltraUI */, + 52996ED72D6F7437003AD246 /* ChatMLX */, + 523067D02D71DF3A0069F093 /* Frameworks */, + 526675432C85EDCB001EF113 /* Products */, ); - path = Extensions; sourceTree = ""; }; - 52B0538B2CB2B03200E8DDBA /* AppleIntelligenceEffect */ = { + 526675432C85EDCB001EF113 /* Products */ = { isa = PBXGroup; children = ( - 52B0539F2CB2C2CD00E8DDBA /* AppleIntelligenceEffectManager.swift */, - 52B053992CB2BF0D00E8DDBA /* AppleIntelligenceEffectController.swift */, - 52B0539A2CB2BF0D00E8DDBA /* AppleIntelligenceEffectView.swift */, - 52B053922CB2B0D300E8DDBA /* ColorWheel.metal */, + 526675422C85EDCB001EF113 /* ChatMLX.app */, ); - path = AppleIntelligenceEffect; + name = Products; sourceTree = ""; }; /* End PBXGroup section */ @@ -440,31 +85,15 @@ ); dependencies = ( ); + fileSystemSynchronizedGroups = ( + 52996ED72D6F7437003AD246 /* ChatMLX */, + ); name = ChatMLX; packageProductDependencies = ( - 526675612C85EFB3001EF113 /* Alamofire */, - 526675642C85EFC7001EF113 /* AlertToast */, - 526675672C85EFDF001EF113 /* CompactSlider */, - 5266756A2C85F0E8001EF113 /* Defaults */, - 5266756D2C85F0FF001EF113 /* Luminare */, - 526675732C85F1F9001EF113 /* Splash */, - 526675762C85F26B001EF113 /* Logging */, - 526675792C85F487001EF113 /* MarkdownUI */, - 5266757C2C85F54D001EF113 /* SwiftUIIntrospect */, - 52E50B1C2C8D6E81005A89DE /* LLM */, - 52E50B1F2C8D719B005A89DE /* LLM */, - 52E50B212C8D719B005A89DE /* MNIST */, - 52E50B282C8DF111005A89DE /* LLM */, - 52E50B2A2C8DF111005A89DE /* MNIST */, - 52FD80BD2C8F21C2006C50F1 /* LLM */, - 52FD80BF2C8F21C2006C50F1 /* MNIST */, - 52FD80C22C8F288A006C50F1 /* LLM */, - 52FD80C42C8F288A006C50F1 /* MNIST */, - 52FD80C72C8F5E42006C50F1 /* LLM */, - 52FD80C92C8F5E42006C50F1 /* MNIST */, - 523D8C502C9C7FCC0092791C /* Transformers */, - 528DBE2E2C9C86FB004CDD88 /* Transformers */, - 527F48142C9EFD5D006AF9FA /* LLM */, + 52996F0B2D6F7787003AD246 /* HuggingfaceHub */, + 52996F0E2D6F77A2003AD246 /* HuggingfaceHub */, + 523068222D71E6540069F093 /* Conversation */, + 523068972D71F0FC0069F093 /* Settings */, ); productName = ChatMLX; productReference = 526675422C85EDCB001EF113 /* ChatMLX.app */; @@ -499,16 +128,6 @@ ); mainGroup = 526675392C85EDCB001EF113; packageReferences = ( - 526675602C85EFB3001EF113 /* XCRemoteSwiftPackageReference "Alamofire" */, - 526675632C85EFC7001EF113 /* XCRemoteSwiftPackageReference "AlertToast" */, - 526675662C85EFDF001EF113 /* XCRemoteSwiftPackageReference "CompactSlider" */, - 526675692C85F0E8001EF113 /* XCRemoteSwiftPackageReference "Defaults" */, - 5266756C2C85F0FF001EF113 /* XCRemoteSwiftPackageReference "Luminare" */, - 526675722C85F1F9001EF113 /* XCRemoteSwiftPackageReference "splash" */, - 526675752C85F26B001EF113 /* XCRemoteSwiftPackageReference "swift-log" */, - 526675782C85F486001EF113 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */, - 5266757B2C85F54D001EF113 /* XCRemoteSwiftPackageReference "swiftui-introspect" */, - 527F48132C9EFD5D006AF9FA /* XCRemoteSwiftPackageReference "mlx-swift-examples" */, ); productRefGroup = 526675432C85EDCB001EF113 /* Products */; projectDirPath = ""; @@ -524,9 +143,6 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( - 5266754D2C85EDCC001EF113 /* Preview Assets.xcassets in Resources */, - 526676782C85F9DA001EF113 /* Localizable.xcstrings in Resources */, - 526676402C85F903001EF113 /* Assets.xcassets in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -537,75 +153,6 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( - 526676422C85F903001EF113 /* TextOutputFormat.swift in Sources */, - 526676512C85F903001EF113 /* RightSidebarView.swift in Sources */, - 52B053A62CB38E8700E8DDBA /* ExperimentalFeaturesView.swift in Sources */, - 526676682C85F903001EF113 /* SettingsTabGroup.swift in Sources */, - 526676452C85F903001EF113 /* UltramanNavigationSplitView.swift in Sources */, - 526676672C85F903001EF113 /* SettingsTab.swift in Sources */, - 52B053932CB2B0D500E8DDBA /* ColorWheel.metal in Sources */, - 5266765C2C85F903001EF113 /* SettingsSidebarItemView.swift in Sources */, - 526676592C85F903001EF113 /* DefaultConversationView.swift in Sources */, - 52B053A02CB2C2CD00E8DDBA /* AppleIntelligenceEffectManager.swift in Sources */, - 5266766E2C85F903001EF113 /* Logger.swift in Sources */, - 526676612C85F903001EF113 /* DownloadTask.swift in Sources */, - 52A689F62CAE8AAB0078CDF9 /* TimeInterval+Extensions.swift in Sources */, - 528D83192CAD491900163AAB /* ChatMLX.xcdatamodeld in Sources */, - 526676502C85F903001EF113 /* MessageBubbleView.swift in Sources */, - 526676582C85F903001EF113 /* AboutView.swift in Sources */, - 526676552C85F903001EF113 /* LocalModelsView.swift in Sources */, - 52A689FA2CAECFE00078CDF9 /* ErrorAlertModifier.swift in Sources */, - 526676662C85F903001EF113 /* RemoteModel.swift in Sources */, - 526676562C85F903001EF113 /* MLXCommunityItemView.swift in Sources */, - 526676442C85F903001EF113 /* UltramanMinimalistWindowModifier.swift in Sources */, - 5266764A2C85F903001EF113 /* UltramanWindow.swift in Sources */, - 526676702C85F903001EF113 /* Defaults+Extensions.swift in Sources */, - 526676462C85F903001EF113 /* UltramanSecureField.swift in Sources */, - 5266766C2C85F903001EF113 /* HubApi.swift in Sources */, - 526676472C85F903001EF113 /* UltramanSidebarButtonStyle.swift in Sources */, - 5266764F2C85F903001EF113 /* EmptyConversation.swift in Sources */, - 526676572C85F903001EF113 /* MLXCommunityView.swift in Sources */, - 528D82262CABE19900163AAB /* Date+Extensions.swift in Sources */, - 5266766D2C85F903001EF113 /* LLMRunner.swift in Sources */, - 5266764E2C85F903001EF113 /* ConversationView.swift in Sources */, - 5266766A2C85F903001EF113 /* Downloader.swift in Sources */, - 526676732C85F903001EF113 /* String+Extensions.swift in Sources */, - 526676742C85F903001EF113 /* View+Extensions.swift in Sources */, - 526676432C85F903001EF113 /* EffectView.swift in Sources */, - 528D83292CAD5C9100163AAB /* Conversation+CoreDataClass.swift in Sources */, - 528D832A2CAD5C9100163AAB /* Conversation+CoreDataProperties.swift in Sources */, - 528D832B2CAD5C9100163AAB /* Message+CoreDataClass.swift in Sources */, - 528D83392CAE51EC00163AAB /* Role.swift in Sources */, - 52B053952CB2B64500E8DDBA /* NoneInteractWindow.swift in Sources */, - 528D832C2CAD5C9100163AAB /* Message+CoreDataProperties.swift in Sources */, - 526676712C85F903001EF113 /* MarkdownUI+Theme+Extensions.swift in Sources */, - 526676532C85F903001EF113 /* DownloadTaskView.swift in Sources */, - 52A689F82CAE8DA30078CDF9 /* SettingsViewModel.swift in Sources */, - 5266764C2C85F903001EF113 /* ConversationSidebarItem.swift in Sources */, - 526676622C85F903001EF113 /* Language.swift in Sources */, - 526676722C85F903001EF113 /* NSWindow+Extensions.swift in Sources */, - 526676412C85F903001EF113 /* SplashCodeSyntaxHighlighter.swift in Sources */, - 526676542C85F903001EF113 /* LocalModelItemView.swift in Sources */, - 526676632C85F903001EF113 /* LocalModel.swift in Sources */, - 52B0539D2CB2BF0D00E8DDBA /* AppleIntelligenceEffectView.swift in Sources */, - 52B0539E2CB2BF0D00E8DDBA /* AppleIntelligenceEffectController.swift in Sources */, - 526676692C85F903001EF113 /* Styles.swift in Sources */, - 5266765A2C85F903001EF113 /* GeneralView.swift in Sources */, - 526675462C85EDCB001EF113 /* ChatMLXApp.swift in Sources */, - 526676492C85F903001EF113 /* UltramanTextField.swift in Sources */, - 5266765E2C85F903001EF113 /* SettingsView.swift in Sources */, - 528D831C2CAD49E600163AAB /* PersistenceController.swift in Sources */, - 5266766B2C85F903001EF113 /* Hub.swift in Sources */, - 5266764D2C85F903001EF113 /* ConversationSidebarView.swift in Sources */, - 528D83372CADB64600163AAB /* ConversationViewModel.swift in Sources */, - 5266765D2C85F903001EF113 /* SettingsSidebarView.swift in Sources */, - 5266764B2C85F903001EF113 /* ConversationDetailView.swift in Sources */, - 52B053A22CB2F6F900E8DDBA /* AppleIntelligenceEffectDisplay.swift in Sources */, - 526676602C85F903001EF113 /* DisplayStyle.swift in Sources */, - 5266765B2C85F903001EF113 /* HuggingFaceView.swift in Sources */, - 526676642C85F903001EF113 /* LocalModelGroup.swift in Sources */, - 526676522C85F903001EF113 /* DownloadManagerView.swift in Sources */, - 526676482C85F903001EF113 /* UltramanTextEditorWithPlaceholder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -740,6 +287,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = ChatMLX/ChatMLX.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 5; @@ -769,7 +317,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = ChatMLX/ChatMLXRelease.entitlements; + CODE_SIGN_ENTITLEMENTS = ChatMLX/ChatMLX.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 5; @@ -817,206 +366,24 @@ }; /* End XCConfigurationList section */ -/* Begin XCRemoteSwiftPackageReference section */ - 526675602C85EFB3001EF113 /* XCRemoteSwiftPackageReference "Alamofire" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/Alamofire/Alamofire.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 5.9.1; - }; - }; - 526675632C85EFC7001EF113 /* XCRemoteSwiftPackageReference "AlertToast" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/elai950/AlertToast.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.3.9; - }; - }; - 526675662C85EFDF001EF113 /* XCRemoteSwiftPackageReference "CompactSlider" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/buh/CompactSlider.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.1.6; - }; - }; - 526675692C85F0E8001EF113 /* XCRemoteSwiftPackageReference "Defaults" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/sindresorhus/Defaults.git"; - requirement = { - branch = main; - kind = branch; - }; - }; - 5266756C2C85F0FF001EF113 /* XCRemoteSwiftPackageReference "Luminare" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/MrKai77/Luminare.git"; - requirement = { - branch = main; - kind = branch; - }; - }; - 526675722C85F1F9001EF113 /* XCRemoteSwiftPackageReference "splash" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/johnsundell/splash.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 0.16.0; - }; - }; - 526675752C85F26B001EF113 /* XCRemoteSwiftPackageReference "swift-log" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/apple/swift-log"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.6.1; - }; - }; - 526675782C85F486001EF113 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/gonzalezreal/swift-markdown-ui.git"; - requirement = { - branch = main; - kind = branch; - }; - }; - 5266757B2C85F54D001EF113 /* XCRemoteSwiftPackageReference "swiftui-introspect" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/siteline/swiftui-introspect.git"; - requirement = { - kind = upToNextMajorVersion; - minimumVersion = 1.3.0; - }; - }; - 527F48132C9EFD5D006AF9FA /* XCRemoteSwiftPackageReference "mlx-swift-examples" */ = { - isa = XCRemoteSwiftPackageReference; - repositoryURL = "https://github.com/ml-explore/mlx-swift-examples/"; - requirement = { - branch = main; - kind = branch; - }; - }; -/* End XCRemoteSwiftPackageReference section */ - /* Begin XCSwiftPackageProductDependency section */ - 523D8C502C9C7FCC0092791C /* Transformers */ = { + 523068222D71E6540069F093 /* Conversation */ = { isa = XCSwiftPackageProductDependency; - productName = Transformers; + productName = Conversation; }; - 526675612C85EFB3001EF113 /* Alamofire */ = { + 523068972D71F0FC0069F093 /* Settings */ = { isa = XCSwiftPackageProductDependency; - package = 526675602C85EFB3001EF113 /* XCRemoteSwiftPackageReference "Alamofire" */; - productName = Alamofire; + productName = Settings; }; - 526675642C85EFC7001EF113 /* AlertToast */ = { + 52996F0B2D6F7787003AD246 /* HuggingfaceHub */ = { isa = XCSwiftPackageProductDependency; - package = 526675632C85EFC7001EF113 /* XCRemoteSwiftPackageReference "AlertToast" */; - productName = AlertToast; + productName = HuggingfaceHub; }; - 526675672C85EFDF001EF113 /* CompactSlider */ = { + 52996F0E2D6F77A2003AD246 /* HuggingfaceHub */ = { isa = XCSwiftPackageProductDependency; - package = 526675662C85EFDF001EF113 /* XCRemoteSwiftPackageReference "CompactSlider" */; - productName = CompactSlider; - }; - 5266756A2C85F0E8001EF113 /* Defaults */ = { - isa = XCSwiftPackageProductDependency; - package = 526675692C85F0E8001EF113 /* XCRemoteSwiftPackageReference "Defaults" */; - productName = Defaults; - }; - 5266756D2C85F0FF001EF113 /* Luminare */ = { - isa = XCSwiftPackageProductDependency; - package = 5266756C2C85F0FF001EF113 /* XCRemoteSwiftPackageReference "Luminare" */; - productName = Luminare; - }; - 526675732C85F1F9001EF113 /* Splash */ = { - isa = XCSwiftPackageProductDependency; - package = 526675722C85F1F9001EF113 /* XCRemoteSwiftPackageReference "splash" */; - productName = Splash; - }; - 526675762C85F26B001EF113 /* Logging */ = { - isa = XCSwiftPackageProductDependency; - package = 526675752C85F26B001EF113 /* XCRemoteSwiftPackageReference "swift-log" */; - productName = Logging; - }; - 526675792C85F487001EF113 /* MarkdownUI */ = { - isa = XCSwiftPackageProductDependency; - package = 526675782C85F486001EF113 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */; - productName = MarkdownUI; - }; - 5266757C2C85F54D001EF113 /* SwiftUIIntrospect */ = { - isa = XCSwiftPackageProductDependency; - package = 5266757B2C85F54D001EF113 /* XCRemoteSwiftPackageReference "swiftui-introspect" */; - productName = SwiftUIIntrospect; - }; - 527F48142C9EFD5D006AF9FA /* LLM */ = { - isa = XCSwiftPackageProductDependency; - package = 527F48132C9EFD5D006AF9FA /* XCRemoteSwiftPackageReference "mlx-swift-examples" */; - productName = LLM; - }; - 528DBE2E2C9C86FB004CDD88 /* Transformers */ = { - isa = XCSwiftPackageProductDependency; - productName = Transformers; - }; - 52E50B1C2C8D6E81005A89DE /* LLM */ = { - isa = XCSwiftPackageProductDependency; - productName = LLM; - }; - 52E50B1F2C8D719B005A89DE /* LLM */ = { - isa = XCSwiftPackageProductDependency; - productName = LLM; - }; - 52E50B212C8D719B005A89DE /* MNIST */ = { - isa = XCSwiftPackageProductDependency; - productName = MNIST; - }; - 52E50B282C8DF111005A89DE /* LLM */ = { - isa = XCSwiftPackageProductDependency; - productName = LLM; - }; - 52E50B2A2C8DF111005A89DE /* MNIST */ = { - isa = XCSwiftPackageProductDependency; - productName = MNIST; - }; - 52FD80BD2C8F21C2006C50F1 /* LLM */ = { - isa = XCSwiftPackageProductDependency; - productName = LLM; - }; - 52FD80BF2C8F21C2006C50F1 /* MNIST */ = { - isa = XCSwiftPackageProductDependency; - productName = MNIST; - }; - 52FD80C22C8F288A006C50F1 /* LLM */ = { - isa = XCSwiftPackageProductDependency; - productName = LLM; - }; - 52FD80C42C8F288A006C50F1 /* MNIST */ = { - isa = XCSwiftPackageProductDependency; - productName = MNIST; - }; - 52FD80C72C8F5E42006C50F1 /* LLM */ = { - isa = XCSwiftPackageProductDependency; - productName = LLM; - }; - 52FD80C92C8F5E42006C50F1 /* MNIST */ = { - isa = XCSwiftPackageProductDependency; - productName = MNIST; + productName = HuggingfaceHub; }; /* End XCSwiftPackageProductDependency section */ - -/* Begin XCVersionGroup section */ - 528D83172CAD491900163AAB /* ChatMLX.xcdatamodeld */ = { - isa = XCVersionGroup; - children = ( - 528D83182CAD491900163AAB /* ChatMLX.xcdatamodel */, - ); - currentVersion = 528D83182CAD491900163AAB /* ChatMLX.xcdatamodel */; - path = ChatMLX.xcdatamodeld; - sourceTree = ""; - versionGroupType = wrapper.xcdatamodel; - }; -/* End XCVersionGroup section */ }; rootObject = 5266753A2C85EDCB001EF113 /* Project object */; } diff --git a/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f70955a..ce61799 100644 --- a/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,157 +1,94 @@ { - "originHash" : "91755e46d4857336740696612733433e7fa7ef978bc35290de8f756037756422", + "originHash" : "cddab7a5a48825d0398510e03669e6f2406bf3dc2c0b1594e3e084d897c0120b", "pins" : [ { - "identity" : "alamofire", + "identity" : "anycodable", "kind" : "remoteSourceControl", - "location" : "https://github.com/Alamofire/Alamofire.git", + "location" : "https://github.com/Flight-School/AnyCodable", "state" : { - "revision" : "e16d3481f5ed35f0472cb93350085853d754913f", - "version" : "5.10.1" + "revision" : "862808b2070cd908cb04f9aafe7de83d35f81b05", + "version" : "0.6.7" } }, { - "identity" : "alerttoast", + "identity" : "coretextswift", "kind" : "remoteSourceControl", - "location" : "https://github.com/elai950/AlertToast.git", + "location" : "https://github.com/krzyzanowskim/CoreTextSwift", "state" : { - "revision" : "b39252eacd159904afd7d718bba0afabb87f2f2f", - "version" : "1.3.9" - } - }, - { - "identity" : "compactslider", - "kind" : "remoteSourceControl", - "location" : "https://github.com/buh/CompactSlider.git", - "state" : { - "revision" : "abe4d1df6f0c85dcb133266cc07c2a5d08295726", - "version" : "1.1.6" + "revision" : "833177201d6421e6322296f39fce6ff6ae52618a", + "version" : "0.2.0" } }, { "identity" : "defaults", "kind" : "remoteSourceControl", - "location" : "https://github.com/sindresorhus/Defaults.git", - "state" : { - "branch" : "main", - "revision" : "4c009d5c2496e7aa126e922c94dd3d6ae049efa2" - } - }, - { - "identity" : "gzipswift", - "kind" : "remoteSourceControl", - "location" : "https://github.com/1024jp/GzipSwift", - "state" : { - "revision" : "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", - "version" : "6.0.1" - } - }, - { - "identity" : "jinja", - "kind" : "remoteSourceControl", - "location" : "https://github.com/maiqingqiang/Jinja", + "location" : "https://github.com/sindresorhus/Defaults", "state" : { - "revision" : "6dbe4c449469fb586d0f7339f900f0dd4d78b167", - "version" : "1.0.6" + "revision" : "cc938ecf0bed848dc80a9726ac5455104e3f9dae", + "version" : "9.0.2" } }, { - "identity" : "luminare", + "identity" : "semaphore", "kind" : "remoteSourceControl", - "location" : "https://github.com/MrKai77/Luminare.git", + "location" : "https://github.com/groue/Semaphore", "state" : { - "branch" : "main", - "revision" : "a9c1d600e972c5523c27eb7cd84637f4f05beaaa" + "revision" : "2543679282aa6f6c8ecf2138acd613ed20790bc2", + "version" : "0.1.0" } }, { - "identity" : "mlx-swift", + "identity" : "sttextkitplus", "kind" : "remoteSourceControl", - "location" : "https://github.com/ml-explore/mlx-swift", + "location" : "https://github.com/krzyzanowskim/STTextKitPlus", "state" : { - "revision" : "d649c62b77c487c25012910b0d02b30283d388ca", - "version" : "0.18.1" + "revision" : "446d6d431b163840ac5cc844ca5cf4704886c30e", + "version" : "0.1.8" } }, { - "identity" : "mlx-swift-examples", + "identity" : "sttextview", "kind" : "remoteSourceControl", - "location" : "https://github.com/ml-explore/mlx-swift-examples/", + "location" : "https://github.com/krzyzanowskim/STTextView", "state" : { - "branch" : "main", - "revision" : "a719a6d05f19e5c25be201842a5c3d471cbd0c38" - } - }, - { - "identity" : "networkimage", - "kind" : "remoteSourceControl", - "location" : "https://github.com/gonzalezreal/NetworkImage", - "state" : { - "revision" : "2849f5323265386e200484b0d0f896e73c3411b9", - "version" : "6.0.1" - } - }, - { - "identity" : "splash", - "kind" : "remoteSourceControl", - "location" : "https://github.com/johnsundell/splash.git", - "state" : { - "revision" : "7f4df436eb78fe64fe2c32c58006e9949fa28ad8", - "version" : "0.16.0" + "revision" : "d30b176bced4ae934181e811b1b6ef4a4cd53d30", + "version" : "2.0.0" } }, { "identity" : "swift-argument-parser", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-argument-parser.git", + "location" : "https://github.com/apple/swift-argument-parser", "state" : { "revision" : "41982a3656a71c768319979febd796c6fd111d5c", "version" : "1.5.0" } }, { - "identity" : "swift-log", + "identity" : "swift-syntax", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-log", + "location" : "https://github.com/swiftlang/swift-syntax", "state" : { - "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", - "version" : "1.6.1" + "revision" : "0687f71944021d616d34d922343dcef086855920", + "version" : "600.0.1" } }, { - "identity" : "swift-markdown-ui", - "kind" : "remoteSourceControl", - "location" : "https://github.com/gonzalezreal/swift-markdown-ui.git", - "state" : { - "branch" : "main", - "revision" : "55441810c0f678c78ed7e2ebd46dde89228e02fc" - } - }, - { - "identity" : "swift-numerics", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-numerics", - "state" : { - "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", - "version" : "1.0.2" - } - }, - { - "identity" : "swift-transformers", + "identity" : "swiftui-introspect", "kind" : "remoteSourceControl", - "location" : "https://github.com/huggingface/swift-transformers", + "location" : "https://github.com/siteline/swiftui-introspect.git", "state" : { - "revision" : "d42fdae473c49ea216671da8caae58e102d28709", - "version" : "0.1.14" + "revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336", + "version" : "1.3.0" } }, { - "identity" : "swiftui-introspect", + "identity" : "swiftui-shimmer", "kind" : "remoteSourceControl", - "location" : "https://github.com/siteline/swiftui-introspect.git", + "location" : "https://github.com/markiv/SwiftUI-Shimmer.git", "state" : { - "revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336", - "version" : "1.3.0" + "revision" : "0226e21f9bf355d40e07e5f5e1c33679d50e167f", + "version" : "1.5.1" } } ], diff --git a/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist b/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist index e2cd285..9046445 100644 --- a/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,10 +4,15 @@ SchemeUserState + AnyCodable (Playground).xcscheme + + orderHint + 9 + ChatMLX.xcscheme_^#shared#^_ orderHint - 3 + 0 SuppressBuildableAutocreation diff --git a/ChatMLX/Assets.xcassets/AccentColor.colorset/Contents.json b/ChatMLX/Assets.xcassets/AccentColor.colorset/Contents.json index 22c4bb0..eb87897 100644 --- a/ChatMLX/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/ChatMLX/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,33 +1,6 @@ { "colors" : [ { - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "1.000", - "green" : "1.000", - "red" : "1.000" - } - }, - "idiom" : "universal" - }, - { - "appearances" : [ - { - "appearance" : "luminosity", - "value" : "dark" - } - ], - "color" : { - "color-space" : "srgb", - "components" : { - "alpha" : "1.000", - "blue" : "1.000", - "green" : "1.000", - "red" : "1.000" - } - }, "idiom" : "universal" } ], diff --git a/ChatMLX/Assets.xcassets/clear.imageset/Contents.json b/ChatMLX/Assets.xcassets/MCP.imageset/Contents.json similarity index 88% rename from ChatMLX/Assets.xcassets/clear.imageset/Contents.json rename to ChatMLX/Assets.xcassets/MCP.imageset/Contents.json index 98f7f1b..2ed1d02 100644 --- a/ChatMLX/Assets.xcassets/clear.imageset/Contents.json +++ b/ChatMLX/Assets.xcassets/MCP.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "clear-l.svg", + "filename" : "MCP.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/ChatMLX/Assets.xcassets/MCP.imageset/MCP.png b/ChatMLX/Assets.xcassets/MCP.imageset/MCP.png new file mode 100644 index 0000000000000000000000000000000000000000..3b44a2ba0bc7888f7920dafc5b346ed7ff4d6b90 GIT binary patch literal 21265 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 \ No newline at end of file diff --git a/ChatMLX/Assets.xcassets/huggingface.imageset/Contents.json b/ChatMLX/Assets.xcassets/huggingface.imageset/Contents.json index a4304c6..f801a75 100644 --- a/ChatMLX/Assets.xcassets/huggingface.imageset/Contents.json +++ b/ChatMLX/Assets.xcassets/huggingface.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "hf-logo-pirate.svg", + "filename" : "huggingface.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/ChatMLX/Assets.xcassets/huggingface.imageset/hf-logo-pirate.svg b/ChatMLX/Assets.xcassets/huggingface.imageset/hf-logo-pirate.svg deleted file mode 100644 index ce69d68..0000000 --- a/ChatMLX/Assets.xcassets/huggingface.imageset/hf-logo-pirate.svg +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - - - - - - - - - - diff --git a/ChatMLX/Assets.xcassets/huggingface.imageset/huggingface.png b/ChatMLX/Assets.xcassets/huggingface.imageset/huggingface.png new file mode 100644 index 0000000000000000000000000000000000000000..85aa4f2ead991b494829364d83d897e135cae25e GIT binary patch literal 23251 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 \ No newline at end of file diff --git a/ChatMLX/Assets.xcassets/plaintext.imageset/Contents.json b/ChatMLX/Assets.xcassets/menubarIcon.imageset/Contents.json similarity index 68% rename from ChatMLX/Assets.xcassets/plaintext.imageset/Contents.json rename to ChatMLX/Assets.xcassets/menubarIcon.imageset/Contents.json index 416e7cc..38d5dd1 100644 --- a/ChatMLX/Assets.xcassets/plaintext.imageset/Contents.json +++ b/ChatMLX/Assets.xcassets/menubarIcon.imageset/Contents.json @@ -1,15 +1,17 @@ { "images" : [ { - "filename" : "doc-plaintext (1).svg", + "filename" : "menubarIcon@1x.png", "idiom" : "universal", "scale" : "1x" }, { + "filename" : "menubarIcon@2x.png", "idiom" : "universal", "scale" : "2x" }, { + "filename" : "menubarIcon@3x.png", "idiom" : "universal", "scale" : "3x" } diff --git a/ChatMLX/Assets.xcassets/menubarIcon.imageset/menubarIcon@1x.png b/ChatMLX/Assets.xcassets/menubarIcon.imageset/menubarIcon@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..7a7fbe88749d78707720bd310adce6fa5c4a18b9 GIT binary patch literal 3433 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 LNkvXXu0mjf5rTXX literal 0 HcmV?d00001 diff --git a/ChatMLX/Assets.xcassets/menubarIcon.imageset/menubarIcon@2x.png b/ChatMLX/Assets.xcassets/menubarIcon.imageset/menubarIcon@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..8fe13c4ba2ab6244edd05824370bab55b3f10a78 GIT binary patch literal 4168 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 Z^M6BnkV&-w_7DI7002ovPDHLkV1g~09ZmoM literal 0 HcmV?d00001 diff --git a/ChatMLX/Assets.xcassets/plaintext.imageset/doc-plaintext (1).svg b/ChatMLX/Assets.xcassets/plaintext.imageset/doc-plaintext (1).svg deleted file mode 100644 index 8354b7f..0000000 --- a/ChatMLX/Assets.xcassets/plaintext.imageset/doc-plaintext (1).svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/ChatMLX/ChatMLX.entitlements b/ChatMLX/ChatMLX.entitlements index 7c7a703..81f6dc3 100644 --- a/ChatMLX/ChatMLX.entitlements +++ b/ChatMLX/ChatMLX.entitlements @@ -5,7 +5,7 @@ com.apple.security.network.client com.apple.security.app-sandbox - + com.apple.security.files.user-selected.read-only diff --git a/ChatMLX/ChatMLXApp.swift b/ChatMLX/ChatMLXApp.swift index 7148dc5..ab1d486 100644 --- a/ChatMLX/ChatMLXApp.swift +++ b/ChatMLX/ChatMLXApp.swift @@ -5,67 +5,29 @@ // Created by John Mai on 2024/8/3. // +import Conversation import Defaults +import Settings import SwiftUI @main struct ChatMLXApp: App { - @Environment(\.scenePhase) private var scenePhase - - @State private var conversationViewModel: ConversationViewModel = .init() - @State private var settingsViewModel: SettingsViewModel = .init() + @State private var conversationStore: ConversationStore = .init() @Default(.language) var language - @State private var runner = LLMRunner() - - let persistenceController = PersistenceController.shared - var body: some Scene { - WindowGroup { - ConversationView() - .environment(conversationViewModel) - .environment( - \.locale, .init(identifier: language.rawValue) - ) - .environment(runner) - .frame(minWidth: 900, minHeight: 580) - .errorAlert( - isPresented: $conversationViewModel.showErrorAlert, - title: $settingsViewModel.errorTitle, - error: $conversationViewModel.error - ) - } - .environment(\.managedObjectContext, persistenceController.container.viewContext) - .onChange(of: scenePhase) { _, newValue in - if newValue == .background { - let context = persistenceController.container.viewContext - if context.hasChanges { - do { - try context.save() - } catch { - logger.error( - "scenePhase.background save error: \(error.localizedDescription)") - } - } + Group { + WindowGroup { + ConversationView() + .environment(conversationStore) } - } - Settings { - SettingsView() - .environment(conversationViewModel) - .environment(settingsViewModel) - .environment( - \.locale, .init(identifier: language.rawValue) - ) - .environment(runner) - .frame(width: 620, height: 480) - .errorAlert( - isPresented: $settingsViewModel.showErrorAlert, - title: $settingsViewModel.errorTitle, - error: $settingsViewModel.error - ) + Settings { + SettingsView() + + } } - .environment(\.managedObjectContext, persistenceController.container.viewContext) + .environment(\.locale, .init(identifier: language.rawValue)) } } diff --git a/ChatMLX/ChatMLXRelease.entitlements b/ChatMLX/ChatMLXRelease.entitlements deleted file mode 100644 index ffc8bb3..0000000 --- a/ChatMLX/ChatMLXRelease.entitlements +++ /dev/null @@ -1,14 +0,0 @@ - - - - - com.apple.security.files.user-selected.read-only - - com.apple.security.app-sandbox - - com.apple.security.network.client - - com.apple.security.network.server - - - diff --git a/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectController.swift b/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectController.swift deleted file mode 100644 index 009773d..0000000 --- a/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectController.swift +++ /dev/null @@ -1,57 +0,0 @@ -// -// FireworkController.swift -// Firework -// -// Created by 秋星桥 on 2024/2/7. -// - -import AppKit -import SwiftUI - -class AppleIntelligenceEffectController: NSWindowController { - override init(window: NSWindow?) { - super.init(window: window) - contentViewController = AppleIntelligenceEffectViewController() - } - - @available(*, unavailable) - required init?(coder _: NSCoder) { fatalError() } - - convenience init(screen: NSScreen) { - let window = NoneInteractWindow( - contentRect: screen.frame, - styleMask: [.borderless, .fullSizeContentView], - backing: .buffered, - defer: false, - screen: screen - ) - self.init(window: window) - } -} - -extension AppleIntelligenceEffectController { - func configureWindow(for screen: NSScreen) { - window?.setFrameOrigin(screen.frame.origin) - window?.setContentSize(screen.frame.size) - window?.orderFrontRegardless() - } -} - -class AppleIntelligenceEffectViewController: NSViewController { - override func loadView() { - view = NSHostingView(rootView: AppleIntelligenceEffectView()) - } - - func fadeOut(completion: (() -> Void)?) { - let fadeOutAnimation = CABasicAnimation(keyPath: "opacity") - fadeOutAnimation.fromValue = 1.0 - fadeOutAnimation.toValue = 0.0 - fadeOutAnimation.duration = 1.0 - fadeOutAnimation.isRemovedOnCompletion = true - view.layer?.add(fadeOutAnimation, forKey: "opacity") - view.layer?.opacity = 0.0 - DispatchQueue.main.asyncAfter(deadline: .now() + 1.0) { - completion?() - } - } -} diff --git a/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectManager.swift b/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectManager.swift deleted file mode 100644 index 50d2920..0000000 --- a/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectManager.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// AppleIntelligenceEffectManager.swift -// ChatMLX -// -// Created by John Mai on 2024/10/6. -// - -import AppKit - -class AppleIntelligenceEffectManager { - static let shared = AppleIntelligenceEffectManager() - - private var effectController: AppleIntelligenceEffectController? - - private init() {} - - func setupEffect() { - guard effectController == nil, let screen = NSScreen.main else { return } - effectController = AppleIntelligenceEffectController(screen: screen) - effectController?.configureWindow(for: screen) - } - - func closeEffect(completion: (() -> Void)? = nil) { - guard let controller = effectController else { - completion?() - return - } - - (controller.contentViewController as? AppleIntelligenceEffectViewController)?.fadeOut { - [weak self] in - controller.window?.close() - self?.effectController = nil - completion?() - } - } -} diff --git a/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectView.swift b/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectView.swift deleted file mode 100644 index 5438658..0000000 --- a/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectView.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// AppleIntelligenceEffectView.swift -// test -// -// Created by John Mai on 2024/10/6. -// - -import SwiftUI - -struct AppleIntelligenceEffectView: View { - private let shader = ShaderLibrary.colorWheel(.boundingRect, .float(1)) - private let angles = [133.0, -133.0] - private let maxBlurRadiusBase: CGFloat = 18 - private let minBlurRadiusBase: CGFloat = 6 - - var useRoundedRectangle: Bool = true - - var body: some View { - TimelineView(.animation) { timeline in - ZStack { - ForEach(angles.indices, id: \.self) { index in - colorWheelRectangle(for: timeline.date, angle: angles[index]) - } - } - } - } - - @MainActor - private func colorWheelRectangle(for date: Date, angle: Double) -> some View { - let time = date.timeIntervalSince1970 - let blurRadius = - angle > 0 - ? maxBlurRadiusBase + 6 * sin(time * 2) - : minBlurRadiusBase + 3 * sin(time * 4) - - return Rectangle() - .fill(shader) - .rotationEffect(.degrees(time * 60)) - .scaleEffect(2.4) - .rotationEffect(.degrees(time * angle)) - .mask(alignment: .center) { - if useRoundedRectangle { - UnevenRoundedRectangle( - cornerRadii: .init( - topLeading: 20, - bottomLeading: 0, - bottomTrailing: 0, - topTrailing: 20 - ) - ) - .stroke(lineWidth: maxBlurRadiusBase) - .blur(radius: blurRadius) - } else { - Rectangle() - .stroke(lineWidth: maxBlurRadiusBase) - .blur(radius: blurRadius) - } - } - } -} diff --git a/ChatMLX/Components/AppleIntelligenceEffect/ColorWheel.metal b/ChatMLX/Components/AppleIntelligenceEffect/ColorWheel.metal deleted file mode 100644 index 4e6afca..0000000 --- a/ChatMLX/Components/AppleIntelligenceEffect/ColorWheel.metal +++ /dev/null @@ -1,25 +0,0 @@ -// -// ColorWheel.metal -// ChatMLX -// -// Created by John Mai on 2024/10/6. -// - -#include -using namespace metal; - -#define M_TWO_PI_F (M_PI_F * 2) - -float3 hsv2rgb(float3 c) { - float4 K = float4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - float3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, saturate(p - K.xxx), c.y); -} - -[[ stitchable ]] half4 colorWheel(float2 position, float4 bounds, float brightness) { - float2 center = position / bounds.zw - 0.5; - float hue = (atan2(center.y, center.x) + M_PI_F) / M_TWO_PI_F; - float saturation = min(length(center) * 2.0, 1.0); - float value = saturate(brightness); - return half4(half3(hsv2rgb(float3(hue, saturation, value))), 1.0); -} diff --git a/ChatMLX/Components/EffectView.swift b/ChatMLX/Components/EffectView.swift deleted file mode 100644 index 79347a9..0000000 --- a/ChatMLX/Components/EffectView.swift +++ /dev/null @@ -1,73 +0,0 @@ -// -// EffectView.swift -// ChatMLX -// -// Created by John Mai on 2024/3/10. -// - -import SwiftUI - -/// A SwiftUI Wrapper for `NSVisualEffectView` -/// -/// ## Usage -/// ```swift -/// EffectView(material: .headerView, blendingMode: .withinWindow) -/// ``` -struct EffectView: NSViewRepresentable { - private let material: NSVisualEffectView.Material - private let blendingMode: NSVisualEffectView.BlendingMode - private let emphasized: Bool - - /// Initializes the - /// [`NSVisualEffectView`](https://developer.apple.com/documentation/appkit/nsvisualeffectview) - /// with a - /// [`Material`](https://developer.apple.com/documentation/appkit/nsvisualeffectview/material) and - /// [`BlendingMode`](https://developer.apple.com/documentation/appkit/nsvisualeffectview/blendingmode) - /// - /// By setting the - /// [`emphasized`](https://developer.apple.com/documentation/appkit/nsvisualeffectview/1644721-isemphasized) - /// flag the emphasized state of the material will be used if available. - /// - /// - Parameters: - /// - material: The material to use. Defaults to `.headerView`. - /// - blendingMode: The blending mode to use. Defaults to `.withinWindow`. - /// - emphasized:A Boolean value indicating whether to emphasize the look of the material. Defaults to `false`. - init( - _ material: NSVisualEffectView.Material = .headerView, - blendingMode: NSVisualEffectView.BlendingMode = .withinWindow, - emphasized: Bool = false - ) { - self.material = material - self.blendingMode = blendingMode - self.emphasized = emphasized - - } - - func makeNSView(context: Context) -> NSVisualEffectView { - let view = NSVisualEffectView() - view.material = material - view.blendingMode = blendingMode - view.isEmphasized = emphasized - view.state = .active - return view - } - - func updateNSView(_ nsView: NSVisualEffectView, context: Context) { - nsView.material = material - nsView.blendingMode = blendingMode - } - - /// Returns the system selection style as an ``EffectView`` if the `condition` is met. - /// Otherwise it returns `Color.clear` - /// - /// - Parameter condition: The condition of when to apply the background. Defaults to `true`. - /// - Returns: A View - @ViewBuilder - static func selectionBackground(_ condition: Bool = true) -> some View { - if condition { - EffectView(.selection, blendingMode: .withinWindow, emphasized: true) - } else { - Color.clear - } - } -} diff --git a/ChatMLX/Components/ErrorAlertModifier.swift b/ChatMLX/Components/ErrorAlertModifier.swift deleted file mode 100644 index 8285a22..0000000 --- a/ChatMLX/Components/ErrorAlertModifier.swift +++ /dev/null @@ -1,42 +0,0 @@ -// -// ErrorAlertModifier.swift -// ChatMLX -// -// Created by John Mai on 2024/10/3. -// - -import SwiftUI - -struct ErrorAlertModifier: ViewModifier { - @Binding var showErrorAlert: Bool - @Binding var errorTitle: String? - @Binding var error: Error? - - func body(content: Content) -> some View { - content - .alert( - errorTitle ?? "Error", isPresented: $showErrorAlert, - actions: { - Button("OK") { - error = nil - } - - Button("Feedback") { - error = nil - NSWorkspace.shared.open( - URL(string: "https://github.com/maiqingqiang/ChatMLX/issues")!) - } - }, - message: { - Text(error?.localizedDescription ?? "An unknown error occurred.") - }) - } -} - -extension View { - func errorAlert(isPresented: Binding, title: Binding, error: Binding) - -> some View - { - modifier(ErrorAlertModifier(showErrorAlert: isPresented, errorTitle: title, error: error)) - } -} diff --git a/ChatMLX/Components/NoneInteractWindow.swift b/ChatMLX/Components/NoneInteractWindow.swift deleted file mode 100644 index f67541b..0000000 --- a/ChatMLX/Components/NoneInteractWindow.swift +++ /dev/null @@ -1,48 +0,0 @@ -// -// NoneInteractWindow.swift -// FireBox -// -// Created by 秋星桥 on 2024/2/9. -// - -import AppKit - -class NoneInteractWindow: NSWindow { - override init( - contentRect: NSRect, - styleMask: NSWindow.StyleMask, - backing: NSWindow.BackingStoreType, - defer flag: Bool - ) { - super.init( - contentRect: contentRect, - styleMask: styleMask, - backing: backing, - defer: flag - ) - - isOpaque = false - alphaValue = 1 - titleVisibility = .hidden - titlebarAppearsTransparent = true - backgroundColor = NSColor.clear - ignoresMouseEvents = true - isMovable = false - collectionBehavior = [ - .fullScreenAuxiliary, - .stationary, - .canJoinAllSpaces, - .ignoresCycle, - ] - level = .statusBar - hasShadow = false - } - - override var canBecomeKey: Bool { - false - } - - override var canBecomeMain: Bool { - false - } -} diff --git a/ChatMLX/Components/SyntaxHighlighter/SplashCodeSyntaxHighlighter.swift b/ChatMLX/Components/SyntaxHighlighter/SplashCodeSyntaxHighlighter.swift deleted file mode 100644 index b0c1ad5..0000000 --- a/ChatMLX/Components/SyntaxHighlighter/SplashCodeSyntaxHighlighter.swift +++ /dev/null @@ -1,33 +0,0 @@ -// -// SplashCodeSyntaxHighlighter.swift -// ChatMLX -// -// Created by John Mai on 2024/3/10. -// - -import MarkdownUI -import Splash -import SwiftUI - -struct SplashCodeSyntaxHighlighter: CodeSyntaxHighlighter { - private let syntaxHighlighter: SyntaxHighlighter - - init(theme: Splash.Theme) { - self.syntaxHighlighter = SyntaxHighlighter( - format: TextOutputFormat(theme: theme)) - } - - func highlightCode(_ content: String, language: String?) -> Text { - guard language != nil else { - return Text(content) - } - - return self.syntaxHighlighter.highlight(content) - } -} - -extension CodeSyntaxHighlighter where Self == SplashCodeSyntaxHighlighter { - static func splash(theme: Splash.Theme) -> Self { - SplashCodeSyntaxHighlighter(theme: theme) - } -} diff --git a/ChatMLX/Components/SyntaxHighlighter/TextOutputFormat.swift b/ChatMLX/Components/SyntaxHighlighter/TextOutputFormat.swift deleted file mode 100644 index c003075..0000000 --- a/ChatMLX/Components/SyntaxHighlighter/TextOutputFormat.swift +++ /dev/null @@ -1,53 +0,0 @@ -// -// TextOutputFormat.swift -// ChatMLX -// -// Created by John Mai on 2024/3/10. -// - -import Splash -import SwiftUI - -struct TextOutputFormat: OutputFormat { - private let theme: Theme - - init(theme: Theme) { - self.theme = theme - } - - func makeBuilder() -> Builder { - Builder(theme: self.theme) - } -} - -extension TextOutputFormat { - struct Builder: OutputBuilder { - private let theme: Theme - private var accumulatedText: [Text] - - fileprivate init(theme: Theme) { - self.theme = theme - self.accumulatedText = [] - } - - mutating func addToken(_ token: String, ofType type: TokenType) { - let color = self.theme.tokenColors[type] ?? .white - self.accumulatedText.append( - Text(token).foregroundColor(.init(color))) - } - - mutating func addPlainText(_ text: String) { - self.accumulatedText.append( - Text(text).foregroundColor(.init(.white)) - ) - } - - mutating func addWhitespace(_ whitespace: String) { - self.accumulatedText.append(Text(whitespace)) - } - - func build() -> Text { - self.accumulatedText.reduce(Text(""), +) - } - } -} diff --git a/ChatMLX/Components/UltramanMinimalistWindowModifier.swift b/ChatMLX/Components/UltramanMinimalistWindowModifier.swift deleted file mode 100644 index a31cadc..0000000 --- a/ChatMLX/Components/UltramanMinimalistWindowModifier.swift +++ /dev/null @@ -1,69 +0,0 @@ -// -// UltramanMinimalistWindowModifier.swift -// ChatMLX -// -// Created by John Mai on 2024/8/10. -// - -import AppKit -import Defaults -import SwiftUI -import SwiftUIIntrospect - -struct UltramanMinimalistWindowModifier: ViewModifier { - @Default(.backgroundBlurRadius) var blurRadius - @Default(.backgroundColor) var backgroundColor - @State private var isFullScreen = false - - func body(content: Content) -> some View { - content - .ignoresSafeArea() - .introspect(.window, on: .macOS(.v14, .v15)) { window in - window.setBackgroundBlur( - radius: Int(blurRadius), color: NSColor(backgroundColor)) - window.toolbarStyle = .unified - window.titlebarAppearsTransparent = true - window.titleVisibility = .hidden - - let toolbar = NSToolbar() - toolbar.showsBaselineSeparator = false - window.toolbar = toolbar - - NotificationCenter.default.addObserver( - forName: NSWindow.didEnterFullScreenNotification, - object: window, queue: .main - ) { _ in - self.isFullScreen = true - self.updateFullScreenSettings(for: window) - } - - NotificationCenter.default.addObserver( - forName: NSWindow.didExitFullScreenNotification, - object: window, queue: .main - ) { _ in - self.isFullScreen = false - self.updateFullScreenSettings(for: window) - } - } - } - - private func updateFullScreenSettings(for window: NSWindow) { - if isFullScreen { - window.collectionBehavior.insert(.fullScreenPrimary) - window.toolbar?.isVisible = false - NSApp.presentationOptions = [ - .autoHideToolbar, .autoHideMenuBar, .fullScreen, - ] - } else { - window.collectionBehavior.remove(.fullScreenPrimary) - window.toolbar?.isVisible = true - NSApp.presentationOptions = [] - } - } -} - -extension View { - func ultramanMinimalistWindowStyle() -> some View { - modifier(UltramanMinimalistWindowModifier()) - } -} diff --git a/ChatMLX/Components/UltramanNavigationSplitView.swift b/ChatMLX/Components/UltramanNavigationSplitView.swift deleted file mode 100644 index cf420a9..0000000 --- a/ChatMLX/Components/UltramanNavigationSplitView.swift +++ /dev/null @@ -1,188 +0,0 @@ -// -// UltramanNavigationSplitView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/3. -// - -import SwiftUI - -struct UltramanNavigationTitleKey: PreferenceKey { - static var defaultValue: LocalizedStringKey = "" - - static func reduce( - value: inout LocalizedStringKey, nextValue: () -> LocalizedStringKey - ) { - value = nextValue() - } -} - -struct UltramanToolbarItem: Identifiable, Equatable { - static func == (lhs: UltramanToolbarItem, rhs: UltramanToolbarItem) -> Bool { - lhs.id == rhs.id && lhs.alignment == rhs.alignment - } - - let id = UUID() - let content: AnyView - let alignment: ToolbarAlignment - - enum ToolbarAlignment { - case leading, trailing - } - - init(alignment: ToolbarAlignment = .trailing, @ViewBuilder content: () -> some View) { - self.content = AnyView(content()) - self.alignment = alignment - } -} - -struct UltramanNavigationToolbarKey: PreferenceKey { - static var defaultValue: [UltramanToolbarItem] = [] - - static func reduce( - value: inout [UltramanToolbarItem], - nextValue: () -> [UltramanToolbarItem] - ) { - let newItems = nextValue() - if !newItems.isEmpty { - value.append(contentsOf: newItems) - } - } -} - -@resultBuilder -struct UltramanToolbarBuilder { - static func buildBlock(_ components: UltramanToolbarItem...) -> [UltramanToolbarItem] { - components - } -} - -extension View { - func ultramanNavigationTitle(_ title: LocalizedStringKey) -> some View { - preference(key: UltramanNavigationTitleKey.self, value: title) - } - - func ultramanToolbar( - alignment: UltramanToolbarItem.ToolbarAlignment = .trailing, - @ViewBuilder content: () -> some View - ) -> some View { - preference( - key: UltramanNavigationToolbarKey.self, - value: [ - UltramanToolbarItem( - alignment: alignment, - content: { - content() - }) - ] - ) - } - - func ultramanToolbar( - @UltramanToolbarBuilder content: () -> [UltramanToolbarItem] - ) -> some View { - preference( - key: UltramanNavigationToolbarKey.self, - value: content() - ) - } -} - -struct UltramanNavigationSplitView: View { - @State var sidebarWidth: CGFloat = 250 - @State private var lastNonZeroWidth: CGFloat = 0 - let sidebar: () -> Sidebar - let detail: () -> Detail - - @State private var navigationTitle: LocalizedStringKey = "" - @State private var toolbarItems: [UltramanToolbarItem] = [] - - @State private var isDragging = false - @State private var isSidebarVisible = true - - let minSidebarWidth: CGFloat = 200 - let maxSidebarWidth: CGFloat = 400 - - var body: some View { - GeometryReader { _ in - HStack(spacing: .zero) { - if isSidebarVisible { - sidebar() - .frame(width: sidebarWidth) - .transition(.move(edge: .leading)) - } - - VStack(spacing: .zero) { - Divider() - detail() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .onPreferenceChange(UltramanNavigationTitleKey.self) { - navigationTitle = $0 - } - .onPreferenceChange(UltramanNavigationToolbarKey.self) { - toolbarItems = $0 - } - } - - .safeAreaInset(edge: .top, alignment: .center, spacing: 0) { - header().frame(height: 52) - } - } - } - } - - @MainActor - @ViewBuilder - func header() -> some View { - VStack(spacing: 0) { - Spacer() - - HStack { - if !isSidebarVisible { - Spacer() - .frame(width: 80) - } - - Button { - toggleSidebar() - } label: { - Image(systemName: "sidebar.leading") - } - .buttonStyle(.plain) - - ForEach(toolbarItems.filter { $0.alignment == .leading }) { - item in - item.content - } - - Spacer() - Text(navigationTitle) - .font(.headline) - - Spacer() - - ForEach(toolbarItems.filter { $0.alignment == .trailing }) { - item in - item.content - } - } - .padding(.horizontal, 10) - .padding(.trailing, 5) - Spacer() - } - .frame(height: 50) - .foregroundColor(.white) - } - - func toggleSidebar() { - withAnimation { - if isSidebarVisible { - lastNonZeroWidth = sidebarWidth - sidebarWidth = 0 - } else { - sidebarWidth = lastNonZeroWidth - } - isSidebarVisible.toggle() - } - } -} diff --git a/ChatMLX/Components/UltramanSecureField.swift b/ChatMLX/Components/UltramanSecureField.swift deleted file mode 100644 index f1a54a9..0000000 --- a/ChatMLX/Components/UltramanSecureField.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// UltramanTextField.swift -// ChatMLX -// -// Created by John Mai on 2024/8/18. -// - -import SwiftUI - -public struct UltramanSecureField: View { - let elementMinHeight: CGFloat = 34 - let horizontalPadding: CGFloat = 8 - - @Binding var text: String - let title: LocalizedStringKey - let placeholder: Text? - let onSubmit: (() -> Void)? - var alignment: Alignment = .leading - - @State var monitor: Any? - @State private var isFocused: Bool = false - - public init( - _ text: Binding, - title: LocalizedStringKey = "", - placeholder: Text?, - onSubmit: (() -> Void)? = nil, - alignment: Alignment = .leading - ) { - self._text = text - self.title = title - self.placeholder = placeholder - self.onSubmit = onSubmit - self.alignment = alignment - } - - public var body: some View { - ZStack(alignment: alignment) { - if !isFocused, text.isEmpty, let placeholder { - placeholder - .foregroundColor(.white.opacity(0.7)) - .padding(alignment == .leading ? .leading : .trailing, 10) - } - - SecureField(title, text: $text) - .padding(.horizontal, horizontalPadding) - .frame(minHeight: elementMinHeight) - .multilineTextAlignment( - alignment == .leading ? .leading : .trailing - ) - .textFieldStyle(.plain) - .onSubmit { - if let onSubmit { - onSubmit() - } - } - .onAppear { - guard monitor != nil else { return } - - monitor = NSEvent.addLocalMonitorForEvents( - matching: .keyDown - ) { - event in - if let window = NSApp.keyWindow, - window.animationBehavior == .documentWindow - { - window.keyDown(with: event) - - // Fixes cmd+w to close window. - let wKey = 13 - if event.keyCode == wKey, - event.modifierFlags.contains(.command) - { - return nil - } - } - return event - } - } - .onDisappear { - if let monitor { - NSEvent.removeMonitor(monitor) - } - monitor = nil - } - .onReceive( - NotificationCenter.default.publisher( - for: NSControl.textDidBeginEditingNotification) - ) { _ in - isFocused = true - } - .onReceive( - NotificationCenter.default.publisher( - for: NSControl.textDidEndEditingNotification) - ) { _ in - isFocused = false - } - } - } -} diff --git a/ChatMLX/Components/UltramanSidebarButtonStyle.swift b/ChatMLX/Components/UltramanSidebarButtonStyle.swift deleted file mode 100644 index 9505865..0000000 --- a/ChatMLX/Components/UltramanSidebarButtonStyle.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// UltramanSidebarButtonStyle.swift -// ChatMLX -// -// Created by John Mai on 2024/8/4. -// - -import SwiftUI - -struct UltramanSidebarButtonStyle: ButtonStyle { - @State var isHovering: Bool = false - @Binding var isActive: Bool - - func makeBody(configuration: Configuration) -> some View { - configuration.label - .padding(4) - .background { - if configuration.isPressed { - Rectangle().foregroundStyle(.quaternary) - } else if isHovering || isActive { - Rectangle().foregroundStyle(.quaternary.opacity(0.7)) - } - } - .onHover { hover in - isHovering = hover - } - .animation( - .easeOut(duration: 0.1), value: [isHovering, isActive, configuration.isPressed]) - } -} diff --git a/ChatMLX/Components/UltramanTextEditorWithPlaceholder.swift b/ChatMLX/Components/UltramanTextEditorWithPlaceholder.swift deleted file mode 100644 index a12583c..0000000 --- a/ChatMLX/Components/UltramanTextEditorWithPlaceholder.swift +++ /dev/null @@ -1,115 +0,0 @@ -// -// UltramanTextEditor.swift -// ChatMLX -// -// Created by John Mai on 2024/8/5. -// -import SwiftUI - -struct UltramanTextEditor: NSViewRepresentable { - @Binding var text: String - var placeholder: String - var onSubmit: () -> Void - - func makeNSView(context: Context) -> NSScrollView { - let scrollView = NSTextView.scrollableTextView() - - let textView = scrollView.documentView as! NSTextView - textView.delegate = context.coordinator - textView.isRichText = false - textView.font = .systemFont(ofSize: NSFont.systemFontSize) - textView.backgroundColor = .clear - textView.drawsBackground = false - textView.textColor = .white - context.coordinator.setupPlaceholder(for: textView) - - return scrollView - } - - func updateNSView(_ nsView: NSScrollView, context: Context) { - let textView = nsView.documentView as! NSTextView - if textView.string != text { - textView.string = text - } - - context.coordinator.updatePlaceholder(for: textView) - - let extraHeight: CGFloat = 50 - let contentSize = textView.string.boundingRect( - with: textView.frame.size, options: .usesLineFragmentOrigin, - attributes: [.font: textView.font!] - ).size - textView.minSize = NSSize( - width: 0, height: contentSize.height + extraHeight - ) - textView.maxSize = NSSize( - width: CGFloat.greatestFiniteMagnitude, - height: CGFloat.greatestFiniteMagnitude - ) - } - - func makeCoordinator() -> Coordinator { - Coordinator(self) - } - - class Coordinator: NSObject, NSTextViewDelegate { - var parent: UltramanTextEditor - var placeholderView: NSTextView? - - init(_ parent: UltramanTextEditor) { - self.parent = parent - super.init() - } - - func setupPlaceholder(for textView: NSTextView) { - let placeholder = NSTextView(frame: textView.bounds) - placeholder.isSelectable = false - placeholder.backgroundColor = .clear - placeholder.textColor = .white.withAlphaComponent(0.7) - placeholder.font = textView.font - placeholder.string = parent.placeholder - placeholder.alignment = .left - placeholder.textContainerInset = NSSize(width: 6, height: 0) - - textView.addSubview(placeholder) - placeholderView = placeholder - - updatePlaceholder(for: textView) - } - - func textDidChange(_ notification: Notification) { - guard let textView = notification.object as? NSTextView else { - return - } - parent.text = textView.string - updatePlaceholder(for: textView) - } - - func textViewDidChangeSelection(_ notification: Notification) { - guard let textView = notification.object as? NSTextView else { - return - } - updatePlaceholder(for: textView) - } - - func textView( - _ textView: NSTextView, doCommandBy commandSelector: Selector - ) -> Bool { - if commandSelector == #selector(NSResponder.insertNewline(_:)) { - if NSEvent.modifierFlags.contains(.shift) { - textView.insertNewlineIgnoringFieldEditor(nil) - return true - } else { - parent.onSubmit() - return true - } - } - return false - } - - func updatePlaceholder(for textView: NSTextView) { - placeholderView?.isHidden = - !textView.string.isEmpty || textView.selectedRange().length > 0 - } - } -} diff --git a/ChatMLX/Components/UltramanTextField.swift b/ChatMLX/Components/UltramanTextField.swift deleted file mode 100644 index 44f5935..0000000 --- a/ChatMLX/Components/UltramanTextField.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// UltramanTextField.swift -// ChatMLX -// -// Created by John Mai on 2024/8/18. -// - -import SwiftUI - -public struct UltramanTextField: View { - let elementMinHeight: CGFloat = 34 - let horizontalPadding: CGFloat = 8 - - @Binding var text: String - let title: LocalizedStringKey - let placeholder: Text? - let onSubmit: (() -> Void)? - var alignment: Alignment = .leading - - @State var monitor: Any? - @State private var isFocused: Bool = false - - public init( - _ text: Binding, - title: LocalizedStringKey = "", - placeholder: Text?, - onSubmit: (() -> Void)? = nil, - alignment: Alignment = .leading - ) { - self._text = text - self.title = title - self.placeholder = placeholder - self.onSubmit = onSubmit - self.alignment = alignment - } - - public var body: some View { - ZStack(alignment: alignment) { - if !isFocused, text.isEmpty, let placeholder { - placeholder - .foregroundColor(.white.opacity(0.7)) - .padding(alignment == .leading ? .leading : .trailing, 10) - } - - TextField(title, text: $text) - .padding(.horizontal, horizontalPadding) - .frame(minHeight: elementMinHeight) - .multilineTextAlignment( - alignment == .leading ? .leading : .trailing - ) - .textFieldStyle(.plain) - .onSubmit { - if let onSubmit { - onSubmit() - } - } - .onAppear { - guard monitor != nil else { return } - - monitor = NSEvent.addLocalMonitorForEvents( - matching: .keyDown - ) { - event in - if let window = NSApp.keyWindow, - window.animationBehavior == .documentWindow - { - window.keyDown(with: event) - - // Fixes cmd+w to close window. - let wKey = 13 - if event.keyCode == wKey, - event.modifierFlags.contains(.command) - { - return nil - } - } - return event - } - } - .onDisappear { - if let monitor { - NSEvent.removeMonitor(monitor) - } - monitor = nil - } - .onReceive( - NotificationCenter.default.publisher( - for: NSControl.textDidBeginEditingNotification) - ) { _ in - isFocused = true - } - .onReceive( - NotificationCenter.default.publisher( - for: NSControl.textDidEndEditingNotification) - ) { _ in - isFocused = false - } - } - } -} diff --git a/ChatMLX/Components/UltramanWindow.swift b/ChatMLX/Components/UltramanWindow.swift deleted file mode 100644 index 913d73e..0000000 --- a/ChatMLX/Components/UltramanWindow.swift +++ /dev/null @@ -1,51 +0,0 @@ -// -// UltramanWindow.swift -// ChatMLX -// -// Created by John Mai on 2024/8/3. -// - -import SwiftUI - -class UltramanWindow: NSWindow { - static var identifier = NSUserInterfaceItemIdentifier("UltramanWindow") - - init(rootView: () -> some View) { - super.init( - contentRect: .zero, - styleMask: [.closable, .titled, .fullSizeContentView, .resizable, .miniaturizable], - backing: .buffered, - defer: false - ) - - let view = NSHostingView( - rootView: rootView() - ) - - contentView = view - contentView?.wantsLayer = true - setContentSize(view.bounds.size) - - toolbarStyle = .unified - titlebarAppearsTransparent = true - titleVisibility = .hidden - - let customToolbar = NSToolbar() - customToolbar.showsBaselineSeparator = false - toolbar = customToolbar - - setBackgroundBlur(radius: 20) - identifier = Self.identifier - - alphaValue = 0 - - makeKeyAndOrderFront(self) - orderFrontRegardless() - NSApp.activate(ignoringOtherApps: true) - - Task { @MainActor in - self.center() - self.alphaValue = 1 - } - } -} diff --git a/ChatMLX/Extensions/Date+Extensions.swift b/ChatMLX/Extensions/Date+Extensions.swift deleted file mode 100644 index 74676a3..0000000 --- a/ChatMLX/Extensions/Date+Extensions.swift +++ /dev/null @@ -1,31 +0,0 @@ -// -// Date+Extensions.swift -// ChatMLX -// -// Created by John Mai on 2024/10/1. -// - -import Foundation - -extension Date { - func toFormatted( - style: DateFormatter.Style = .medium, - locale: Locale = .current - ) -> String { - let formatter = DateFormatter() - formatter.dateStyle = style - formatter.timeStyle = style - formatter.locale = locale - return formatter.string(from: self) - } - - func toTimeFormatted( - style: DateFormatter.Style = .medium, - locale: Locale = .current - ) -> String { - let formatter = DateFormatter() - formatter.timeStyle = style - formatter.locale = locale - return formatter.string(from: self) - } -} diff --git a/ChatMLX/Extensions/Defaults+Extensions.swift b/ChatMLX/Extensions/Defaults+Extensions.swift deleted file mode 100644 index 4d2f0eb..0000000 --- a/ChatMLX/Extensions/Defaults+Extensions.swift +++ /dev/null @@ -1,44 +0,0 @@ -// -// Defaults+Extensions.swift -// ChatMLX -// -// Created by John Mai on 2024/8/14. -// - -import Defaults -import SwiftUI - -extension Defaults.Keys { - static let defaultModel = Key("defaultModel", default: "") - static let language = Key("language", default: .english) - static let backgroundBlurRadius = Key("backgroundBlurRadius", default: 35) - static let backgroundColor = Key("backgroundColor", default: .black.opacity(0.4)) - static let huggingFaceEndpoint = Key( - "huggingFaceEndpoint", default: "https://huggingface.co") - static let customHuggingFaceEndpoints = Key<[String]>("customHuggingFaceEndpoints", default: []) - static let useCustomHuggingFaceEndpoint = Key( - "useCustomHuggingFaceEndpoint", default: false) - static let huggingFaceToken = Key("huggingFaceToken", default: nil) - - static let defaultTitle = Key("defaultTitle", default: "Default Conversation") - static let defaultTemperature = Key("defaultTemperature", default: 0.6) - static let defaultTopP = Key("defaultTopP", default: 1.0) - static let defaultUseMaxLength = Key("defaultUseMaxLength", default: true) - static let defaultMaxLength = Key("defaultMaxLength", default: 1024) - static let defaultRepetitionContextSize = Key( - "defaultRepetitionContextSize", default: 20) - static let defaultMaxMessagesLimit = Key("defaultMaxMessagesCount", default: 20) - static let defaultUseMaxMessagesLimit = Key("defaultUseMaxMessagesCount", default: false) - static let defaultRepetitionPenalty = Key("defaultRepetitionPenalty", default: 0) - static let defaultUseRepetitionPenalty = Key( - "defaultUseRepetitionPenalty", default: false) - static let defaultUseSystemPrompt = Key("defaultUseSystemPrompt", default: false) - static let defaultSystemPrompt = Key("defaultSystemPrompt", default: "") - - static let gpuCacheLimit = Key("gpuCacheLimit", default: 128) - - static let enableAppleIntelligenceEffect = Key( - "enableAppleIntelligenceEffect", default: false) - static let appleIntelligenceEffectDisplay = Key( - "appleIntelligenceEffectDisplay", default: .appInternal) -} diff --git a/ChatMLX/Extensions/MarkdownUI+Theme+Extensions.swift b/ChatMLX/Extensions/MarkdownUI+Theme+Extensions.swift deleted file mode 100644 index a6aebbd..0000000 --- a/ChatMLX/Extensions/MarkdownUI+Theme+Extensions.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// MarkdownUI+Theme+Extensions.swift -// ChatMLX -// -// Created by John Mai on 2024/8/18. -// - -import MarkdownUI -import SwiftUI - -extension MarkdownUI.Theme { - static let customGitHub = Theme.gitHub.text { - ForegroundColor(.white) - BackgroundColor(.clear) - } - .code { - FontFamilyVariant(.monospaced) - FontSize(.em(0.94)) - BackgroundColor(.clear) - } - .codeBlock { configuration in - VStack(spacing: 0) { - HStack { - Text(configuration.language ?? "plain text") - .font(.system(.caption, design: .monospaced)) - .fontWeight(.semibold) - .foregroundColor(.white) - Spacer() - - Button { - let pasteboard = NSPasteboard.general - pasteboard.clearContents() - pasteboard.setString(configuration.content, forType: .string) - } label: { - Image(systemName: "clipboard") - } - .buttonStyle(.borderless) - } - .padding(.horizontal) - .padding(.vertical, 8) - .background(.black.opacity(0.2)) - - Divider() - ScrollView(.horizontal) { - configuration.label - .fixedSize(horizontal: false, vertical: true) - .relativeLineSpacing(.em(0.225)) - .markdownTextStyle { - FontFamilyVariant(.monospaced) - FontSize(.em(0.85)) - } - .padding(16) - } - .background(.black.opacity(0.1)) - .markdownMargin(top: 0, bottom: 16) - } - .clipShape(RoundedRectangle(cornerRadius: 8)) - } -} diff --git a/ChatMLX/Extensions/NSWindow+Extensions.swift b/ChatMLX/Extensions/NSWindow+Extensions.swift deleted file mode 100644 index d4217bb..0000000 --- a/ChatMLX/Extensions/NSWindow+Extensions.swift +++ /dev/null @@ -1,56 +0,0 @@ -// -// NSWindow+Extensions.swift -// ChatMLX -// -// Created by John Mai on 2024/8/3. -// - -import SwiftUI - -extension NSWindow { - /// Sets the background blur of the window with a specified radius. - /// - Parameter radius: The blur radius to apply. - func setBackgroundBlur(radius: Int, color: NSColor = .black.withAlphaComponent(0.4)) { - guard let connection = try? getCGSConnection() else { - logger.error("Failed to get CGS connection") - return - } - - let status = CGSSetWindowBackgroundBlurRadius(connection, windowNumber, radius) - if status != noErr { - logger.error("Error setting blur radius: \(status)") - } - - backgroundColor = color - ignoresMouseEvents = false - } -} - -// MARK: - Private APIs and Helper Functions - -typealias CGSConnectionID = UInt32 - -@_silgen_name("CGSDefaultConnectionForThread") -func CGSDefaultConnectionForThread() -> CGSConnectionID? - -@_silgen_name("CGSSetWindowBackgroundBlurRadius") @discardableResult -func CGSSetWindowBackgroundBlurRadius( - _ connection: CGSConnectionID, - _ windowNum: NSInteger, - _ radius: Int -) -> OSStatus - -extension NSWindow { - /// Attempts to get the default CGS connection for the current thread. - /// - Returns: A `CGSConnectionID` if successful, `nil` otherwise. - fileprivate func getCGSConnection() throws -> CGSConnectionID { - guard let connection = CGSDefaultConnectionForThread() else { - throw NSError( - domain: "com.Luminare.NSWindow", - code: 1, - userInfo: [NSLocalizedDescriptionKey: "Unable to get CGS connection"] - ) - } - return connection - } -} diff --git a/ChatMLX/Extensions/String+Extensions.swift b/ChatMLX/Extensions/String+Extensions.swift deleted file mode 100644 index f7b6122..0000000 --- a/ChatMLX/Extensions/String+Extensions.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// String+Extensions.swift -// ChatMLX -// -// Created by John Mai on 2024/8/11. -// - -extension String { - func deletingPrefix(_ prefix: String) -> String { - guard self.hasPrefix(prefix) else { return self } - return String(self.dropFirst(prefix.count)) - } -} diff --git a/ChatMLX/Extensions/TimeInterval+Extensions.swift b/ChatMLX/Extensions/TimeInterval+Extensions.swift deleted file mode 100644 index 9e4a6d8..0000000 --- a/ChatMLX/Extensions/TimeInterval+Extensions.swift +++ /dev/null @@ -1,29 +0,0 @@ -// -// TimeInterval+Extensions.swift -// ChatMLX -// -// Created by John Mai on 2024/10/3. -// - -import Foundation - -extension TimeInterval { - func formatted( - allowedUnits: NSCalendar.Unit = [.hour, .minute, .second], - unitsStyle: DateComponentsFormatter.UnitsStyle = .abbreviated, - includingMilliseconds: Bool = true - ) -> String { - let formatter = DateComponentsFormatter() - formatter.allowedUnits = allowedUnits - formatter.unitsStyle = unitsStyle - - var formattedString = formatter.string(from: self) ?? "" - - if includingMilliseconds { - let milliseconds = Int((self.truncatingRemainder(dividingBy: 1)) * 1000) - formattedString += String(format: " %03dms", milliseconds) - } - - return formattedString - } -} diff --git a/ChatMLX/Extensions/View+Extensions.swift b/ChatMLX/Extensions/View+Extensions.swift deleted file mode 100644 index 7d77253..0000000 --- a/ChatMLX/Extensions/View+Extensions.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// View+Extensions.swift -// ChatMLX -// -// Created by John Mai on 2024/8/4. -// - -import SwiftUI - -struct SafeAreaInsetsKey: PreferenceKey { - static var defaultValue = EdgeInsets() - static func reduce(value: inout EdgeInsets, nextValue: () -> EdgeInsets) { - value = nextValue() - } -} - -extension View { - func placeholder( - when shouldShow: Bool, - alignment: Alignment = .leading, - @ViewBuilder placeholder: () -> some View - ) -> some View { - ZStack(alignment: alignment) { - placeholder() - .padding(2) - .opacity(shouldShow ? 1 : 0) - self - } - } - - func placeholder( - _ text: String, - when shouldShow: Bool, - alignment: Alignment = .leading - ) -> some View { - placeholder(when: shouldShow, alignment: alignment) { - Text(text).foregroundColor(.white.opacity(0.6)) - } - } - - func printSafeAreaInsets(id: String) -> some View { - background( - GeometryReader { proxy in - Color.clear - .preference(key: SafeAreaInsetsKey.self, value: proxy.safeAreaInsets) - } - .onPreferenceChange(SafeAreaInsetsKey.self) { value in - logger.debug("\(id) insets:\(value)") - } - ) - } -} diff --git a/ChatMLX/Features/Conversation/ConversationDetailView.swift b/ChatMLX/Features/Conversation/ConversationDetailView.swift deleted file mode 100644 index ddfa7f1..0000000 --- a/ChatMLX/Features/Conversation/ConversationDetailView.swift +++ /dev/null @@ -1,376 +0,0 @@ -// -// ConversationDetailView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/4. -// - -import AlertToast -import Defaults -import Luminare -import MLX -import MLXLLM -import SwiftUI - -struct ConversationDetailView: View { - @ObservedObject var conversation: Conversation - - @Environment(LLMRunner.self) var runner - @Environment(\.managedObjectContext) private var viewContext - @Environment(ConversationViewModel.self) private var vm - - @State private var newMessage = "" - - @State private var showRightSidebar = false - @State private var showInfoPopover = false - - @State private var localModels: [LocalModel] = [] - @State private var displayStyle: DisplayStyle = .markdown - @State private var isEditorFullScreen = false - @State private var showToast = false - @State private var toastMessage = "" - @State private var toastType: AlertToast.AlertType = .regular - @State private var loading = true - @State private var scrollViewProxy: ScrollViewProxy? - - @FocusState private var isInputFocused: Bool - - @Default(.enableAppleIntelligenceEffect) var enableAppleIntelligenceEffect - @Default(.appleIntelligenceEffectDisplay) var appleIntelligenceEffectDisplay - - var body: some View { - ZStack(alignment: .trailing) { - VStack(spacing: 0) { - if !isEditorFullScreen { - MessageBox() - Divider() - } - Editor() - } - - if showRightSidebar { - Color.black.opacity(0.00001) - .ignoresSafeArea() - .onTapGesture { - withAnimation { - showRightSidebar = false - } - } - - RightSidebarView(conversation: conversation) - } - } - .onAppear(perform: loadModels) - .toast(isPresenting: $showToast, duration: 1.5, offsetY: 30) { - AlertToast( - displayMode: .hud, type: toastType, title: toastMessage - ) - } - .ultramanNavigationTitle( - LocalizedStringKey(conversation.title) - ) - .ultramanToolbar(alignment: .trailing) { - Button(action: { - withAnimation { - showRightSidebar.toggle() - } - - }) { - Image(systemName: "slider.horizontal.3") - } - .buttonStyle(.plain) - } - } - - @MainActor - @ViewBuilder - private func MessageBox() -> some View { - ScrollViewReader { proxy in - ScrollView { - LazyVStack { - ForEach(conversation.messages) { message in - MessageBubbleView( - message: message, - displayStyle: $displayStyle - ).id(message.id) - } - } - .padding() - } - .onChange( - of: conversation.messages.last, - { _, _ in - scrollToBottom() - } - ) - .onAppear { - scrollViewProxy = proxy - scrollToBottom() - } - } - } - - private func scrollToBottom() { - guard let lastMessageId = conversation.messages.last?.id, let scrollViewProxy else { - return - } - - withAnimation { - scrollViewProxy.scrollTo(lastMessageId, anchor: .bottom) - } - } - - @MainActor - @ViewBuilder - private func EditorToolbar() -> some View { - HStack { - Button { - withAnimation { - displayStyle = (displayStyle == .markdown) ? .plain : .markdown - } - } label: { - Image(displayStyle == .markdown ? "plaintext" : "markdown") - } - - Button(action: { - conversation.messages = [] - }) { - Image("clear") - } - - Button { - withAnimation { - isEditorFullScreen.toggle() - } - } label: { - Image( - systemName: isEditorFullScreen - ? "arrow.down.right.and.arrow.up.left" - : "arrow.up.left.and.arrow.down.right") - } - .help(isEditorFullScreen ? "Exit Full Screen" : "Enter Full Screen") - - Spacer() - - Button { - showInfoPopover.toggle() - } label: { - if runner.gpuActiveMemory > 0 { - HStack { - Image(systemName: "info.circle") - Text("\(runner.gpuActiveMemory)M") - } - .padding(4) - .background(Color.black.opacity(0.2)) - .cornerRadius(20) - } else { - Image(systemName: "info.circle") - .padding(4) - } - } - .font(.subheadline) - .popover(isPresented: $showInfoPopover) { - VStack(alignment: .leading) { - LabeledContent { - Text(conversation.promptTime.formatted()) - } label: { - Text("Prompt Time") - .fontWeight(.bold) - } - - LabeledContent { - Text("\(Int(conversation.promptTokensPerSecond))") - } label: { - Text("Prompt Tokens/second") - .fontWeight(.bold) - } - - LabeledContent { - Text(conversation.generateTime.formatted()) - } label: { - Text("Generate Time") - .fontWeight(.bold) - } - - LabeledContent { - Text("\(Int(conversation.tokensPerSecond))") - } label: { - Text("Generate Tokens/second") - .fontWeight(.bold) - } - } - .padding() - .background(.clear) - } - - Image(systemName: "circle.fill") - .controlSize(.mini) - .foregroundStyle( - runner.modelConfiguration?.name == conversation.model - ? .green : .red - ) - .symbolEffect(.variableColor, isActive: runner.running) - .help("Model State") - - Picker( - selection: $conversation.model, - label: Image(systemName: "brain") - ) { - if !loading { - Text("Not selected").tag("") - ForEach(localModels, id: \.id) { model in - Text(model.name) - .tag(model.origin) - } - } - } - .pickerStyle(.menu) - .labelsHidden() - } - .buttonStyle(.borderless) - .foregroundStyle(.white) - .tint(.white) - .frame(height: 35) - .padding(.horizontal, 10) - } - - @MainActor - @ViewBuilder - private func Editor() -> some View { - VStack(alignment: .leading, spacing: 0) { - EditorToolbar() - - ZStack(alignment: .bottom) { - UltramanTextEditor( - text: $newMessage, - placeholder: "Type your message…", - onSubmit: sendMessage - ) - .padding(.horizontal, 5) - - HStack(spacing: 16) { - Spacer() - Button("Clear") { - newMessage = "" - } - .buttonStyle(.borderless) - .disabled(newMessage.isEmpty) - - Button { - sendMessage() - } label: { - if runner.running { - Label { - Text("Send") - } icon: { - ProgressView() - .controlSize(.small) - .padding(.trailing, 2) - .colorInvert() - .brightness(1) - } - } else { - Label("Send", systemImage: "paperplane") - } - } - .buttonStyle(LuminareCompactButtonStyle()) - .fixedSize() - .disabled( - newMessage.trimmingCharacters( - in: .whitespacesAndNewlines - ).isEmpty || runner.running) - } - .padding() - } - .frame(maxHeight: isEditorFullScreen ? .infinity : 150) - } - } - - private func sendMessage() { - let trimmedMessage = newMessage.trimmingCharacters( - in: .whitespacesAndNewlines) - guard !trimmedMessage.isEmpty else { return } - - if conversation.model.isEmpty { - showToastMessage("Please select a model", type: .error(Color.red)) - return - } - - newMessage = "" - isInputFocused = false - - Message(context: viewContext).user(content: trimmedMessage, conversation: conversation) - - if enableAppleIntelligenceEffect, appleIntelligenceEffectDisplay == .global { - AppleIntelligenceEffectManager.shared.setupEffect() - } - - runner.generate(conversation: conversation, in: viewContext) { - Task { @MainActor in - scrollToBottom() - } - } completion: { - Task { @MainActor in - if enableAppleIntelligenceEffect, appleIntelligenceEffectDisplay == .global { - AppleIntelligenceEffectManager.shared.closeEffect() - } - scrollToBottom() - } - } - } - - private func loadModels() { - let fileManager = FileManager.default - let documentsURL = fileManager.urls( - for: .documentDirectory, in: .userDomainMask - )[0] - let modelsURL = documentsURL.appendingPathComponent( - "huggingface/models") - - do { - let contents = try fileManager.contentsOfDirectory( - at: modelsURL, includingPropertiesForKeys: nil, - options: [.skipsHiddenFiles] - ) - var models: [LocalModel] = [] - - for groupURL in contents { - if groupURL.hasDirectoryPath { - let modelContents = try fileManager.contentsOfDirectory( - at: groupURL, includingPropertiesForKeys: nil, - options: [.skipsHiddenFiles] - ) - - for modelURL in modelContents { - if modelURL.hasDirectoryPath { - models.append( - LocalModel( - group: groupURL.lastPathComponent, - name: modelURL.lastPathComponent, - url: modelURL - ) - ) - } - } - } - } - - if !models.contains(where: { $0.origin == conversation.model }) { - conversation.model = "" - } - - Task { @MainActor in - localModels = models - loading = false - } - } catch { - vm.throwError(error, title: "Load Models Failed") - } - } - - private func showToastMessage(_ message: String, type: AlertToast.AlertType) { - toastMessage = message - toastType = type - showToast = true - } -} diff --git a/ChatMLX/Features/Conversation/ConversationSidebarItem.swift b/ChatMLX/Features/Conversation/ConversationSidebarItem.swift deleted file mode 100644 index b09997f..0000000 --- a/ChatMLX/Features/Conversation/ConversationSidebarItem.swift +++ /dev/null @@ -1,67 +0,0 @@ -// -// ConversationSidebarItem.swift -// ChatMLX -// -// Created by John Mai on 2024/8/4. -// - -import SwiftUI - -struct ConversationSidebarItem: View { - @ObservedObject var conversation: Conversation - - @Environment(\.managedObjectContext) private var viewContext - - @Binding var selectedConversation: Conversation? - - @State private var isHovering: Bool = false - @State private var isActive: Bool = false - @State private var showIndicator: Bool = false - - var body: some View { - Button { - selectedConversation = conversation - } label: { - VStack(alignment: .leading, spacing: 4) { - Text(LocalizedStringKey(conversation.title)) - .font(.headline) - - HStack { - Text(conversation.messages.first?.content ?? "") - .font(.subheadline) - .lineLimit(1) - - Spacer() - if conversation.isFault || conversation.isDeleted { - Text(conversation.updatedAt.toFormatted()) - .font(.caption) - } - } - .foregroundStyle(.white.opacity(0.7)) - } - .padding(6) - } - .buttonStyle(UltramanSidebarButtonStyle(isActive: $isActive)) - .onAppear { - checkIfSelfIsActiveTab() - } - .onChange(of: selectedConversation) { _, _ in - checkIfSelfIsActiveTab() - } - .contextMenu { - Button(role: .destructive, action: deleteConversation) { - Label("Delete", systemImage: "trash") - } - } - } - - private func checkIfSelfIsActiveTab() { - withAnimation(.easeOut(duration: 0.1)) { - isActive = selectedConversation == conversation - } - } - - private func deleteConversation() { - try? PersistenceController.shared.delete(conversation) - } -} diff --git a/ChatMLX/Features/Conversation/ConversationSidebarView.swift b/ChatMLX/Features/Conversation/ConversationSidebarView.swift deleted file mode 100644 index 3883da4..0000000 --- a/ChatMLX/Features/Conversation/ConversationSidebarView.swift +++ /dev/null @@ -1,93 +0,0 @@ -// -// ConversationSidebarView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/3. -// - -import Defaults -import Luminare -import SwiftUI - -struct ConversationSidebarView: View { - @Environment(ConversationViewModel.self) private var conversationViewModel - - @Binding var selectedConversation: Conversation? - - @Environment(\.managedObjectContext) private var viewContext - - @FetchRequest( - sortDescriptors: [NSSortDescriptor(keyPath: \Conversation.updatedAt, ascending: false)], - animation: .default - ) - private var conversations: FetchedResults - - @State private var showingNewConversationAlert = false - @State private var newConversationTitle = "" - @State private var showingClearConfirmation = false - - let padding: CGFloat = 8 - - @State private var keyword = "" - - var body: some View { - VStack(spacing: 0) { - HStack { - Spacer() - Button(action: conversationViewModel.createConversation) { - Image(systemName: "plus") - } - - SettingsLink { - Image(systemName: "gear") - } - } - .frame(height: 50) - .padding(.horizontal, padding) - .buttonStyle(.plain) - - HStack { - Image("AppLogo") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 60, height: 60) - .shadow(radius: 5) - Text("ChatMLX") - .font(.title) - .fontWeight(.bold) - } - - LuminareSection { - UltramanTextField( - $keyword, placeholder: Text("Search Conversation..."), - onSubmit: updateSearchPredicate - ) - - .frame(height: 25) - }.padding(.horizontal, padding) - - ScrollView { - LazyVStack(spacing: 0) { - ForEach(conversations) { conversation in - ConversationSidebarItem( - conversation: conversation, - selectedConversation: $selectedConversation - ) - } - } - } - .padding(.top, 6) - } - .background(.black.opacity(0.4)) - } - - private func updateSearchPredicate() { - if keyword.isEmpty { - conversations.nsPredicate = nil - } else { - conversations.nsPredicate = NSPredicate( - format: "title CONTAINS [cd] %@ OR ANY messages.content CONTAINS [cd] %@", keyword, - keyword) - } - } -} diff --git a/ChatMLX/Features/Conversation/ConversationView.swift b/ChatMLX/Features/Conversation/ConversationView.swift deleted file mode 100644 index d990561..0000000 --- a/ChatMLX/Features/Conversation/ConversationView.swift +++ /dev/null @@ -1,60 +0,0 @@ -// -// ConversationView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/3. -// - -import Defaults -import SwiftUI - -struct ConversationView: View { - @Environment(ConversationViewModel.self) private var conversationViewModel - @Environment(LLMRunner.self) private var runner - - @Default(.enableAppleIntelligenceEffect) var enableAppleIntelligenceEffect - @Default(.appleIntelligenceEffectDisplay) var appleIntelligenceEffectDisplay - - var body: some View { - @Bindable var conversationViewModel = conversationViewModel - - UltramanNavigationSplitView( - sidebar: { - ConversationSidebarView( - selectedConversation: $conversationViewModel.selectedConversation) - }, - detail: { - Detail() - } - ) - .foregroundColor(.white) - .ultramanMinimalistWindowStyle() - .overlay { - if enableAppleIntelligenceEffect, appleIntelligenceEffectDisplay == .appInternal, - runner.running - { - AppleIntelligenceEffectView(useRoundedRectangle: false) - .ignoresSafeArea() - .allowsHitTesting(false) - } - } - } - - @MainActor - @ViewBuilder - private func Detail() -> some View { - Group { - if let conversation = conversationViewModel.selectedConversation { - ConversationDetailView( - conversation: conversation - ).id(conversation.id) - } else { - EmptyConversation() - } - } - } -} - -#Preview { - ConversationView() -} diff --git a/ChatMLX/Features/Conversation/ConversationViewModel.swift b/ChatMLX/Features/Conversation/ConversationViewModel.swift deleted file mode 100644 index b9f0296..0000000 --- a/ChatMLX/Features/Conversation/ConversationViewModel.swift +++ /dev/null @@ -1,36 +0,0 @@ -// -// ConversationViewModel.swift -// ChatMLX -// -// Created by John Mai on 2024/10/3. -// - -import SwiftUI - -@Observable -class ConversationViewModel { - var detailWidth: CGFloat = 550 - var selectedConversation: Conversation? - - var error: Error? - var errorTitle: String? - var showErrorAlert = false - - func throwError(_ error: Error, title: String? = nil) { - logger.error("\(error.localizedDescription)") - self.error = error - errorTitle = title - showErrorAlert = true - } - - func createConversation() { - do { - let context = PersistenceController.shared.container.viewContext - let conversation = Conversation(context: context) - try PersistenceController.shared.save() - selectedConversation = conversation - } catch { - throwError(error, title: "Create Conversation Failed") - } - } -} diff --git a/ChatMLX/Features/Conversation/EmptyConversation.swift b/ChatMLX/Features/Conversation/EmptyConversation.swift deleted file mode 100644 index 172df0b..0000000 --- a/ChatMLX/Features/Conversation/EmptyConversation.swift +++ /dev/null @@ -1,35 +0,0 @@ -// -// EmptyConversation.swift -// ChatMLX -// -// Created by John Mai on 2024/8/3. -// - -import Luminare -import SwiftUI - -struct EmptyConversation: View { - @Environment(ConversationViewModel.self) private var conversationViewModel - - var body: some View { - ContentUnavailableView { - Label("No Conversation", systemImage: "tray.fill") - .foregroundColor(.white) - } description: { - Text("Please select a new conversation") - .foregroundColor(.white) - Button( - action: conversationViewModel.createConversation, - label: { - HStack { - Image(systemName: "plus") - .foregroundStyle(.white) - Text("New Conversation") - } - .foregroundColor(.white) - } - ).buttonStyle(LuminareCompactButtonStyle()) - .fixedSize() - } - } -} diff --git a/ChatMLX/Features/Conversation/MessageBubbleView.swift b/ChatMLX/Features/Conversation/MessageBubbleView.swift deleted file mode 100644 index 27cd7e3..0000000 --- a/ChatMLX/Features/Conversation/MessageBubbleView.swift +++ /dev/null @@ -1,184 +0,0 @@ -// -// MessageBubbleView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/4. -// - -import AlertToast -import MarkdownUI -import SwiftUI - -struct MessageBubbleView: View { - @ObservedObject var message: Message - @Binding var displayStyle: DisplayStyle - @State private var showToast = false - - @Environment(LLMRunner.self) var runner - @Environment(ConversationViewModel.self) var vm - - @Environment(\.managedObjectContext) private var viewContext - - private func copyText() { - let pasteboard = NSPasteboard.general - pasteboard.clearContents() - pasteboard.setString(message.content, forType: .string) - showToast = true - } - - var body: some View { - HStack { - if message.role == .assistant { - assistantMessageView - } else { - Spacer() - userMessageView - } - } - .textSelection(.enabled) - .padding(.vertical, 8) - .toast(isPresenting: $showToast, duration: 1.5, offsetY: 30) { - AlertToast(displayMode: .hud, type: .complete(.green), title: "Copied") - } - } - - @MainActor - @ViewBuilder - private var assistantMessageView: some View { - HStack(alignment: .top, spacing: 12) { - Image("AppLogo") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 30, height: 30) - .background(.white) - .clipShape(RoundedRectangle(cornerRadius: 5)) - .shadow(color: .black.opacity(0.25), radius: 5, x: -1, y: 5) - - VStack(alignment: .leading) { - if displayStyle == .markdown { - Markdown(MarkdownContent(message.content)) - .markdownCodeSyntaxHighlighter( - .splash(theme: .sunset(withFont: .init(size: 16))) - ) - .markdownTextStyle { - ForegroundColor(.white) - } - .markdownTheme(.customGitHub) - } else { - Text(message.content) - } - - if let error = message.error, !error.isEmpty { - HStack { - Image(systemName: "exclamationmark.triangle") - .foregroundStyle(.yellow) - Text(error) - } - .padding(5) - .background(.red.opacity(0.3)) - .foregroundColor(.white) - .cornerRadius(5) - } - - HStack { - Button(action: copyText) { - Image(systemName: "doc.on.doc") - .help("Copy") - } - - Button(action: regenerate) { - Image(systemName: "arrow.clockwise") - .help("Regenerate") - } - - Text(message.updatedAt.toTimeFormatted()) - .font(.caption) - - if message.role == .assistant, message.inferring { - ProgressView() - .controlSize(.small) - .colorInvert() - .brightness(1) - .padding(.leading, 5) - } - } - .buttonStyle(.plain) - .foregroundColor(.white.opacity(0.8)) - .padding(.top, 4) - .frame(maxWidth: .infinity, alignment: .leading) - } - Spacer() - } - } - - @MainActor - @ViewBuilder - private var userMessageView: some View { - VStack(alignment: .trailing) { - Text(message.content) - .padding(10) - .background(Color.black.opacity(0.1618)) - .foregroundColor(.white) - .cornerRadius(8) - - HStack { - Text(message.updatedAt.toTimeFormatted()) - .font(.caption) - - Button(action: copyText) { - Image(systemName: "doc.on.doc") - .help("Copy") - } - - Button(action: delete) { - Image(systemName: "trash") - } - } - .buttonStyle(.plain) - .foregroundColor(.white.opacity(0.8)) - .padding(.top, 4) - .frame(maxWidth: .infinity, alignment: .trailing) - } - } - - private func delete() { - guard message.role == .user else { return } - let conversation = message.conversation - let messages = conversation.messages - if let index = messages.firstIndex(of: message) { - for message in messages[index...] { - viewContext.delete(message) - } - } - - Task(priority: .background) { - do { - try await viewContext.perform { - if viewContext.hasChanges { - try viewContext.save() - } - } - } catch { - vm.throwError(error, title: "Delete Message Failed") - } - } - } - - private func regenerate() { - guard message.role == .assistant else { return } - - Task { - let conversation = message.conversation - let messages = conversation.messages - if let index = messages.firstIndex(of: message) { - for message in messages[index...] { - viewContext.delete(message) - } - } - - await MainActor.run { - runner.generate(conversation: conversation, in: viewContext, completion: nil) - } - } - } -} diff --git a/ChatMLX/Features/Conversation/RightSidebarView.swift b/ChatMLX/Features/Conversation/RightSidebarView.swift deleted file mode 100644 index 0f0c09c..0000000 --- a/ChatMLX/Features/Conversation/RightSidebarView.swift +++ /dev/null @@ -1,218 +0,0 @@ -// -// RightSidebarView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/5. -// - -import CompactSlider -import Luminare -import SwiftUI - -struct RightSidebarView: View { - @ObservedObject var conversation: Conversation - - private let padding: CGFloat = 6 - - var body: some View { - ScrollView { - VStack { - LuminareSection("Conversation Title") { - UltramanTextField( - Binding( - get: { - conversation.title - }, - set: { title in - conversation.title = title - } - ), placeholder: Text("Conversation Title") - ) - .frame(height: 25) - } - - LuminareSection("Model Settings") { - HStack { - Text("Temperature") - Spacer() - CompactSlider( - value: $conversation.temperature, in: 0 ... 2, - step: 0.01 - ) { - Text( - "\(conversation.temperature, specifier: "%.2f")" - ) - .foregroundStyle(.white) - } - .frame(width: 100) - } - .padding(padding) - - HStack { - Text("Top P") - Spacer() - CompactSlider( - value: $conversation.topP, in: 0 ... 1, step: 0.01 - ) { - Text("\(conversation.topP, specifier: "%.2f")") - .foregroundStyle(.white) - } - .frame(width: 100) - } - .padding(padding) - - HStack { - Text("Use Max Length") - Spacer() - Toggle("", isOn: $conversation.useMaxLength) - .labelsHidden() - .toggleStyle(.switch) - } - .padding(padding) - - if conversation.useMaxLength { - HStack { - Text("Max Length") - Spacer() - CompactSlider( - value: Binding( - get: { - Double(conversation.maxLength) - }, - set: { - conversation.maxLength = Int64($0) - } - ), in: 0 ... 8192, step: 1 - ) { - Text("\(Int(conversation.maxLength))") - .foregroundStyle(.white) - } - .frame(width: 100) - } - .padding(padding) - } - - HStack { - Text("Repetition Context Size") - Spacer() - CompactSlider( - value: Binding( - get: { - Double(conversation.repetitionContextSize) - }, - set: { - conversation.repetitionContextSize = Int($0) - } - ), in: 0 ... 100, step: 1 - ) { - Text("\(conversation.repetitionContextSize)") - .foregroundStyle(.white) - } - .frame(width: 100) - } - .padding(padding) - - HStack { - Text("Use Repetition Penalty") - Spacer() - Toggle("", isOn: $conversation.useRepetitionPenalty) - .labelsHidden() - .toggleStyle(.switch) - } - .padding(padding) - - if conversation.useRepetitionPenalty { - HStack { - Text("Repetition Penalty") - Spacer() - CompactSlider( - value: $conversation.repetitionPenalty, - in: 1 ... 2, - step: 0.01 - ) { - Text( - "\(conversation.repetitionPenalty, specifier: "%.2f")" - ) - .foregroundStyle(.white) - } - .frame(width: 100) - } - .padding(padding) - } - } - .compactSliderSecondaryColor(.white) - - LuminareSection("Message Control") { - HStack { - Text("Use Max Messages Limit") - Spacer() - Toggle("", isOn: $conversation.useMaxMessagesLimit) - .labelsHidden() - .toggleStyle(.switch) - } - .padding(padding) - - if conversation.useMaxMessagesLimit { - HStack { - Text("Max Messages Limit") - Spacer() - CompactSlider( - value: Binding( - get: { - Double(conversation.maxMessagesLimit) - }, - set: { - conversation.maxMessagesLimit = Int32($0) - } - ), in: 1 ... 50, step: 1 - ) { - Text("\(conversation.maxMessagesLimit)") - .foregroundStyle(.white) - } - .frame(width: 100) - } - .padding(padding) - } - } - .compactSliderSecondaryColor(.white) - - LuminareSection("System Prompt") { - HStack { - Text("Use System Prompt") - Spacer() - Toggle("", isOn: $conversation.useSystemPrompt) - .labelsHidden() - .toggleStyle(.switch) - } - .padding(padding) - - if conversation.useSystemPrompt { - UltramanTextEditor( - text: $conversation.systemPrompt, - placeholder: "System prompt", - onSubmit: { - - } - ) - .frame(height: 100) - .padding(padding) - } - } - - Spacer() - } - .padding() - } - .frame(width: 260) - .transition(.move(edge: .trailing)) - .background(.black.opacity(0.3)) - .scrollContentBackground(.hidden) - .background( - EffectView( - .hudWindow, - blendingMode: .withinWindow, - emphasized: true - ) - ) - } -} diff --git a/ChatMLX/Features/Settings/AboutView.swift b/ChatMLX/Features/Settings/AboutView.swift deleted file mode 100644 index 0bffc12..0000000 --- a/ChatMLX/Features/Settings/AboutView.swift +++ /dev/null @@ -1,58 +0,0 @@ -// -// AboutView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/10. -// - -import Luminare -import SwiftUI - -struct AboutView: View { - @State private var isCheckingUpdate = false - - var body: some View { - VStack(spacing: 30) { - Image("AppLogo") - .resizable() - .aspectRatio(contentMode: .fit) - .frame(width: 100, height: 100) - .clipShape(RoundedRectangle(cornerRadius: 20)) - .shadow(radius: 5) - - Text("ChatMLX") - .font(.largeTitle) - .fontWeight(.bold) - - Text( - "Version \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown")" - ) - .font(.subheadline) - .foregroundColor(.white) - - Link("GitHub", destination: URL(string: "https://github.com/maiqingqiang/ChatMLX")!) - .font(.headline) - .foregroundColor(.blue.opacity(0.8)) - - Spacer() - LuminareSection { - Button(action: { - isCheckingUpdate = true - DispatchQueue.main.asyncAfter(deadline: .now() + 2) { - isCheckingUpdate = false - } - - NSWorkspace.shared.open(URL(string: "https://github.com/maiqingqiang/ChatMLX")!) - }) { - Text(isCheckingUpdate ? "Checking..." : "Check for updates") - } - .frame(height: 30) - .buttonStyle(LuminareButtonStyle()) - .disabled(isCheckingUpdate) - } - } - .padding() - .frame(maxWidth: .infinity, maxHeight: .infinity) - .ultramanNavigationTitle("About") - } -} diff --git a/ChatMLX/Features/Settings/DefaultConversationView.swift b/ChatMLX/Features/Settings/DefaultConversationView.swift deleted file mode 100644 index c4b8449..0000000 --- a/ChatMLX/Features/Settings/DefaultConversationView.swift +++ /dev/null @@ -1,268 +0,0 @@ -// -// DefaultChatView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/27. -// - -import CompactSlider -import Defaults -import Luminare -import SwiftUI - -struct DefaultConversationView: View { - @Default(.defaultTitle) var defaultTitle - @Default(.defaultModel) var defaultModel - @Default(.defaultTemperature) var defaultTemperature - @Default(.defaultTopP) var defaultTopP - @Default(.defaultMaxLength) var defaultMaxLength - @Default(.defaultRepetitionContextSize) var defaultRepetitionContextSize - @Default(.defaultMaxMessagesLimit) var defaultMaxMessagesLimit - @Default(.defaultUseMaxMessagesLimit) var defaultUseMaxMessagesLimit - @Default(.defaultRepetitionPenalty) var defaultRepetitionPenalty - @Default(.defaultUseRepetitionPenalty) var defaultUseRepetitionPenalty - @Default(.defaultUseMaxLength) var defaultUseMaxLength - @Default(.defaultUseSystemPrompt) var defaultUseSystemPrompt - @Default(.defaultSystemPrompt) var defaultSystemPrompt - - @State private var localModels: [LocalModel] = [] - - @Environment(SettingsViewModel.self) var vm - - private let padding: CGFloat = 6 - - var body: some View { - ScrollView { - VStack { - LuminareSection("Title") { - UltramanTextField( - $defaultTitle, - placeholder: Text("Default conversation title") - ) - .frame(height: 25) - } - - LuminareSection("Model Settings") { - HStack { - Text("Model") - Spacer() - Picker( - selection: $defaultModel, - label: Image(systemName: "brain") - ) { - if !localModels.isEmpty { - Text("Not selected").tag("") - ForEach(localModels, id: \.id) { model in - Text(model.name).tag(model.origin) - } - } - } - .labelsHidden() - .buttonStyle(.borderless) - .foregroundStyle(.white) - .tint(.white) - } - .padding(padding) - - HStack { - Text("Temperature") - Spacer() - CompactSlider( - value: $defaultTemperature, in: 0 ... 2, step: 0.01 - ) { - Text("\(defaultTemperature, specifier: "%.2f")") - .foregroundStyle(.white) - } - .frame(width: 200) - } - .padding(padding) - - HStack { - Text("Top P") - Spacer() - CompactSlider( - value: $defaultTopP, in: 0 ... 1, step: 0.01 - ) { - Text("\(defaultTopP, specifier: "%.2f")") - .foregroundStyle(.white) - } - .frame(width: 200) - } - .padding(padding) - - HStack { - Text("Use Max Length") - Spacer() - Toggle("", isOn: $defaultUseMaxLength) - .toggleStyle(.switch) - } - .padding(padding) - - if defaultUseMaxLength { - HStack { - Text("Max Length") - Spacer() - CompactSlider( - value: Binding( - get: { Double(defaultMaxLength) }, - set: { defaultMaxLength = Int64($0) } - ), in: 0 ... 8192, step: 1 - ) { - Text("\(defaultMaxLength)") - .foregroundStyle(.white) - } - .frame(width: 200) - } - .padding(padding) - } - - HStack { - Text("Repetition Context Size") - Spacer() - CompactSlider( - value: Binding( - get: { Double(defaultRepetitionContextSize) }, - set: { defaultRepetitionContextSize = Int32($0) } - ), in: 0 ... 100, step: 1 - ) { - Text("\(defaultRepetitionContextSize)") - .foregroundStyle(.white) - } - .frame(width: 200) - } - .padding(padding) - - HStack { - Text("Use Repetition Penalty") - Spacer() - Toggle("", isOn: $defaultUseRepetitionPenalty) - .toggleStyle(.switch) - } - .padding(padding) - - if defaultUseRepetitionPenalty { - HStack { - Text("Repetition Penalty") - Spacer() - CompactSlider( - value: $defaultRepetitionPenalty, in: 1 ... 2, - step: 0.01 - ) { - Text( - "\(defaultRepetitionPenalty, specifier: "%.2f")" - ) - .foregroundStyle(.white) - } - .frame(width: 200) - } - .padding(padding) - } - } - .compactSliderSecondaryColor(.white) - - LuminareSection("Message Control") { - HStack { - Text("Use Max Messages Limit") - Spacer() - Toggle("", isOn: $defaultUseMaxMessagesLimit) - .toggleStyle(.switch) - } - .padding(padding) - - if defaultUseMaxMessagesLimit { - HStack { - Text("Max Messages Limit") - Spacer() - CompactSlider( - value: Binding( - get: { Double(defaultMaxMessagesLimit) }, - set: { defaultMaxMessagesLimit = Int32($0) } - ), in: 1 ... 50, step: 1 - ) { - Text("\(defaultMaxMessagesLimit)") - .foregroundStyle(.white) - } - .frame(width: 200) - } - .padding(padding) - } - } - .compactSliderSecondaryColor(.white) - - LuminareSection("System Prompt") { - HStack { - Text("Use System Prompt") - Spacer() - Toggle("", isOn: $defaultUseSystemPrompt) - .toggleStyle(.switch) - } - .padding(padding) - - if defaultUseSystemPrompt { - UltramanTextEditor( - text: $defaultSystemPrompt, - placeholder: "System prompt", - onSubmit: {} - ) - .frame(height: 100) - .padding(padding) - } - } - - Spacer() - } - .padding() - } - .scrollContentBackground(.hidden) - .onAppear(perform: loadModels) - .ultramanNavigationTitle("Default Conversation") - } - - private func loadModels() { - let fileManager = FileManager.default - let documentsURL = fileManager.urls( - for: .documentDirectory, in: .userDomainMask - )[0] - let modelsURL = documentsURL.appendingPathComponent( - "huggingface/models") - - do { - let contents = try fileManager.contentsOfDirectory( - at: modelsURL, includingPropertiesForKeys: nil, - options: [.skipsHiddenFiles] - ) - var models: [LocalModel] = [] - - for groupURL in contents { - if groupURL.hasDirectoryPath { - let modelContents = try fileManager.contentsOfDirectory( - at: groupURL, includingPropertiesForKeys: nil, - options: [.skipsHiddenFiles] - ) - - for modelURL in modelContents { - if modelURL.hasDirectoryPath { - models.append( - LocalModel( - group: groupURL.lastPathComponent, - name: modelURL.lastPathComponent, - url: modelURL - ) - ) - } - } - } - } - - if !models.contains(where: { $0.origin == defaultModel }) { - defaultModel = "" - } - - Task { @MainActor in - localModels = models - } - } catch { - vm.throwError(error, title: "Load Models Failed") - } - } -} diff --git a/ChatMLX/Features/Settings/DownloadManager/DownloadManagerView.swift b/ChatMLX/Features/Settings/DownloadManager/DownloadManagerView.swift deleted file mode 100644 index 178866c..0000000 --- a/ChatMLX/Features/Settings/DownloadManager/DownloadManagerView.swift +++ /dev/null @@ -1,59 +0,0 @@ -// -// DownloadManagerView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/11. -// - -import SwiftUI - -struct DownloadManagerView: View { - @Environment(SettingsViewModel.self) private var settingsViewModel - - @State private var repoId: String = "" - @State var showingAlert = false - - var body: some View { - - List { - ForEach(settingsViewModel.tasks) { task in - DownloadTaskView(task: task) - } - } - .onChange(of: showingAlert) { _, _ in - } - .frame(maxWidth: .infinity, maxHeight: .infinity) - .scrollContentBackground(.hidden) - .onAppear() - .ultramanNavigationTitle("Download Manager") - .ultramanToolbar(alignment: .trailing) { - Button(action: show) { - Image(systemName: "plus") - } - .buttonStyle(.plain) - } - .alert("New Task", isPresented: $showingAlert) { - TextField( - "Hugging Face Repo Id", text: $repoId, - prompt: Text("mlx-community/OpenELM-3B")) - Button("Cancel", role: .cancel) { - repoId = "" - } - Button(action: addTask) { - Text("Done") - } - } message: { - Text("Please enter Hugging Face Repo ID") - } - } - - private func show() { - showingAlert = true - } - - private func addTask() { - let task = DownloadTask(repoId) - task.start() - settingsViewModel.tasks.append(task) - } -} diff --git a/ChatMLX/Features/Settings/DownloadManager/DownloadTaskView.swift b/ChatMLX/Features/Settings/DownloadManager/DownloadTaskView.swift deleted file mode 100644 index 40f230f..0000000 --- a/ChatMLX/Features/Settings/DownloadManager/DownloadTaskView.swift +++ /dev/null @@ -1,86 +0,0 @@ -// -// DownloadTaskView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/11. -// - -import SwiftUI - -struct DownloadTaskView: View { - @Bindable var task: DownloadTask - @Environment(SettingsViewModel.self) private var settingsViewModel - - var body: some View { - HStack { - VStack { - HStack { - Text(task.repoId.deletingPrefix("mlx-community/")) - .font(.headline) - .lineLimit(1) - .help(task.repoId) - - Spacer() - - Text("\(task.progress * 100, specifier: "%.2f")%") - .font(.subheadline) - .fontWeight(.bold) - .frame(width: 50, alignment: .trailing) - } - - HStack { - Spacer() - - Text("\(task.completedUnitCount) / \(task.totalUnitCount)") - .font(.caption) - .foregroundStyle(.white.opacity(0.7)) - } - - ProgressView(value: task.progress) - .progressViewStyle(LinearProgressViewStyle()) - .frame(height: 4) - } - - Spacer() - - if task.isCompleted { - Image(systemName: "checkmark.circle.fill") - .foregroundColor(.green) - } else { - if task.isDownloading { - Button(action: { - task.stop() - }) { - Image(systemName: "pause.circle") - .foregroundColor(.yellow) - } - } else { - HStack { - Button(action: { - task.start() - }) { - Image(systemName: "play.circle") - .foregroundColor(.green) - } - - Button(action: { - settingsViewModel.tasks.removeAll(where: { - $0.id == task.id - }) - }) { - Image(systemName: "trash") - .renderingMode(.original) - } - } - } - } - } - .imageScale(.large) - .buttonStyle(.plain) - .padding() - .background(.black.opacity(0.3)) - .listRowSeparator(.hidden) - .clipShape(RoundedRectangle(cornerRadius: 10)) - .shadow(color: .black, radius: 2) - } -} diff --git a/ChatMLX/Features/Settings/ExperimentalFeaturesView.swift b/ChatMLX/Features/Settings/ExperimentalFeaturesView.swift deleted file mode 100644 index 1a97b86..0000000 --- a/ChatMLX/Features/Settings/ExperimentalFeaturesView.swift +++ /dev/null @@ -1,70 +0,0 @@ -// -// ExperimentalFeaturesView.swift -// ChatMLX -// -// Created by John Mai on 2024/10/7. -// - -import Defaults -import Luminare -import SwiftUI - -struct ExperimentalFeaturesView: View { - @Default(.enableAppleIntelligenceEffect) var enableAppleIntelligenceEffect - @Default(.appleIntelligenceEffectDisplay) var appleIntelligenceEffectDisplay - - @State private var showingPopover: Bool = false - - var body: some View { - VStack(spacing: 18) { - LuminareSection("Window Appearance") { - HStack { - Text("Apple Intelligence Effect") - Spacer() - Toggle("", isOn: $enableAppleIntelligenceEffect) - .toggleStyle(.switch) - } - .padding(6) - - if enableAppleIntelligenceEffect { - HStack { - Text("Display Mode") - Spacer() - Picker( - "Display Mode", - selection: $appleIntelligenceEffectDisplay - ) { - ForEach(AppleIntelligenceEffectDisplay.allCases) { display in - Text(display.localized).tag(display) - } - } - .labelsHidden() - .buttonStyle(.borderless) - .foregroundStyle(.white) - .tint(.white) - } - .padding(8) - } - } - Spacer() - } - .ultramanToolbar(alignment: .trailing) { - Button(action: { - showingPopover = true - }) { - Image(systemName: "exclamationmark.triangle") - } - .buttonStyle(.plain) - .symbolRenderingMode(.multicolor) - .popover(isPresented: $showingPopover, arrowEdge: .bottom) { - Text( - "Experimental features may have performance limitations. Features and programmatic interfaces are subject to change at any time." - ) - .frame(width: 200) - .padding() - } - } - .ultramanNavigationTitle("Experimental Features") - .padding() - } -} diff --git a/ChatMLX/Features/Settings/GeneralView.swift b/ChatMLX/Features/Settings/GeneralView.swift deleted file mode 100644 index 83c1cdb..0000000 --- a/ChatMLX/Features/Settings/GeneralView.swift +++ /dev/null @@ -1,157 +0,0 @@ -// -// GeneralView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/18. -// - -import CompactSlider -import CoreData -import Defaults -import Luminare -import SwiftUI - -struct GeneralView: View { - @Default(.backgroundBlurRadius) var blurRadius - @Default(.backgroundColor) var backgroundColor - @Default(.language) var language - @Default(.gpuCacheLimit) var gpuCacheLimit - - @Environment(\.managedObjectContext) private var viewContext - - @Environment(SettingsViewModel.self) private var vm - @Environment(ConversationViewModel.self) private var conversationViewModel - - @Environment(LLMRunner.self) var runner - - let maxRAM = ProcessInfo.processInfo.physicalMemory / (1024 * 1024) - - var body: some View { - VStack(spacing: 18) { - LuminareSection("Language") { - HStack { - Text("Language") - Spacer() - Picker( - "Language", - selection: $language - ) { - ForEach(Language.allCases) { language in - Text(language.displayName).tag(language) - } - } - .labelsHidden() - .buttonStyle(.borderless) - .foregroundStyle(.white) - .tint(.white) - } - .padding(8) - } - - LuminareSection("Window Appearance") { - HStack { - Text("Blur") - Spacer() - CompactSlider(value: $blurRadius, in: 0 ... 100) { - Text("\(Int(blurRadius))") - .foregroundStyle(.white) - } - .frame(width: 200) - .compactSliderSecondaryColor(.white) - } - .padding(5) - - HStack { - Text("Color") - Spacer() - ColorPicker("", selection: $backgroundColor) - .labelsHidden() - } - .padding(5) - } - - LuminareSection("System Settings") { - HStack { - Text("GPU Cache Limit") - Spacer() - CompactSlider( - value: Binding( - get: { Double(gpuCacheLimit) }, - set: { gpuCacheLimit = Int32($0) } - ), in: 0 ... Double(maxRAM), step: 128 - ) { - Text("\(Int(gpuCacheLimit))MB") - .foregroundStyle(.white) - } - .frame(width: 200) - .compactSliderSecondaryColor(.white) - .onChange(of: gpuCacheLimit) { oldValue, newValue in - if oldValue != newValue { - runner.loadState = .idle - } - } - } - .padding(5) - - Button("Clear All Conversations", action: clearAllConversations) - .frame(height: 35) - Button("Reset All Settings", action: resetAllSettings) - .frame(height: 35) - } - .buttonStyle(LuminareDestructiveButtonStyle()) - - Spacer() - } - - .ultramanNavigationTitle("General") - .padding() - } - - private func resetAllSettings() { - Defaults.reset(.defaultModel) - Defaults.reset(.language) - Defaults.reset(.backgroundBlurRadius) - Defaults.reset(.backgroundColor) - Defaults.reset(.huggingFaceEndpoint) - Defaults.reset(.customHuggingFaceEndpoints) - Defaults.reset(.useCustomHuggingFaceEndpoint) - Defaults.reset(.huggingFaceToken) - Defaults.reset(.defaultTitle) - Defaults.reset(.defaultTemperature) - Defaults.reset(.defaultTopP) - Defaults.reset(.defaultUseMaxLength) - Defaults.reset(.defaultMaxLength) - Defaults.reset(.defaultRepetitionContextSize) - Defaults.reset(.defaultMaxMessagesLimit) - Defaults.reset(.defaultUseMaxMessagesLimit) - Defaults.reset(.defaultRepetitionPenalty) - Defaults.reset(.defaultUseRepetitionPenalty) - Defaults.reset(.defaultUseSystemPrompt) - Defaults.reset(.defaultSystemPrompt) - Defaults.reset(.gpuCacheLimit) - } - - private func clearAllConversations() { - do { - let persistenceController = PersistenceController.shared - - let messageObjectIds = try persistenceController.clear("Message") - let conversationObjectIds = try persistenceController.clear("Conversation") - - NSManagedObjectContext.mergeChanges( - fromRemoteContextSave: [ - NSDeletedObjectsKey: messageObjectIds + conversationObjectIds - ], - into: [persistenceController.container.viewContext] - ) - - conversationViewModel.selectedConversation = nil - } catch { - vm.throwError(error, title: "Clear All Conversations Failed") - } - } -} - -#Preview { - GeneralView() -} diff --git a/ChatMLX/Features/Settings/HuggingFaceView.swift b/ChatMLX/Features/Settings/HuggingFaceView.swift deleted file mode 100644 index 8608868..0000000 --- a/ChatMLX/Features/Settings/HuggingFaceView.swift +++ /dev/null @@ -1,157 +0,0 @@ -// -// HuggingFaceView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/27. -// - -import Defaults -import Luminare -import SwiftUI - -struct HuggingFaceView: View { - @Default(.huggingFaceEndpoint) var endpoint - @Default(.customHuggingFaceEndpoints) var customEndpoints - @Default(.useCustomHuggingFaceEndpoint) var useCustomEndpoint - - @Default(.huggingFaceToken) var token - - @State private var newCustomEndpoint: String = "" - @State private var showingAlert = false - @State private var alertMessage = "" - - var body: some View { - VStack(spacing: 18) { - LuminareSection("Hugging Face Token") { - HStack { - Text("Token") - Spacer() - UltramanSecureField( - Binding( - get: { token ?? "" }, - set: { token = $0.isEmpty ? nil : $0 } - ), - placeholder: Text("Enter your Hugging Face token"), - alignment: .trailing - ) - .frame(height: 25) - } - .padding(5) - } - - LuminareSection("Hugging Face Endpoint") { - - HStack { - Text("Endpoint") - Spacer() - Picker("", selection: $endpoint) { - if useCustomEndpoint { - ForEach(customEndpoints, id: \.self) { - customEndpoint in - Text(customEndpoint).tag(customEndpoint) - } - } - Text("https://huggingface.co").tag( - "https://huggingface.co") - Text("https://hf-mirror.com").tag( - "https://hf-mirror.com") - } - .labelsHidden() - .buttonStyle(.borderless) - .foregroundStyle(.white) - .tint(.white) - } - .padding(5) - - HStack { - Text("Use Custom Endpoint") - Spacer() - Toggle("", isOn: $useCustomEndpoint) - .toggleStyle(.switch) - .labelsHidden() - } - .padding(8) - - if useCustomEndpoint { - HStack { - Text("Custom Endpoint") - Spacer() - UltramanTextField( - $newCustomEndpoint, - placeholder: Text("Custom Hugging Face Endpoint"), - alignment: .trailing - ) - .frame(height: 25) - .onSubmit { - addCustomEndpoint() - } - } - .padding(5) - - if !customEndpoints.isEmpty { - List { - ForEach(customEndpoints, id: \.self) { - customEndpoint in - HStack { - Text(customEndpoint) - Spacer() - - Button { - removeCustomEndpoint(customEndpoint) - } label: { - Image(systemName: "trash") - .foregroundColor(.red) - } - .buttonStyle(.plain) - } - .padding(.vertical, 4) - } - } - .scrollContentBackground(.hidden) - .listStyle(.plain) - } - } - } - - Spacer() - } - .ultramanNavigationTitle("Hugging Face") - .padding() - .alert(isPresented: $showingAlert) { - Alert( - title: Text("Warning"), message: Text(alertMessage), - dismissButton: .default(Text("Done")) - ) - } - } - - private func addCustomEndpoint() { - guard !newCustomEndpoint.isEmpty else { - alertMessage = "Please enter a valid endpoint." - showingAlert = true - return - } - - guard URL(string: newCustomEndpoint) != nil else { - alertMessage = "Please enter a valid endpoint url." - showingAlert = true - return - } - - if !customEndpoints.contains(newCustomEndpoint) { - customEndpoints.append(newCustomEndpoint) - endpoint = newCustomEndpoint - newCustomEndpoint = "" - } else { - alertMessage = "The endpoint already exists." - showingAlert = true - } - } - - private func removeCustomEndpoint(_ endpoint: String) { - customEndpoints.removeAll { $0 == endpoint } - if self.endpoint == endpoint { - self.endpoint = "https://huggingface.co" - } - } -} diff --git a/ChatMLX/Features/Settings/LocalModels/LocalModelItemView.swift b/ChatMLX/Features/Settings/LocalModels/LocalModelItemView.swift deleted file mode 100644 index e8fb1f6..0000000 --- a/ChatMLX/Features/Settings/LocalModels/LocalModelItemView.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// LocalModelItemView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/13. -// - -import SwiftUI - -struct LocalModelItemView: View { - @Binding var model: LocalModel - var onDelete: () -> Void - @State private var showingDeleteAlert = false - - var body: some View { - VStack { - HStack { - Text(model.name) - Spacer() - Button(action: { showingDeleteAlert = true }) { - Image(systemName: "trash") - .foregroundColor(.red) - } - .buttonStyle(.plain) - } - } - .padding() - .background(.black.opacity(0.3)) - .listRowSeparator(.hidden) - .clipShape(RoundedRectangle(cornerRadius: 10)) - .shadow(color: .black, radius: 2) - .alert("Confirm Deletion", isPresented: $showingDeleteAlert) { - Button("Cancel", role: .cancel) {} - Button("Delete", role: .destructive, action: onDelete) - } message: { - Text("Are you sure you want to delete '\(model.origin)'?") - } - } -} diff --git a/ChatMLX/Features/Settings/LocalModels/LocalModelsView.swift b/ChatMLX/Features/Settings/LocalModels/LocalModelsView.swift deleted file mode 100644 index 43e3159..0000000 --- a/ChatMLX/Features/Settings/LocalModels/LocalModelsView.swift +++ /dev/null @@ -1,136 +0,0 @@ -// -// LocalModelsView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/10. -// - -import Defaults -import SwiftUI - -struct LocalModelsView: View { - @State private var modelGroups: [LocalModelGroup] = [] - @Default(.defaultModel) var defaultModel - - @Environment(SettingsViewModel.self) var vm - - var body: some View { - List { - ForEach(modelGroups.indices, id: \.self) { groupIndex in - Section( - header: Text(modelGroups[groupIndex].name).font( - .title2.bold()) - ) { - ForEach(modelGroups[groupIndex].models.indices, id: \.self) { modelIndex in - LocalModelItemView( - model: $modelGroups[groupIndex].models[modelIndex], - onDelete: { - Task { - deleteModel( - at: IndexSet(integer: modelIndex), - from: groupIndex) - loadModels() - } - }) - } - .onDelete { offsets in - Task { - deleteModel(at: offsets, from: groupIndex) - loadModels() - } - } - } - } - } - .onAppear(perform: loadModels) - .scrollContentBackground(.hidden) - .listStyle(SidebarListStyle()) - .ultramanNavigationTitle("Models") - .ultramanToolbar { - Button(action: openModelsDirectory) { - Image(systemName: "folder") - } - .buttonStyle(.plain) - } - } - - private func loadModels() { - let fileManager = FileManager.default - let documentsURL = fileManager.urls( - for: .documentDirectory, in: .userDomainMask)[0] - let modelsURL = documentsURL.appendingPathComponent( - "huggingface/models") - - do { - let contents = try fileManager.contentsOfDirectory( - at: modelsURL, includingPropertiesForKeys: nil, - options: [.skipsHiddenFiles]) - var groups: [LocalModelGroup] = [] - - for groupURL in contents { - if groupURL.hasDirectoryPath { - let groupName = groupURL.lastPathComponent - var models: [LocalModel] = [] - - let modelContents = try fileManager.contentsOfDirectory( - at: groupURL, includingPropertiesForKeys: nil, - options: [.skipsHiddenFiles]) - - for modelURL in modelContents { - if modelURL.hasDirectoryPath { - let modelName = modelURL.lastPathComponent - models.append( - LocalModel( - group: groupName, - name: modelName, - url: modelURL) - ) - } - } - - groups.append( - LocalModelGroup(name: groupName, models: models)) - } - } - - if !groups.contains(where: { - $0.models.contains(where: { $0.origin == defaultModel }) - }) { - defaultModel = "" - } - - Task { @MainActor in - modelGroups = groups - } - } catch { - vm.throwError(error, title: "Load Models Failed") - } - } - - private func deleteModel(at offsets: IndexSet, from group: Int) { - let fileManager = FileManager.default - - for index in offsets { - let model = modelGroups[group].models[index] - do { - try fileManager.removeItem(at: model.url) - modelGroups[group].models.remove(at: index) - if defaultModel == model.origin { - defaultModel = "" - } - } catch { - vm.throwError(error, title: "Delete Model Failed") - } - } - } - - private func openModelsDirectory() { - let fileManager = FileManager.default - let documentsURL = fileManager.urls( - for: .documentDirectory, in: .userDomainMask)[0] - let modelsURL = documentsURL.appendingPathComponent( - "huggingface/models") - - NSWorkspace.shared.open(modelsURL) - } -} diff --git a/ChatMLX/Features/Settings/MLXCommunity/MLXCommunityItemView.swift b/ChatMLX/Features/Settings/MLXCommunity/MLXCommunityItemView.swift deleted file mode 100644 index 3f0801f..0000000 --- a/ChatMLX/Features/Settings/MLXCommunity/MLXCommunityItemView.swift +++ /dev/null @@ -1,85 +0,0 @@ -// -// MLXCommunityItemView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/11. -// - -import SwiftUI - -struct MLXCommunityItemView: View { - @Binding var model: RemoteModel - @Environment(SettingsViewModel.self) var settingsViewModel - - var body: some View { - VStack(alignment: .leading, spacing: 8) { - HStack { - Text(model.repoId.deletingPrefix("mlx-community/")) - .font(.headline) - .lineLimit(1) - - Spacer() - - Button(action: download) { - Image(systemName: "arrow.down.circle") - } - .buttonStyle(.borderless) - - Button(action: { - if let url = URL( - string: "https://huggingface.co/\(model.repoId)") - { - NSWorkspace.shared.open(url) - } - }) { - Image(systemName: "safari") - } - .buttonStyle(.borderless) - } - - HStack { - Label("\(model.downloads)", systemImage: "arrow.down.circle") - .font(.subheadline) - - Label("\(model.likes)", systemImage: "heart.fill") - .font(.subheadline) - .foregroundColor(.red.opacity(0.6)) - - Spacer() - - if let pipelineTag = model.pipelineTag { - Text(pipelineTag) - .font(.subheadline) - .padding(4) - .background(Color.blue.opacity(0.2)) - .cornerRadius(4) - } - } - - ScrollView(.horizontal, showsIndicators: false) { - HStack { - ForEach(model.tags, id: \.self) { tag in - Text(tag) - .font(.caption) - .padding(4) - .background(Color.gray.opacity(0.2)) - .cornerRadius(4) - } - } - } - } - .padding() - .background(.black.opacity(0.3)) - .listRowSeparator(.hidden) - .clipShape(RoundedRectangle(cornerRadius: 10)) - .shadow(color: .black, radius: 2) - } - - private func download() { - let task = DownloadTask(model.repoId) - task.start() - - settingsViewModel.tasks.append(task) - settingsViewModel.activeTabID = .downloadManager - } -} diff --git a/ChatMLX/Features/Settings/MLXCommunity/MLXCommunityView.swift b/ChatMLX/Features/Settings/MLXCommunity/MLXCommunityView.swift deleted file mode 100644 index 5c4127c..0000000 --- a/ChatMLX/Features/Settings/MLXCommunity/MLXCommunityView.swift +++ /dev/null @@ -1,198 +0,0 @@ -// -// MLXCommunityView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/11. -// - -import Alamofire -import Luminare -import SwiftUI - -struct MLXCommunityView: View { - @Environment(SettingsViewModel.self) var settingsViewModel - - @State private var searchQuery = "" - @State var isFetching = false - @State var next: String? - - @State var status: Status = .isLoading - - private let sessionManager: Session - - enum Status { - case isLoading - case idle - case error(String) - } - - init() { - let configuration = URLSessionConfiguration.default - configuration.requestCachePolicy = .returnCacheDataElseLoad - configuration.urlCache = URLCache( - memoryCapacity: 20 * 1024 * 1024, diskCapacity: 0) - - sessionManager = Session(configuration: configuration) - } - - var body: some View { - @Bindable var settingsViewModel = settingsViewModel - VStack { - LuminareSection { - UltramanTextField( - $searchQuery, placeholder: Text("Search...") - ) { - Task { - settingsViewModel.remoteModels = [] - await fetchModels(search: searchQuery) - } - } - - } - .padding(.top) - .padding(.horizontal) - - List { - ForEach($settingsViewModel.remoteModels) { model in - MLXCommunityItemView(model: model) - } - lastRowView - } - .scrollContentBackground(.hidden) - } - .onAppear { - Task { - await fetchModels() - } - } - .ultramanNavigationTitle("MLX Community") - .ultramanToolbar(alignment: .trailing) { - Button(action: { - Task { - settingsViewModel.remoteModels = [] - await fetchModels() - } - }) { - Image(systemName: "arrow.clockwise") - } - .disabled(isFetching) - .buttonStyle(.plain) - } - } - - @MainActor - @ViewBuilder - var lastRowView: some View { - ZStack(alignment: .center) { - switch status { - case .isLoading: - ProgressView() - case .idle: - EmptyView() - case .error(let error): - Text(error) - } - } - .frame(height: 50) - .frame(maxWidth: .infinity) - .onAppear { - Task { - await loadMoreModelsIfNeeded() - } - } - } - - func parseLinks(_ links: String?) -> [String: String] { - guard let links else { return [:] } - - var linkDict = [String: String]() - let linkComponents = links.split(separator: ",") - - for component in linkComponents { - let parts = component.split(separator: ";") - if parts.count == 2 { - let urlPart = parts[0].trimmingCharacters( - in: .whitespacesAndNewlines) - let relPart = parts[1].trimmingCharacters( - in: .whitespacesAndNewlines) - - let url = urlPart.trimmingCharacters( - in: CharacterSet(charactersIn: "<>")) - let rel = relPart.replacingOccurrences(of: "rel=", with: "") - .trimmingCharacters(in: CharacterSet(charactersIn: "\"")) - - linkDict[rel] = url - } - } - - return linkDict - } - - func fetchModels(search: String? = nil) async { - guard !isFetching else { return } - isFetching = true - status = .isLoading - - var urlComponents = URLComponents( - string: "https://huggingface.co/api/models")! - var queryItems: [URLQueryItem] = [ - URLQueryItem(name: "limit", value: "20"), - URLQueryItem(name: "author", value: "mlx-community"), - URLQueryItem(name: "sort", value: "downloads"), - URLQueryItem(name: "pipeline_tag", value: "text-generation"), - ] - - if let search { - queryItems.append(URLQueryItem(name: "search", value: search)) - } - - urlComponents.queryItems = queryItems - - guard let url = urlComponents.url else { return } - - sessionManager.request(url).validate().responseDecodable( - of: [RemoteModel].self - ) { response in - switch response.result { - case .success(let decodedResponse): - settingsViewModel.remoteModels = decodedResponse - if let links = response.response?.allHeaderFields["Link"] - as? String - { - next = parseLinks(links)["next"] - } - status = .idle - case .failure(let error): - logger.error("Failed to fetch models: \(error)") - status = .error(error.localizedDescription) - } - isFetching = false - } - } - - func loadMoreModelsIfNeeded() async { - guard !isFetching, let nextURL = URL(string: next ?? "") else { return } - isFetching = true - status = .isLoading - - sessionManager.request(nextURL).validate().responseDecodable( - of: [RemoteModel].self - ) { response in - switch response.result { - case .success(let decodedResponse): - settingsViewModel.remoteModels.append( - contentsOf: decodedResponse) - if let links = response.response?.allHeaderFields["Link"] - as? String - { - next = parseLinks(links)["next"] - } - status = .idle - case .failure(let error): - logger.error("Failed to fetch more models: \(error)") - status = .error(error.localizedDescription) - } - isFetching = false - } - } -} diff --git a/ChatMLX/Features/Settings/SettingsSidebarItemView.swift b/ChatMLX/Features/Settings/SettingsSidebarItemView.swift deleted file mode 100644 index 1c09dd1..0000000 --- a/ChatMLX/Features/Settings/SettingsSidebarItemView.swift +++ /dev/null @@ -1,78 +0,0 @@ -// -// SettingsSidebarItemView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/10. -// - -import SwiftUI - -struct SettingsSidebarItemView: View { - @Environment(SettingsViewModel.self) var settingsViewModel - - let tab: SettingsTab - - @State private var isHovering: Bool = false - @State private var isActive: Bool = false - @State private var showIndicator: Bool = false - - init(_ tab: SettingsTab) { - self.tab = tab - } - - var body: some View { - HStack(spacing: 8) { - tab.iconView() - - Text(LocalizedStringKey(tab.id.rawValue)) - - if showIndicator { - VStack { - Circle() - .foregroundStyle(.red) - .frame(width: 4, height: 4) - .padding(.top, 6) - .shadow(color: .red, radius: 4) - - Spacer() - } - } - - Spacer() - } - .padding(4) - .background { - if isActive || isHovering { - Rectangle().foregroundStyle(.quaternary.opacity(0.7)) - } - } - .clipShape(.rect(cornerRadius: 12)) - .overlay { - if isActive { - RoundedRectangle(cornerRadius: 12) - .strokeBorder(.quaternary, lineWidth: 1) - } - } - .onHover { isHovering = $0 } - .onAppear { - checkIfSelfIsActiveTab() - showIndicator = tab.showIndicator?(settingsViewModel) ?? false - } - .onChange(of: settingsViewModel.activeTabID) { _, _ in - checkIfSelfIsActiveTab() - } - .onChange(of: tab.showIndicator?(settingsViewModel) ?? false) { - _, newValue in - withAnimation { - showIndicator = newValue - } - } - .listRowSeparator(.hidden) - } - - func checkIfSelfIsActiveTab() { - withAnimation(.easeOut(duration: 0.1)) { - isActive = settingsViewModel.activeTabID == tab.id - } - } -} diff --git a/ChatMLX/Features/Settings/SettingsSidebarView.swift b/ChatMLX/Features/Settings/SettingsSidebarView.swift deleted file mode 100644 index ccbc8de..0000000 --- a/ChatMLX/Features/Settings/SettingsSidebarView.swift +++ /dev/null @@ -1,62 +0,0 @@ -// -// SettingsSidebarView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/10. -// - -import SwiftUI - -struct SettingsSidebarView: View { - @Environment(SettingsViewModel.self) var settingsViewModel - - let titlebarHeight: CGFloat = 50 - let groupSpacing: CGFloat = 4 - let itemPadding: CGFloat = 15 - let groupTitlePadding: CGFloat = 4 - let itemSpacing: CGFloat = 4 - - static let tabs: [SettingsTab] = [ - .init(.general, Image(systemName: "gearshape")), - .init(.defaultConversation, Image(systemName: "person.bubble")), - .init(.huggingFace, Image("huggingface")), - .init(.models, Image(systemName: "brain")), - .init(.mlxCommunity, Image("MLX")), - .init( - .downloadManager, Image(systemName: "arrow.down.circle"), - showIndicator: { $0.tasks.contains { $0.isDownloading } } - ), - .init(.experimentalFeatures, Image(systemName: "flask")), - .init(.about, Image(systemName: "info.circle")), - ] - - var body: some View { - @Bindable var settingsViewModel = settingsViewModel - VStack(alignment: .leading) { - Group { - Text("Settings") - .font(.title2) - .padding(.top, 50) - Text("Preferences and model settings") - .font(.subheadline) - .foregroundStyle(.white.opacity(0.5)) - } - .padding(.horizontal, itemPadding) - - List(selection: $settingsViewModel.activeTabID) { - ForEach(Self.tabs) { tab in - SettingsSidebarItemView(tab) - } - } - .scrollContentBackground(.hidden) - .listStyle(.plain) - - Spacer() - } - .background(.black.opacity(0.4)) - } -} - -#Preview { - SettingsView() -} diff --git a/ChatMLX/Features/Settings/SettingsView.swift b/ChatMLX/Features/Settings/SettingsView.swift deleted file mode 100644 index 76603d0..0000000 --- a/ChatMLX/Features/Settings/SettingsView.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// SettingsView.swift -// ChatMLX -// -// Created by John Mai on 2024/8/10. -// - -import SwiftUI - -struct SettingsView: View { - @Environment(SettingsViewModel.self) var vm - - var body: some View { - @Bindable var vm = vm - - UltramanNavigationSplitView(sidebarWidth: 220) { - SettingsSidebarView() - } detail: { - Group { - switch vm.activeTabID { - case .general: - GeneralView() - case .defaultConversation: - DefaultConversationView() - case .huggingFace: - HuggingFaceView() - case .models: - LocalModelsView() - case .downloadManager: - DownloadManagerView() - case .mlxCommunity: - MLXCommunityView() - case .experimentalFeatures: - ExperimentalFeaturesView() - case .about: - AboutView() - } - } - } - .ultramanMinimalistWindowStyle() - .foregroundColor(.white) - } -} diff --git a/ChatMLX/Features/Settings/SettingsViewModel.swift b/ChatMLX/Features/Settings/SettingsViewModel.swift deleted file mode 100644 index ce26612..0000000 --- a/ChatMLX/Features/Settings/SettingsViewModel.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// SettingsViewModel.swift -// ChatMLX -// -// Created by John Mai on 2024/10/3. -// -import SwiftUI - -@Observable -class SettingsViewModel { - var tasks: [DownloadTask] = [] - var sidebarWidth: CGFloat = 250 - var activeTabID: SettingsTab.ID = .general - var remoteModels: [RemoteModel] = [] - - var error: Error? - var errorTitle: String? - var showErrorAlert = false - - func throwError(_ error: Error, title: String? = nil) { - logger.error("\(error.localizedDescription)") - self.error = error - errorTitle = title - showErrorAlert = true - } - -} diff --git a/ChatMLX/Localizable.xcstrings b/ChatMLX/Localizable.xcstrings index 201d43e..acf7a40 100644 --- a/ChatMLX/Localizable.xcstrings +++ b/ChatMLX/Localizable.xcstrings @@ -2,6 +2,7 @@ "sourceLanguage" : "en", "strings" : { "" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -219,19 +220,19 @@ "value" : "" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "" @@ -240,6 +241,7 @@ } }, "%.2f" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -457,19 +459,19 @@ "value" : "%.2f" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "%.2f" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "%.2f" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "%.2f" @@ -478,6 +480,7 @@ } }, "%.2f%%" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -695,19 +698,19 @@ "value" : "%.2f%%" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "%.2f%%" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "%.2f%%" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "%.2f%%" @@ -716,6 +719,7 @@ } }, "%d" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -933,19 +937,19 @@ "value" : "%d" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "%d" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "%d" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "%d" @@ -954,6 +958,7 @@ } }, "%lld" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -1171,19 +1176,19 @@ "value" : "%lld" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "%lld" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "%lld" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "%lld" @@ -1192,6 +1197,7 @@ } }, "%lld / %lld" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -1415,19 +1421,19 @@ "value" : "%1$lld / %2$lld" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "%1$lld / %2$lld" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "%1$lld / %2$lld" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "%1$lld / %2$lld" @@ -1436,6 +1442,7 @@ } }, "%lldM" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -1653,19 +1660,19 @@ "value" : "%lldM" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "%lldM" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "%lldM" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "%lldM" @@ -1674,6 +1681,7 @@ } }, "%lldMB" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -1891,19 +1899,19 @@ "value" : "%lldMB" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "%lldMB" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "%lldMB" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "%lldMB" @@ -2130,19 +2138,19 @@ "value" : "Giới thiệu" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "關於" + "value" : "关于" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "关于" + "value" : "關於" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "關於" @@ -2151,6 +2159,7 @@ } }, "Apple Intelligence Effect" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -2368,27 +2377,28 @@ "value" : "Hiệu ứng Trí tuệ của Apple" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "Apple 智能效果" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "Apple 智能效果" + "value" : "Apple 智能效應" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "Apple 智能效應" + "value" : "Apple 智能效果" } } } }, "Are you sure you want to delete '%@'?" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -2606,19 +2616,19 @@ "value" : "Bạn có chắc chắn muốn xóa '%@' không?" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "您確定要刪除「%@」嗎?" + "value" : "您确定要删除'%@'吗?" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "您确定要删除'%@'吗?" + "value" : "您確定要刪除「%@」嗎?" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "您確定要刪除「%@」嗎?" @@ -2627,6 +2637,7 @@ } }, "Blur" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -2844,19 +2855,19 @@ "value" : "Làm mờ" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "模糊" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "模糊" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "模糊" @@ -2865,6 +2876,7 @@ } }, "Cancel" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -3082,19 +3094,19 @@ "value" : "Hủy" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "取消" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "取消" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "取消" @@ -3103,6 +3115,7 @@ } }, "ChatMLX" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -3320,19 +3333,19 @@ "value" : "ChatMLX" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "ChatMLX" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "ChatMLX" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "ChatMLX" @@ -3559,19 +3572,19 @@ "value" : "Kiểm tra cập nhật" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "檢查更新" + "value" : "检查更新" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "检查更新" + "value" : "檢查更新" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "檢查更新" @@ -3798,12 +3811,6 @@ "value" : "Đang kiểm tra..." } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "正在檢查..." - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3815,6 +3822,12 @@ "state" : "translated", "value" : "正在檢查" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "正在檢查..." + } } } }, @@ -4037,19 +4050,19 @@ "value" : "Xóa" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "清除" + "value" : "清空" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "清空" + "value" : "清除" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "清除" @@ -4058,6 +4071,7 @@ } }, "Clear All Conversations" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -4275,19 +4289,19 @@ "value" : "Xóa Tất Cả Cuộc Trò Chuyện" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "清除所有對話" + "value" : "清除所有对话" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "清除所有对话" + "value" : "清除所有對話" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "清除所有對話" @@ -4296,6 +4310,7 @@ } }, "Color" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -4513,19 +4528,19 @@ "value" : "Màu sắc" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "顏色" + "value" : "颜色" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "颜色" + "value" : "顏色" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "顏色" @@ -4534,6 +4549,7 @@ } }, "Confirm Deletion" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -4751,19 +4767,19 @@ "value" : "Xác nhận Xóa" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "確認刪除" + "value" : "确认删除" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "确认删除" + "value" : "確認刪除" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "確認刪除" @@ -4772,6 +4788,7 @@ } }, "Conversation Title" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -4989,19 +5006,19 @@ "value" : "Tiêu đề cuộc trò chuyện" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "對話標題" + "value" : "对话标题" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "对话标题" + "value" : "對話標題" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "對話標題" @@ -5010,6 +5027,7 @@ } }, "Copy" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -5227,12 +5245,6 @@ "value" : "Sao chép" } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "複製" - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -5244,10 +5256,17 @@ "state" : "translated", "value" : "拷貝" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "複製" + } } } }, "Custom Endpoint" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -5465,12 +5484,6 @@ "value" : "Điểm Kết Thúc Tùy Chỉnh" } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "自訂端點" - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -5482,10 +5495,17 @@ "state" : "translated", "value" : "自定義端點" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "自訂端點" + } } } }, "Custom Hugging Face Endpoint" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -5703,12 +5723,6 @@ "value" : "Điểm cuối Custom Hugging Face" } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "自定 Hugging Face 端點" - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -5720,10 +5734,17 @@ "state" : "translated", "value" : "自訂 Hugging Face 端點" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "自定 Hugging Face 端點" + } } } }, "Default Conversation" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -5941,12 +5962,6 @@ "value" : "Đoạn hội thoại mặc định" } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "默認對話" - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -5958,10 +5973,17 @@ "state" : "translated", "value" : "預設對話" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "默認對話" + } } } }, "Default conversation title" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -6179,19 +6201,19 @@ "value" : "Tiêu đề cuộc trò chuyện mặc định" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "預設對話標題" + "value" : "默认对话标题" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "默认对话标题" + "value" : "預設對話標題" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "預設對話標題" @@ -6200,6 +6222,7 @@ } }, "Delete" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -6417,19 +6440,19 @@ "value" : "Xóa" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "刪除" + "value" : "删除" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "删除" + "value" : "刪除" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "刪除" @@ -6438,6 +6461,7 @@ } }, "Display Mode" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -6655,19 +6679,19 @@ "value" : "Chế độ hiển thị" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "顯示模式" + "value" : "显示模式" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "显示模式" + "value" : "顯示模式" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "顯示模式" @@ -6894,19 +6918,19 @@ "value" : "Xong" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "完成" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "完成" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "完成" @@ -7133,19 +7157,19 @@ "value" : "Trình quản lý Tải xuống" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "下載管理器" + "value" : "下载管理" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "下载管理" + "value" : "下載管理器" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "下載管理器" @@ -7154,6 +7178,7 @@ } }, "Endpoint" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -7371,19 +7396,19 @@ "value" : "Điểm cuối" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "端點" + "value" : "节点" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "节点" + "value" : "端點" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "端點" @@ -7392,6 +7417,7 @@ } }, "Enter Full Screen" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -7609,12 +7635,6 @@ "value" : "Toàn Màn Hình" } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "全螢幕模式" - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7626,10 +7646,17 @@ "state" : "translated", "value" : "進入全螢幕模式" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "全螢幕模式" + } } } }, "Enter your Hugging Face token" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -7847,12 +7874,6 @@ "value" : "Nhập mã thông báo Hugging Face của bạn" } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "輸入你的 Hugging Face 令牌" - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -7864,10 +7885,17 @@ "state" : "translated", "value" : "輸入您的 Hugging Face 令牌" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "輸入你的 Hugging Face 令牌" + } } } }, "Exit Full Screen" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -8085,12 +8113,6 @@ "value" : "Thoát Chế Độ Toàn Màn Hình" } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "退出全屏幕" - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8102,10 +8124,17 @@ "state" : "translated", "value" : "退出全螢幕" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "退出全屏幕" + } } } }, "Experimental Features" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -8323,19 +8352,19 @@ "value" : "Tính Năng Thử Nghiệm" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "實驗性功能" + "value" : "实验性功能" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "实验性功能" + "value" : "實驗性功能" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", "value" : "實驗性功能" @@ -8344,6 +8373,7 @@ } }, "Experimental features may have performance limitations. Features and programmatic interfaces are subject to change at any time." : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -8561,12 +8591,6 @@ "value" : "Các tính năng thử nghiệm có thể có những hạn chế về hiệu suất. Các tính năng và giao diện lập trình có thể thay đổi bất kỳ lúc nào." } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "實驗功能可能有性能限制。功能和編程接口可能會隨時更改。" - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8578,10 +8602,17 @@ "state" : "translated", "value" : "實驗功能可能有性能限制。功能和程式介面可能隨時更改。" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "實驗功能可能有性能限制。功能和編程接口可能會隨時更改。" + } } } }, "Feedback" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { @@ -8799,12 +8830,6 @@ "value" : "Phản hồi" } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "意見反饋" - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -8816,4167 +8841,4187 @@ "state" : "translated", "value" : "意見回饋" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "意見反饋" + } } } }, - "GPU Cache Limit" : { + "General" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "حد ذاكرة التخزين المؤقت لوحدة معالجة الرسوميات" + "value" : "عام" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Límit de memòria cau de la GPU" + "value" : "General" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Limit pro mezipaměť GPU" + "value" : "Obecné" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Begrænsning for GPU-cache" + "value" : "Generelt" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "GPU-Cache-Grenze" + "value" : "Allgemein" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Όριο προσωρινής μνήμης GPU" + "value" : "Γενικά" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "GPU Cache Limit" + "value" : "General" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "GPU Cache Limit" + "value" : "General" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "GPU Cache Limit" + "value" : "General" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Límite de caché de GPU" + "value" : "General" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Límite de Caché de GPU" + "value" : "General" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "GPU-välimuistin raja" + "value" : "Yleiset" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Limite de cache GPU" + "value" : "Général" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Limite de cache GPU" + "value" : "Général" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "מגבלת המטמון של ה-GPU" + "value" : "כללי" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "GPU कैश सीमा" + "value" : "सामान्य" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Ograničenje GPU predmemorije" + "value" : "Općenito" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "GPU gyorsítótár korlátja" + "value" : "Általános" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Batas Cache GPU" + "value" : "Umum" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Limite cache GPU" + "value" : "Generali" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "GPUキャッシュ制限" + "value" : "一般" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "GPU 캐시 제한" + "value" : "일반" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Had Cache GPU" + "value" : "Umum" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "GPU-hurtigbuffergrense" + "value" : "Generelt" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "GPU-cachelimiet" + "value" : "Algemeen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Limit bufora GPU" + "value" : "Ogólne" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Limite de Cache da GPU" + "value" : "Geral" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Limite da Cache da GPU" + "value" : "Geral" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Limită cache GPU" + "value" : "General" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ограничение кэша GPU" + "value" : "Основные настройки" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Limit vyrovnávacej pamäte GPU" + "value" : "Všeobecné" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Gräns för GPU-cache" + "value" : "Allmänt" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ขีดจำกัดแคช GPU" + "value" : "ทั่วไป" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "GPU Önbellek Limiti" + "value" : "Genel" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Граничення кешу GPU" + "value" : "Основні" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Giới hạn bộ nhớ đệm GPU" + "value" : "Chung" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "GPU 緩存限制" + "value" : "通用" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "GPU缓存限制" + "value" : "概述" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "GPU 快取上限" + "value" : "一般" } } } }, - "General" : { - "extractionState" : "manual", + "Generate Time" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "عام" + "value" : "توليد الوقت" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "Genera hora" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Obecné" + "value" : "Vygenerovat čas" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Generelt" + "value" : "Generer tid" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Allgemein" + "value" : "Zeit generieren" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Γενικά" + "value" : "Δημιουργία Χρόνου" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "Generate Time" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "Generate Time" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "Generate Time" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "Generar hora" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "Generar hora" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Yleiset" + "value" : "Luo aika" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Général" + "value" : "Générer l'heure" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Général" + "value" : "Générer l'heure" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "כללי" + "value" : "צור זמן" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "सामान्य" + "value" : "समय उत्पन्न करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Općenito" + "value" : "Generiraj vrijeme" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Általános" + "value" : "Idő létrehozása" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Umum" + "value" : "Hasilkan Waktu" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Generali" + "value" : "Genera ora" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "一般" + "value" : "時間を生成" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "일반" + "value" : "시간 생성" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Umum" + "value" : "Jana Masa" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Generelt" + "value" : "Generer tid" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Algemeen" + "value" : "Tijd genereren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ogólne" + "value" : "Generuj czas" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Geral" + "value" : "Gerar Horário" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Geral" + "value" : "Gerar Tempo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "Generează timp" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Основные настройки" + "value" : "Сгенерировать время" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Všeobecné" + "value" : "Generovať čas" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Allmänt" + "value" : "Generera tid" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ทั่วไป" + "value" : "สร้างเวลา" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Genel" + "value" : "Süre Oluştur" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Основні" + "value" : "Згенерувати час" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Chung" - } - }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "一般" + "value" : "Tạo thời gian" } }, "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "通用" + "value" : "生成时间" } }, "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "概述" + "value" : "生成時間" + } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "生成時間" } } } }, - "Generate Time" : { + "Generate Tokens/second" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "توليد الوقت" + "value" : "توليد الرموز/الثانية" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Genera hora" + "value" : "Genera tokens/segon" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Vygenerovat čas" + "value" : "Generovat tokeny/sekundu" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Generer tid" + "value" : "Generer Tokens/sekund" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zeit generieren" + "value" : "Token pro Sekunde generieren" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Δημιουργία Χρόνου" + "value" : "Δημιουργία διακριτικών/δευτερόλεπτο" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Generate Time" + "value" : "Generate Tokens/second" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Generate Time" + "value" : "Generate Tokens/second" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Generate Time" + "value" : "Generate Tokens per second" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Generar hora" + "value" : "Generar tokens/segundo" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Generar hora" + "value" : "Generar tokens/segundo" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Luo aika" + "value" : "Luo merkkejä/sekunti" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Générer l'heure" + "value" : "Générer des jetons/seconde" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Générer l'heure" + "value" : "Générer des jetons/seconde" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "צור זמן" + "value" : "יצירת סמלים/שנייה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "समय उत्पन्न करें" + "value" : "प्रती/सेकंड टोकन जनरेट करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Generiraj vrijeme" + "value" : "Generiraj toka/sekundi" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Idő létrehozása" + "value" : "Tokenek létrehozása/másodperc" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Hasilkan Waktu" + "value" : "Hasilkan Token/detik" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Genera ora" + "value" : "Genera token/secondo" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "時間を生成" + "value" : "トークン/秒を生成" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "시간 생성" + "value" : "초당 토큰 생성" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Jana Masa" + "value" : "Jana Token/saat" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Generer tid" + "value" : "Generer token/sekund" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Tijd genereren" + "value" : "Genereer Tokens/seconde" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Generuj czas" + "value" : "Generuj Tokeny/sekundę" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Gerar Horário" + "value" : "Gerar Tokens/segundo" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Gerar Tempo" + "value" : "Gerar Tokens/segundo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Generează timp" + "value" : "Generează jetoane/secundă" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сгенерировать время" + "value" : "Создать токены/секунда" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Generovať čas" + "value" : "Generovať tokeny/sekundu" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Generera tid" + "value" : "Generera tokens/sekund" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "สร้างเวลา" + "value" : "สร้างโทเคน/วินาที" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Süre Oluştur" + "value" : "Saniye Başı Jeton Üret" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Згенерувати час" + "value" : "Генерувати токени/секунда" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Tạo thời gian" + "value" : "Tạo mã mỗi giây" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "生成時間" + "value" : "生成令牌/秒" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "生成时间" + "value" : "每秒生成 Token" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "生成時間" + "value" : "每秒產生代幣" } } } }, - "Generate Tokens/second" : { + "GitHub" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "توليد الرموز/الثانية" + "value" : "GitHub" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Genera tokens/segon" + "value" : "GitHub" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Generovat tokeny/sekundu" + "value" : "GitHub" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Generer Tokens/sekund" + "value" : "GitHub" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Token pro Sekunde generieren" + "value" : "GitHub" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Δημιουργία διακριτικών/δευτερόλεπτο" + "value" : "GitHub" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Generate Tokens/second" + "value" : "GitHub" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Generate Tokens/second" + "value" : "GitHub" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Generate Tokens per second" + "value" : "GitHub" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Generar tokens/segundo" + "value" : "GitHub" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Generar tokens/segundo" + "value" : "GitHub" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Luo merkkejä/sekunti" + "value" : "GitHub" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Générer des jetons/seconde" + "value" : "GitHub" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Générer des jetons/seconde" + "value" : "GitHub" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "יצירת סמלים/שנייה" + "value" : "GitHub" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "प्रती/सेकंड टोकन जनरेट करें" + "value" : "GitHub" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Generiraj toka/sekundi" + "value" : "GitHub" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Tokenek létrehozása/másodperc" + "value" : "GitHub" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Hasilkan Token/detik" + "value" : "GitHub" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Genera token/secondo" + "value" : "GitHub" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "トークン/秒を生成" + "value" : "GitHub" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "초당 토큰 생성" + "value" : "GitHub" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Jana Token/saat" + "value" : "GitHub" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Generer token/sekund" + "value" : "GitHub" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Genereer Tokens/seconde" + "value" : "GitHub" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Generuj Tokeny/sekundę" + "value" : "GitHub" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Gerar Tokens/segundo" + "value" : "GitHub" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Gerar Tokens/segundo" + "value" : "GitHub" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Generează jetoane/secundă" + "value" : "GitHub" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Создать токены/секунда" + "value" : "GitHub" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Generovať tokeny/sekundu" + "value" : "GitHub" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Generera tokens/sekund" + "value" : "GitHub" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "สร้างโทเคน/วินาที" + "value" : "GitHub" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Saniye Başı Jeton Üret" + "value" : "GitHub" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Генерувати токени/секунда" + "value" : "GitHub" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Tạo mã mỗi giây" + "value" : "GitHub" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "每秒產生代幣" + "value" : "GitHub" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "生成令牌/秒" + "value" : "GitHub" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "每秒生成 Token" + "value" : "GitHub" } } } }, - "GitHub" : { + "GPU Cache Limit" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "حد ذاكرة التخزين المؤقت لوحدة معالجة الرسوميات" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Límit de memòria cau de la GPU" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Limit pro mezipaměť GPU" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Begrænsning for GPU-cache" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU-Cache-Grenze" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Όριο προσωρινής μνήμης GPU" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU Cache Limit" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU Cache Limit" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU Cache Limit" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Límite de caché de GPU" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Límite de Caché de GPU" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU-välimuistin raja" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Limite de cache GPU" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Limite de cache GPU" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "מגבלת המטמון של ה-GPU" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU कैश सीमा" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Ograničenje GPU predmemorije" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU gyorsítótár korlátja" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Batas Cache GPU" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Limite cache GPU" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPUキャッシュ制限" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU 캐시 제한" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Had Cache GPU" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU-hurtigbuffergrense" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU-cachelimiet" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Limit bufora GPU" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Limite de Cache da GPU" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Limite da Cache da GPU" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Limită cache GPU" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Ограничение кэша GPU" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Limit vyrovnávacej pamäte GPU" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Gräns för GPU-cache" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "ขีดจำกัดแคช GPU" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU Önbellek Limiti" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Граничення кешу GPU" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "Giới hạn bộ nhớ đệm GPU" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU缓存限制" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU 快取上限" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "GPU 緩存限制" } } } }, - "Hugging Face" : { + "https://hf-mirror.com" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "https://hf-mirror.com" } } } }, - "Hugging Face Endpoint" : { + "https://huggingface.co" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "نقطة النهاية Hugging Face" + "value" : "https://huggingface.co" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Punt final de Hugging Face" + "value" : "https://huggingface.co" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face koncový bod" + "value" : "https://huggingface.co" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-endepunkt" + "value" : "https://huggingface.co" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-Endpunkt" + "value" : "https://huggingface.co" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint Hugging Face" + "value" : "https://huggingface.co" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Endpoint" + "value" : "https://huggingface.co" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Endpoint" + "value" : "https://huggingface.co" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Endpoint" + "value" : "https://huggingface.co" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Punto Final de Hugging Face" + "value" : "https://huggingface.co" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Punto de conexión de Hugging Face" + "value" : "https://huggingface.co" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face -päätepiste" + "value" : "https://huggingface.co" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Point de terminaison Hugging Face" + "value" : "https://huggingface.co" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Point de terminaison Hugging Face" + "value" : "https://huggingface.co" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "נקודת קצה של Hugging Face" + "value" : "https://huggingface.co" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face एंडपॉइंट" + "value" : "https://huggingface.co" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face završna točka" + "value" : "https://huggingface.co" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face végpont" + "value" : "https://huggingface.co" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Endpoint" + "value" : "https://huggingface.co" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint di Hugging Face" + "value" : "https://huggingface.co" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face エンドポイント" + "value" : "https://huggingface.co" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "허깅 페이스 엔드포인트" + "value" : "https://huggingface.co" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Titik Akhir Hugging Face" + "value" : "https://huggingface.co" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-endepunkt" + "value" : "https://huggingface.co" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-eindpunt" + "value" : "https://huggingface.co" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Endpoint" + "value" : "https://huggingface.co" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint do Hugging Face" + "value" : "https://huggingface.co" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Ponto Final Hugging Face" + "value" : "https://huggingface.co" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint Hugging Face" + "value" : "https://huggingface.co" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Конечная точка Hugging Face" + "value" : "https://huggingface.co" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face endpoint" + "value" : "https://huggingface.co" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-slutpunkt" + "value" : "https://huggingface.co" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Endpoint" + "value" : "https://huggingface.co" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Uç Noktası" + "value" : "https://huggingface.co" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Кінцева точка Hugging Face" + "value" : "https://huggingface.co" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Điểm cuối Hugging Face" + "value" : "https://huggingface.co" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 端點" + "value" : "https://huggingface.co" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 节点" + "value" : "https://huggingface.co" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 端點" + "value" : "https://huggingface.co" } } } }, - "Hugging Face Repo Id" : { - "extractionState" : "manual", + "Hugging Face" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "معرّف مستودع Hugging Face" + "value" : "Hugging Face" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Id de Repositori de Hugging Face" + "value" : "Hugging Face" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "ID úložiště Hugging Face" + "value" : "Hugging Face" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo-id" + "value" : "Hugging Face" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo-ID" + "value" : "Hugging Face" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αναγνωριστικό Αποθετηρίου Hugging Face" + "value" : "Hugging Face" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo ID" + "value" : "Hugging Face" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo ID" + "value" : "Hugging Face" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo Id" + "value" : "Hugging Face" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "ID del repositorio de Hugging Face" + "value" : "Hugging Face" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "ID del repo de Hugging Face" + "value" : "Hugging Face" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face -repojen tunnus" + "value" : "Hugging Face" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "ID du dépôt Hugging Face" + "value" : "Hugging Face" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "ID du dépôt Hugging Face" + "value" : "Hugging Face" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face מזהה מאגר" + "value" : "Hugging Face" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face रेपो आईडी" + "value" : "Hugging Face" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "ID spremišta Hugging Face" + "value" : "Hugging Face" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face tárolóazonosító" + "value" : "Hugging Face" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "ID Repo Hugging Face" + "value" : "Hugging Face" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "ID Repository Hugging Face" + "value" : "Hugging Face" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face リポジトリID" + "value" : "Hugging Face" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 저장소 ID" + "value" : "Hugging Face" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "ID Repo Hugging Face" + "value" : "Hugging Face" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo-ID" + "value" : "Hugging Face" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-repo-ID" + "value" : "Hugging Face" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Identyfikator repozytorium Hugging Face" + "value" : "Hugging Face" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "ID do Repositório Hugging Face" + "value" : "Hugging Face" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "ID do Repositório Hugging Face" + "value" : "Hugging Face" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "ID-ul depozitului Hugging Face" + "value" : "Hugging Face" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Идентификатор репозитория Hugging Face" + "value" : "Hugging Face" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo ID" + "value" : "Hugging Face" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo-ID" + "value" : "Hugging Face" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo Id" + "value" : "Hugging Face" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo Kimliği" + "value" : "Hugging Face" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Ідентифікатор репозиторію Hugging Face" + "value" : "Hugging Face" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo Id" + "value" : "Hugging Face" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo ID" + "value" : "Hugging Face" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 仓库 ID" + "value" : "Hugging Face" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 儲存庫 ID" + "value" : "Hugging Face" } } } }, - "Hugging Face Token" : { + "Hugging Face Endpoint" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "كود Hugging Face" + "value" : "نقطة النهاية Hugging Face" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Token de Hugging Face" + "value" : "Punt final de Hugging Face" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Token Hugging Face" + "value" : "Hugging Face koncový bod" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-token" + "value" : "Hugging Face-endepunkt" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging-Face-Token" + "value" : "Hugging Face-Endpunkt" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Διακριτικό Hugging Face" + "value" : "Endpoint Hugging Face" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Hugging Face Endpoint" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Hugging Face Endpoint" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Hugging Face Endpoint" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Token de Hugging Face" + "value" : "Punto Final de Hugging Face" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Token de Hugging Face" + "value" : "Punto de conexión de Hugging Face" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face -avain" + "value" : "Hugging Face -päätepiste" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Jeton Hugging Face" + "value" : "Point de terminaison Hugging Face" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Jeton Hugging Face" + "value" : "Point de terminaison Hugging Face" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אסימון Hugging Face" + "value" : "נקודת קצה של Hugging Face" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face टोकन" + "value" : "Hugging Face एंडपॉइंट" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face token" + "value" : "Hugging Face završna točka" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face token" + "value" : "Hugging Face végpont" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Hugging Face Endpoint" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Token di Hugging Face" + "value" : "Endpoint di Hugging Face" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face トークン" + "value" : "Hugging Face エンドポイント" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "허깅 페이스 토큰" + "value" : "허깅 페이스 엔드포인트" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Token Hugging Face" + "value" : "Titik Akhir Hugging Face" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-token" + "value" : "Hugging Face-endepunkt" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-token" + "value" : "Hugging Face-eindpunt" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Token Hugging Face" + "value" : "Hugging Face Endpoint" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Token do Hugging Face" + "value" : "Endpoint do Hugging Face" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Token do Hugging Face" + "value" : "Ponto Final Hugging Face" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Token Hugging Face" + "value" : "Endpoint Hugging Face" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Токен Hugging Face" + "value" : "Конечная точка Hugging Face" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Hugging Face endpoint" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-token" + "value" : "Hugging Face-slutpunkt" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "โทเค็น Hugging Face" + "value" : "Hugging Face Endpoint" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Hugging Face Uç Noktası" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Токен Hugging Face" + "value" : "Кінцева точка Hugging Face" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Mã Hugging Face" + "value" : "Điểm cuối Hugging Face" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Hugging Face 节点" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 令牌" + "value" : "Hugging Face 端點" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Hugging Face 端點" } } } }, - "Language" : { + "Hugging Face Repo Id" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "اللغة" + "value" : "معرّف مستودع Hugging Face" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Idioma" + "value" : "Id de Repositori de Hugging Face" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Jazyk" + "value" : "ID úložiště Hugging Face" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Sprog" + "value" : "Hugging Face Repo-id" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sprache" + "value" : "Hugging Face Repo-ID" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Γλώσσα" + "value" : "Αναγνωριστικό Αποθετηρίου Hugging Face" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Language" + "value" : "Hugging Face Repo ID" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Language" + "value" : "Hugging Face Repo ID" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Language" + "value" : "Hugging Face Repo Id" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Idioma" + "value" : "ID del repositorio de Hugging Face" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Idioma" + "value" : "ID del repo de Hugging Face" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Kieli" + "value" : "Hugging Face -repojen tunnus" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Langue" + "value" : "ID du dépôt Hugging Face" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Langue" + "value" : "ID du dépôt Hugging Face" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "שפה" + "value" : "Hugging Face מזהה מאגר" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "भाषा" + "value" : "Hugging Face रेपो आईडी" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Jezik" + "value" : "ID spremišta Hugging Face" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Nyelv" + "value" : "Hugging Face tárolóazonosító" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Bahasa" + "value" : "ID Repo Hugging Face" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Lingua" + "value" : "ID Repository Hugging Face" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "言語" + "value" : "Hugging Face リポジトリID" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "언어" + "value" : "Hugging Face 저장소 ID" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Bahasa" + "value" : "ID Repo Hugging Face" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Språk" + "value" : "Hugging Face Repo-ID" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Taal" + "value" : "Hugging Face-repo-ID" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Język" + "value" : "Identyfikator repozytorium Hugging Face" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Idioma" + "value" : "ID do Repositório Hugging Face" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Idioma" + "value" : "ID do Repositório Hugging Face" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Limbă" + "value" : "ID-ul depozitului Hugging Face" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Язык" + "value" : "Идентификатор репозитория Hugging Face" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Jazyk" + "value" : "Hugging Face Repo ID" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Språk" + "value" : "Hugging Face Repo-ID" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ภาษา" + "value" : "Hugging Face Repo Id" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Dil" + "value" : "Hugging Face Repo Kimliği" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Мова" + "value" : "Ідентифікатор репозиторію Hugging Face" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Ngôn ngữ" + "value" : "Hugging Face Repo Id" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "語言" + "value" : "Hugging Face 仓库 ID" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "语言" + "value" : "Hugging Face 儲存庫 ID" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "語言" + "value" : "Hugging Face Repo ID" } } } }, - "MLX Community" : { - "extractionState" : "manual", + "Hugging Face Token" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "مجتمع MLX" + "value" : "كود Hugging Face" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Comunitat MLX" + "value" : "Token de Hugging Face" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Komunita MLX" + "value" : "Token Hugging Face" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "MLX-fællesskab" + "value" : "Hugging Face-token" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Community" + "value" : "Hugging-Face-Token" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Κοινότητα MLX" + "value" : "Διακριτικό Hugging Face" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Community" + "value" : "Hugging Face Token" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Community" + "value" : "Hugging Face Token" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Community" + "value" : "Hugging Face Token" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Comunidad MLX" + "value" : "Token de Hugging Face" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Comunidad MLX" + "value" : "Token de Hugging Face" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "MLX-yhteisö" + "value" : "Hugging Face -avain" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Communauté MLX" + "value" : "Jeton Hugging Face" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Communauté MLX" + "value" : "Jeton Hugging Face" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "קהילת MLX" + "value" : "אסימון Hugging Face" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "MLX समुदाय" + "value" : "Hugging Face टोकन" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Zajednica MLX" + "value" : "Hugging Face token" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Közösség" + "value" : "Hugging Face token" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Komunitas MLX" + "value" : "Hugging Face Token" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Community MLX" + "value" : "Token di Hugging Face" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "MLXコミュニティ" + "value" : "Hugging Face トークン" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "MLX 커뮤니티" + "value" : "허깅 페이스 토큰" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Komuniti MLX" + "value" : "Token Hugging Face" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "MLX-fellesskap" + "value" : "Hugging Face-token" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "MLX-community" + "value" : "Hugging Face-token" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Społeczność MLX" + "value" : "Token Hugging Face" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Comunidade MLX" + "value" : "Token do Hugging Face" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Comunidade" + "value" : "Token do Hugging Face" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Comunitatea MLX" + "value" : "Token Hugging Face" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Сообщество" + "value" : "Токен Hugging Face" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Komunita MLX" + "value" : "Hugging Face Token" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "MLX-community" + "value" : "Hugging Face-token" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ชุมชน MLX" + "value" : "โทเค็น Hugging Face" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Topluluğu" + "value" : "Hugging Face Token" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Спільнота MLX" + "value" : "Токен Hugging Face" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Cộng đồng MLX" + "value" : "Mã Hugging Face" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "MLX 社群" + "value" : "Hugging Face 令牌" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "MLX 社区" + "value" : "Hugging Face Token" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "MLX 社群" + "value" : "Hugging Face Token" } } } }, - "Max Length" : { + "Language" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الحد الأقصى لطول" + "value" : "اللغة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Longitud màxima" + "value" : "Idioma" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Maximální délka" + "value" : "Jazyk" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Maks. længde" + "value" : "Sprog" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Maximale Länge" + "value" : "Sprache" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Μέγιστο μήκος" + "value" : "Γλώσσα" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Max Length" + "value" : "Language" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Max Length" + "value" : "Language" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Max Length" + "value" : "Language" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Longitud máxima" + "value" : "Idioma" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Longitud Máxima" + "value" : "Idioma" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Enimmäispituus" + "value" : "Kieli" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Longueur maximale" + "value" : "Langue" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Longueur maximale" + "value" : "Langue" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אורך מרבי" + "value" : "שפה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "अधिकतम लंबाई" + "value" : "भाषा" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Maksimalna duljina" + "value" : "Jezik" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Maximális hosszasság" + "value" : "Nyelv" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Panjang Maksimum" + "value" : "Bahasa" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Lunghezza massima" + "value" : "Lingua" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "最大長" + "value" : "言語" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "최대 길이" + "value" : "언어" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Panjang Maksimum" + "value" : "Bahasa" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Maks lengde" + "value" : "Språk" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Maximale lengte" + "value" : "Taal" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Maksymalna długość" + "value" : "Język" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Comprimento Máximo" + "value" : "Idioma" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Comprimento Máximo" + "value" : "Idioma" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Lungime maximă" + "value" : "Limbă" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Макс. длина" + "value" : "Язык" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Maximálna dĺžka" + "value" : "Jazyk" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Maxlängd" + "value" : "Språk" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ความยาวสูงสุด" + "value" : "ภาษา" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Maksimum Uzunluk" + "value" : "Dil" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Максимальна довжина" + "value" : "Мова" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Chiều dài tối đa" + "value" : "Ngôn ngữ" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "最大長度" + "value" : "语言" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "最大长度" + "value" : "語言" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "最大長度" + "value" : "語言" } } } }, - "Max Messages Limit" : { + "Max Length" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الحد الأقصى للرسائل" + "value" : "الحد الأقصى لطول" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Límit màxim de missatges" + "value" : "Longitud màxima" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Maximální limit zpráv" + "value" : "Maximální délka" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Maksimumgrænse for beskeder" + "value" : "Maks. længde" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Maximale Nachrichtenanzahl" + "value" : "Maximale Länge" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Μέγιστο Όριο Μηνυμάτων" + "value" : "Μέγιστο μήκος" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Maximum Messages Limit" + "value" : "Max Length" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Maximum Messages Limit" + "value" : "Max Length" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Max Messages Limit" + "value" : "Max Length" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Límite Máximo de Mensajes" + "value" : "Longitud máxima" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Límite Máximo de Mensajes" + "value" : "Longitud Máxima" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Viestiin enimmäisraja" + "value" : "Enimmäispituus" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Limite maximale de messages" + "value" : "Longueur maximale" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Limite maximale de messages" + "value" : "Longueur maximale" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "מגבלת הודעות מרבית" + "value" : "אורך מרבי" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "अधिकतम संदेश सीमा" + "value" : "अधिकतम लंबाई" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Maksimalni broj poruka" + "value" : "Maksimalna duljina" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Üzenetek maximális száma" + "value" : "Maximális hosszasság" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Batas Pesan Maksimal" + "value" : "Panjang Maksimum" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Limite massimo messaggi" + "value" : "Lunghezza massima" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "メッセージ上限" + "value" : "最大長" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "최대 메시지 제한" + "value" : "최대 길이" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Had Maksimum Mesej" + "value" : "Panjang Maksimum" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Maks grense for meldinger" + "value" : "Maks lengde" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Maximaal aantal berichten" + "value" : "Maximale lengte" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Limit maksymalnej liczby wiadomości" + "value" : "Maksymalna długość" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Limite Máximo de Mensagens" + "value" : "Comprimento Máximo" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Limite Máximo de Mensagens" + "value" : "Comprimento Máximo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Limită maximă mesaje" + "value" : "Lungime maximă" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Максимальный лимит сообщений" + "value" : "Макс. длина" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Maximálny limit správ" + "value" : "Maximálna dĺžka" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Maximalt antal meddelanden" + "value" : "Maxlängd" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "จำกัดจำนวนข้อความสูงสุด" + "value" : "ความยาวสูงสุด" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Maksimum Mesaj Sınırı" + "value" : "Maksimum Uzunluk" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Максимальна кількість повідомлень" + "value" : "Максимальна довжина" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Giới hạn tin nhắn tối đa" + "value" : "Chiều dài tối đa" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "訊息數量上限" + "value" : "最大长度" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "最大消息限制" + "value" : "最大長度" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "訊息數量上限" + "value" : "最大長度" } } } }, - "Message Control" : { + "Max Messages Limit" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "التحكم في الرسائل" + "value" : "الحد الأقصى للرسائل" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Control de missatges" + "value" : "Límit màxim de missatges" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Řízení zpráv" + "value" : "Maximální limit zpráv" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Beskedkontrol" + "value" : "Maksimumgrænse for beskeder" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nachrichtensteuerung" + "value" : "Maximale Nachrichtenanzahl" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Έλεγχος Μηνυμάτων" + "value" : "Μέγιστο Όριο Μηνυμάτων" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Message Control" + "value" : "Maximum Messages Limit" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Message Control" + "value" : "Maximum Messages Limit" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Message Control" + "value" : "Max Messages Limit" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Control de mensajes" + "value" : "Límite Máximo de Mensajes" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Control de Mensajes" + "value" : "Límite Máximo de Mensajes" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Viestien hallinta" + "value" : "Viestiin enimmäisraja" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Contrôle des messages" + "value" : "Limite maximale de messages" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Contrôle des messages" + "value" : "Limite maximale de messages" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "בקרת הודעות" + "value" : "מגבלת הודעות מרבית" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "संदेश नियंत्रण" + "value" : "अधिकतम संदेश सीमा" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Kontrola poruka" + "value" : "Maksimalni broj poruka" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Üzenetvezérlés" + "value" : "Üzenetek maximális száma" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Kontrol Pesan" + "value" : "Batas Pesan Maksimal" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Controllo messaggi" + "value" : "Limite massimo messaggi" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "メッセージコントロール" + "value" : "メッセージ上限" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "메시지 제어" + "value" : "최대 메시지 제한" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Kawalan Mesej" + "value" : "Had Maksimum Mesej" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Meldingskontroll" + "value" : "Maks grense for meldinger" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Berichtbeheer" + "value" : "Maximaal aantal berichten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Sterowanie wiadomością" + "value" : "Limit maksymalnej liczby wiadomości" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Controle de Mensagens" + "value" : "Limite Máximo de Mensagens" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Controlo de Mensagens" + "value" : "Limite Máximo de Mensagens" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Control Mesaje" + "value" : "Limită maximă mesaje" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Управление сообщениями" + "value" : "Максимальный лимит сообщений" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Ovládanie správ" + "value" : "Maximálny limit správ" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Meddelandekontroll" + "value" : "Maximalt antal meddelanden" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "การควบคุมข้อความ" + "value" : "จำกัดจำนวนข้อความสูงสุด" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Mesaj Kontrolü" + "value" : "Maksimum Mesaj Sınırı" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Керування повідомленнями" + "value" : "Максимальна кількість повідомлень" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Kiểm soát Tin nhắn" + "value" : "Giới hạn tin nhắn tối đa" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "訊息控制" + "value" : "最大消息限制" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "消息控制" + "value" : "訊息數量上限" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "訊息控制" + "value" : "訊息數量上限" } } } }, - "Model" : { + "Message Control" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "طراز" + "value" : "التحكم في الرسائل" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Control de missatges" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Řízení zpráv" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Beskedkontrol" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Modell" + "value" : "Nachrichtensteuerung" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Μοντέλο" + "value" : "Έλεγχος Μηνυμάτων" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Message Control" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Message Control" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Message Control" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Modelo" + "value" : "Control de mensajes" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Modelo" + "value" : "Control de Mensajes" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Malli" + "value" : "Viestien hallinta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Modèle" + "value" : "Contrôle des messages" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Modèle" + "value" : "Contrôle des messages" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "דגם" + "value" : "בקרת הודעות" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "मॉडल" + "value" : "संदेश नियंत्रण" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Kontrola poruka" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Modell" + "value" : "Üzenetvezérlés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Kontrol Pesan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Modello" + "value" : "Controllo messaggi" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "モデル" + "value" : "メッセージコントロール" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "모델" + "value" : "메시지 제어" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Kawalan Mesej" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Modell" + "value" : "Meldingskontroll" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Berichtbeheer" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Sterowanie wiadomością" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Modelo" + "value" : "Controle de Mensagens" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Modelo" + "value" : "Controlo de Mensagens" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Control Mesaje" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Модель" + "value" : "Управление сообщениями" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Ovládanie správ" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Modell" + "value" : "Meddelandekontroll" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "โมเดล" + "value" : "การควบคุมข้อความ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Mesaj Kontrolü" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Модель" + "value" : "Керування повідомленнями" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Kiểu máy" + "value" : "Kiểm soát Tin nhắn" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "模型" + "value" : "消息控制" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "模型" + "value" : "訊息控制" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "模型" + "value" : "訊息控制" } } } }, - "Model Settings" : { + "MLX Community" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "إعدادات النموذج" + "value" : "مجتمع MLX" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Configuració del model" + "value" : "Comunitat MLX" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Nastavení modelu" + "value" : "Komunita MLX" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Modelindstillinger" + "value" : "MLX-fællesskab" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Modelleinstellungen" + "value" : "MLX Community" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Ρυθμίσεις Μοντέλου" + "value" : "Κοινότητα MLX" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Model Settings" + "value" : "MLX Community" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Model Settings" + "value" : "MLX Community" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Model Settings" + "value" : "MLX Community" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Configuración de modelo" + "value" : "Comunidad MLX" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Configuración de Modelo" + "value" : "Comunidad MLX" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Mallin asetukset" + "value" : "MLX-yhteisö" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Paramètres du modèle" + "value" : "Communauté MLX" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Paramètres du modèle" + "value" : "Communauté MLX" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "הגדרות דגם" + "value" : "קהילת MLX" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "मॉडल सेटिंग्स" + "value" : "MLX समुदाय" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Postavke modela" + "value" : "Zajednica MLX" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Modellbeállítások" + "value" : "MLX Közösség" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Pengaturan Model" + "value" : "Komunitas MLX" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impostazioni modello" + "value" : "Community MLX" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "モデル設定" + "value" : "MLXコミュニティ" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "모델 설정" + "value" : "MLX 커뮤니티" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Tetapan Model" + "value" : "Komuniti MLX" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Modellinnstillinger" + "value" : "MLX-fellesskap" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Modelinstellingen" + "value" : "MLX-community" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustawienia modelu" + "value" : "Społeczność MLX" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Configurações de Modelo" + "value" : "Comunidade MLX" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Definições do Modelo" + "value" : "MLX Comunidade" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Setări model" + "value" : "Comunitatea MLX" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки модели" + "value" : "MLX Сообщество" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Nastavenia modelu" + "value" : "Komunita MLX" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Modellinställningar" + "value" : "MLX-community" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "การตั้งค่าโมเดล" + "value" : "ชุมชน MLX" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Model Ayarları" + "value" : "MLX Topluluğu" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Налаштування моделі" + "value" : "Спільнота MLX" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Cài đặt Mô hình" + "value" : "Cộng đồng MLX" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "模型設定" + "value" : "MLX 社区" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "模型设置" + "value" : "MLX 社群" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "模型設定" + "value" : "MLX 社群" } } } }, - "Model State" : { + "mlx-community/OpenELM-3B" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "حالة النموذج" + "value" : "mlx-community/OpenELM-3B" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Estat del model" + "value" : "mlx-community/OpenELM-3B" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Stav modelu" + "value" : "mlx-community/OpenELM-3B" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Modeltilstand" + "value" : "mlx-community/OpenELM-3B" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Modellstatus" + "value" : "mlx-community/OpenELM-3B" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Κατάσταση Μοντέλου" + "value" : "mlx-community/OpenELM-3B" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Model State" + "value" : "mlx-community/OpenELM-3B" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Model Status" + "value" : "mlx-community/OpenELM-3B" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Model State" + "value" : "mlx-community/OpenELM-3B" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Estado del Modelo" + "value" : "mlx-community/OpenELM-3B" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Estado del modelo" + "value" : "mlx-community/OpenELM-3B" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Mallin tila" + "value" : "mlx-community/OpenELM-3B" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "État du modèle" + "value" : "mlx-community/OpenELM-3B" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "État du modèle" + "value" : "mlx-community/OpenELM-3B" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "מצב דגם" + "value" : "mlx-community/OpenELM-3B" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "मॉडल स्थिति" + "value" : "mlx-community/OpenELM-3B" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Stanje modela" + "value" : "mlx-community/OpenELM-3B" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Modellállapot" + "value" : "mlx-community/OpenELM-3B" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Keadaan Model" + "value" : "mlx-community/OpenELM-3B" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Stato modello" + "value" : "mlx-community/OpenELM-3B" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "モデルの状態" + "value" : "mlx-community/OpenELM-3B" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "모델 상태" + "value" : "mlx-community/OpenELM-3B" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Keadaan Model" + "value" : "mlx-community/OpenELM-3B" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Modelltilstand" + "value" : "mlx-community/OpenELM-3B" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Modelstatus" + "value" : "mlx-community/OpenELM-3B" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Stan modelu" + "value" : "mlx-community/OpenELM-3B" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Estado do Modelo" + "value" : "mlx-community/OpenELM-3B" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Estado do Modelo" + "value" : "mlx-community/OpenELM-3B" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Model de stare" + "value" : "mlx-community/OpenELM-3B" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Состояние модели" + "value" : "mlx-community/OpenELM-3B" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Stav modelu" + "value" : "mlx-community/OpenELM-3B" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Modelläge" + "value" : "mlx-community/OpenELM-3B" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "สถานะโมเดล" + "value" : "mlx-community/OpenELM-3B" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Model Durumu" + "value" : "mlx-community/OpenELM-3B" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Стан моделі" + "value" : "mlx-community/OpenELM-3B" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Trạng thái mô hình" + "value" : "mlx-community/OpenELM-3B" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "模型狀態" + "value" : "mlx-community/OpenELM-3B" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "模型状态" + "value" : "mlx-community/OpenELM-3B" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "模型狀態" + "value" : "mlx-community/OpenELM-3B" } } } }, - "Models" : { - "extractionState" : "manual", + "Model" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "النماذج" + "value" : "طراز" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Models" + "value" : "Model" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Modely" + "value" : "Model" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Modeller" + "value" : "Model" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Modelle" + "value" : "Modell" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Μοντέλα" + "value" : "Μοντέλο" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Models" + "value" : "Model" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Models" + "value" : "Model" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Models" + "value" : "Model" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Modelos" + "value" : "Modelo" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Modelos" + "value" : "Modelo" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Mallit" + "value" : "Malli" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Modèles" + "value" : "Modèle" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Modèles" + "value" : "Modèle" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "דגמים" + "value" : "דגם" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "मॉडल्स" + "value" : "मॉडल" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Modeli" + "value" : "Model" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Modellek" + "value" : "Modell" } }, "id" : { @@ -12988,7 +13033,7 @@ "it" : { "stringUnit" : { "state" : "translated", - "value" : "Modelli" + "value" : "Modello" } }, "ja" : { @@ -13012,55 +13057,55 @@ "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Modeller" + "value" : "Modell" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Modellen" + "value" : "Model" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Modele" + "value" : "Model" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Modelos" + "value" : "Modelo" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Modelos" + "value" : "Modelo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Modele" + "value" : "Model" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Модели" + "value" : "Модель" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Modely" + "value" : "Model" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Modeller" + "value" : "Modell" } }, "th" : { @@ -13072,9571 +13117,9601 @@ "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Modeller" + "value" : "Model" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Моделі" + "value" : "Модель" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Mô hình" + "value" : "Kiểu máy" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", "value" : "模型" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", "value" : "模型" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "型號" + "value" : "模型" } } } }, - "New Chat" : { - "extractionState" : "manual", + "Model Settings" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "دردشة جديدة" + "value" : "إعدادات النموذج" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Xat nou" + "value" : "Configuració del model" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Nový chat" + "value" : "Nastavení modelu" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Ny chat" + "value" : "Modelindstillinger" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neuer Chat" + "value" : "Modelleinstellungen" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Νέα συνομιλία" + "value" : "Ρυθμίσεις Μοντέλου" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "New Chat" + "value" : "Model Settings" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "New Chat" + "value" : "Model Settings" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "New Chat" + "value" : "Model Settings" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nuevo Chat" + "value" : "Configuración de modelo" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Nuevo chat" + "value" : "Configuración de Modelo" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Uusi keskustelu" + "value" : "Mallin asetukset" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle discussion" + "value" : "Paramètres du modèle" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle Discussion" + "value" : "Paramètres du modèle" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "צ'אט חדש" + "value" : "הגדרות דגם" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "नयी चैट" + "value" : "मॉडल सेटिंग्स" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Nova poruka" + "value" : "Postavke modela" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Új csevegés" + "value" : "Modellbeállítások" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Obrolan Baru" + "value" : "Pengaturan Model" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuova chat" + "value" : "Impostazioni modello" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "新規チャット" + "value" : "モデル設定" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "새로운 채팅" + "value" : "모델 설정" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Sembang Baru" + "value" : "Tetapan Model" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ny chat" + "value" : "Modellinnstillinger" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuwe chat" + "value" : "Modelinstellingen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nowy czat" + "value" : "Ustawienia modelu" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Nova Conversa" + "value" : "Configurações de Modelo" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Nova Conversa" + "value" : "Definições do Modelo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Chat nou" + "value" : "Setări model" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Новый чат" + "value" : "Настройки модели" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Nový chat" + "value" : "Nastavenia modelu" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Ny Chatt" + "value" : "Modellinställningar" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "เริ่มแชทใหม่" + "value" : "การตั้งค่าโมเดล" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Yeni Sohbet" + "value" : "Model Ayarları" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Нова розмова" + "value" : "Налаштування моделі" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Trò chuyện mới" + "value" : "Cài đặt Mô hình" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "新聊天" + "value" : "模型设置" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "新建对话" + "value" : "模型設定" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "新聊天" + "value" : "模型設定" } } } }, - "New Conversation" : { + "Model State" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "محادثة جديدة" + "value" : "حالة النموذج" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Nova conversa" + "value" : "Estat del model" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Nová konverzace" + "value" : "Stav modelu" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Ny samtale" + "value" : "Modeltilstand" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neue Konversation" + "value" : "Modellstatus" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Νέα Συνομιλία" + "value" : "Κατάσταση Μοντέλου" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "New Chat" + "value" : "Model State" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "New Conversation" + "value" : "Model Status" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "New Chat" + "value" : "Model State" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva conversación" + "value" : "Estado del Modelo" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva conversación" + "value" : "Estado del modelo" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Uusi keskustelu" + "value" : "Mallin tila" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle conversation" + "value" : "État du modèle" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle conversation" + "value" : "État du modèle" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "שיחה חדשה" + "value" : "מצב דגם" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "नई बातचीत" + "value" : "मॉडल स्थिति" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Novi razgovor" + "value" : "Stanje modela" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Új beszélgetés" + "value" : "Modellállapot" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Percakapan Baru" + "value" : "Keadaan Model" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuova conversazione" + "value" : "Stato modello" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "新規メッセージ" + "value" : "モデルの状態" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "새 대화" + "value" : "모델 상태" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Perbualan Baru" + "value" : "Keadaan Model" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ny samtale" + "value" : "Modelltilstand" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuw gesprek" + "value" : "Modelstatus" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nowa rozmowa" + "value" : "Stan modelu" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Nova Conversa" + "value" : "Estado do Modelo" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Nova Conversa" + "value" : "Estado do Modelo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Conversație Nouă" + "value" : "Model de stare" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Новый чат" + "value" : "Состояние модели" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Nový rozhovor" + "value" : "Stav modelu" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Ny konversation" + "value" : "Modelläge" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "เริ่มการสนทนาใหม่" + "value" : "สถานะโมเดล" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Yeni Konuşma" + "value" : "Model Durumu" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Нова розмова" + "value" : "Стан моделі" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Cuộc trò chuyện mới" + "value" : "Trạng thái mô hình" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "新對話" + "value" : "模型状态" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "新建对话" + "value" : "模型狀態" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "新對話" + "value" : "模型狀態" } } } }, - "New Task" : { + "Models" : { "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "مهمة جديدة" + "value" : "النماذج" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Nova tasca" + "value" : "Models" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Nový úkol" + "value" : "Modely" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Ny Opgave" + "value" : "Modeller" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neue Aufgabe" + "value" : "Modelle" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Νέα Εργασία" + "value" : "Μοντέλα" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "New Task" + "value" : "Models" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "New Task" + "value" : "Models" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "New Task" + "value" : "Models" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva tarea" + "value" : "Modelos" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva Tarea" + "value" : "Modelos" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Uusi tehtävä" + "value" : "Mallit" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle tâche" + "value" : "Modèles" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle tâche" + "value" : "Modèles" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "משימה חדשה" + "value" : "דגמים" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "नई कार्य" + "value" : "मॉडल्स" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Novi zadatak" + "value" : "Modeli" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Új feladat" + "value" : "Modellek" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Tugas Baru" + "value" : "Model" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuova attività" + "value" : "Modelli" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "新規タスク" + "value" : "モデル" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "새 작업" + "value" : "모델" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Tugasan Baharu" + "value" : "Model" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ny oppgave" + "value" : "Modeller" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuwe taak" + "value" : "Modellen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nowe zadanie" + "value" : "Modele" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Nova Tarefa" + "value" : "Modelos" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Nova Tarefa" + "value" : "Modelos" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Activitate nouă" + "value" : "Modele" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Новая задача" + "value" : "Модели" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Nová úloha" + "value" : "Modely" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Ny uppgift" + "value" : "Modeller" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "งานใหม่" + "value" : "โมเดล" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Yeni Görev" + "value" : "Modeller" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Нове завдання" + "value" : "Моделі" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Tác vụ Mới" + "value" : "Mô hình" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "新任務" + "value" : "模型" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "新建任务" + "value" : "型號" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "新增任務" + "value" : "模型" } } } }, - "No Chat" : { + "New Chat" : { "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "لا توجد محادثة" + "value" : "دردشة جديدة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Sense xat" + "value" : "Xat nou" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Žádný chat" + "value" : "Nový chat" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Ingen chat" + "value" : "Ny chat" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kein Chat" + "value" : "Neuer Chat" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Χωρίς Συνομιλία" + "value" : "Νέα συνομιλία" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "No Chat" + "value" : "New Chat" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "No Chat" + "value" : "New Chat" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "No Chat" + "value" : "New Chat" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sin chat" + "value" : "Nuevo Chat" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Sin chat" + "value" : "Nuevo chat" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Ei keskustelua" + "value" : "Uusi keskustelu" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Pas de discussion" + "value" : "Nouvelle discussion" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Pas de discussion" + "value" : "Nouvelle Discussion" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אין צ'אט" + "value" : "צ'אט חדש" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "कोई चैट नहीं" + "value" : "नयी चैट" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Nema chata" + "value" : "Nova poruka" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Nincs csevegés" + "value" : "Új csevegés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Tidak Ada Obrolan" + "value" : "Obrolan Baru" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nessuna chat" + "value" : "Nuova chat" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "チャットなし" + "value" : "新規チャット" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "채팅 없음" + "value" : "새로운 채팅" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Tiada Sembang" + "value" : "Sembang Baru" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ingen Chat" + "value" : "Ny chat" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geen chat" + "value" : "Nieuwe chat" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Brak czatu" + "value" : "Nowy czat" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Sem bate-papo" + "value" : "Nova Conversa" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Sem Chat" + "value" : "Nova Conversa" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Fără chat" + "value" : "Chat nou" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Нет чата" + "value" : "Новый чат" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Žiadny chat" + "value" : "Nový chat" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Ingen chatt" + "value" : "Ny Chatt" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ไม่มีการแชท" + "value" : "เริ่มแชทใหม่" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Sohbet Yok" + "value" : "Yeni Sohbet" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Немає чату" + "value" : "Нова розмова" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Không có Trò chuyện" + "value" : "Trò chuyện mới" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "無聊天" + "value" : "新建对话" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "无对话" + "value" : "新聊天" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "沒有聊天" + "value" : "新聊天" } } } }, - "No Conversation" : { + "New Conversation" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "لا توجد محادثة" + "value" : "محادثة جديدة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Cap Conversa" + "value" : "Nova conversa" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Žádný chat" + "value" : "Nová konverzace" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Ingen samtale" + "value" : "Ny samtale" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kein Gespräch" + "value" : "Neue Konversation" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Καμία συνομιλία" + "value" : "Νέα Συνομιλία" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "No Conversation" + "value" : "New Chat" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "No Conversation" + "value" : "New Conversation" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "No Conversation" + "value" : "New Chat" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sin conversación" + "value" : "Nueva conversación" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Sin conversación" + "value" : "Nueva conversación" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Ei keskusteluja" + "value" : "Uusi keskustelu" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Aucune conversation" + "value" : "Nouvelle conversation" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Aucune conversation" + "value" : "Nouvelle conversation" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אין שיחה" + "value" : "שיחה חדשה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "कोई बातचीत नहीं" + "value" : "नई बातचीत" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Nema razgovora" + "value" : "Novi razgovor" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Nincs beszélgetés" + "value" : "Új beszélgetés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Tidak Ada Percakapan" + "value" : "Percakapan Baru" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nessuna conversazione" + "value" : "Nuova conversazione" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "会話なし" + "value" : "新規メッセージ" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "대화 없음" + "value" : "새 대화" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Tiada Perbualan" + "value" : "Perbualan Baru" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ingen samtaler" + "value" : "Ny samtale" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Geen gesprek" + "value" : "Nieuw gesprek" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Brak rozmowy" + "value" : "Nowa rozmowa" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Sem conversa" + "value" : "Nova Conversa" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Sem Conversa" + "value" : "Nova Conversa" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Nicio conversație" + "value" : "Conversație Nouă" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Нет беседы" + "value" : "Новый чат" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Žiadna konverzácia" + "value" : "Nový rozhovor" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Ingen konversation" + "value" : "Ny konversation" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ไม่มีการสนทนา" + "value" : "เริ่มการสนทนาใหม่" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Sohbet Yok" + "value" : "Yeni Konuşma" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Немає розмов" + "value" : "Нова розмова" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Không có hội thoại" + "value" : "Cuộc trò chuyện mới" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "沒有對話" + "value" : "新建对话" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "无对话" + "value" : "新對話" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "尚未有對話" + "value" : "新對話" } } } }, - "Not selected" : { + "New Task" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "غير محدد" + "value" : "مهمة جديدة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "No seleccionat" + "value" : "Nova tasca" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Nevybráno" + "value" : "Nový úkol" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Ikke valgt" + "value" : "Ny Opgave" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nicht ausgewählt" + "value" : "Neue Aufgabe" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Δεν έχει επιλεγεί" + "value" : "Νέα Εργασία" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Not selected" + "value" : "New Task" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Not selected" + "value" : "New Task" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Not selected" + "value" : "New Task" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "No seleccionado" + "value" : "Nueva tarea" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "No seleccionado" + "value" : "Nueva Tarea" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Ei valittu" + "value" : "Uusi tehtävä" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Non sélectionné" + "value" : "Nouvelle tâche" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Non sélectionné" + "value" : "Nouvelle tâche" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "לא נבחר" + "value" : "משימה חדשה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "चयनित नहीं" + "value" : "नई कार्य" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Nije odabrano" + "value" : "Novi zadatak" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Nincs kiválasztva" + "value" : "Új feladat" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Belum dipilih" + "value" : "Tugas Baru" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Non selezionato" + "value" : "Nuova attività" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "未選択" + "value" : "新規タスク" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "선택 안 됨" + "value" : "새 작업" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Tidak dipilih" + "value" : "Tugasan Baharu" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ikke valgt" + "value" : "Ny oppgave" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Niet geselecteerd" + "value" : "Nieuwe taak" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nie wybrano" + "value" : "Nowe zadanie" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Não selecionado" + "value" : "Nova Tarefa" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Não selecionado" + "value" : "Nova Tarefa" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Neselectat" + "value" : "Activitate nouă" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Не выбрано" + "value" : "Новая задача" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Nevybraté" + "value" : "Nová úloha" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Inte valt" + "value" : "Ny uppgift" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ไม่ได้เลือก" + "value" : "งานใหม่" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Seçilmedi" + "value" : "Yeni Görev" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Не вибрано" + "value" : "Нове завдання" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Không được chọn" + "value" : "Tác vụ Mới" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "未選擇" + "value" : "新建任务" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "无选择" + "value" : "新增任務" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "未選取" + "value" : "新任務" } } } }, - "OK" : { + "No Chat" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "حسنًا" + "value" : "لا توجد محادثة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "D'acord" + "value" : "Sense xat" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Žádný chat" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Ingen chat" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Kein Chat" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Χωρίς Συνομιλία" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "No Chat" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "No Chat" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "No Chat" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Sin chat" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Sin chat" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Ei keskustelua" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Pas de discussion" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Pas de discussion" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אישור" + "value" : "אין צ'אט" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "ठीक है" + "value" : "कोई चैट नहीं" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "U redu" + "value" : "Nema chata" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Nincs csevegés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Tidak Ada Obrolan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Nessuna chat" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "チャットなし" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "확인" + "value" : "채팅 없음" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Tiada Sembang" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Ingen Chat" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Geen chat" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Brak czatu" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Sem bate-papo" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Sem Chat" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Fără chat" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "ОК" + "value" : "Нет чата" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Žiadny chat" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Ingen chatt" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ตกลง" + "value" : "ไม่มีการแชท" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Tamam" + "value" : "Sohbet Yok" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Гаразд" + "value" : "Немає чату" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Đồng ý" + "value" : "Không có Trò chuyện" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "確定" + "value" : "无对话" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "确定" + "value" : "沒有聊天" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "好的" + "value" : "無聊天" } } } }, - "Please enter Hugging Face Repo ID" : { - "extractionState" : "manual", + "No Conversation" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "يرجى إدخال معرف مستودع Hugging Face" + "value" : "لا توجد محادثة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Introduïu l'ID del repositori de Hugging Face" + "value" : "Cap Conversa" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Zadejte ID repozitáře Hugging Face" + "value" : "Žádný chat" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Indtast venligst Hugging Face Repo ID" + "value" : "Ingen samtale" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitte Hugging Face Repo-ID eingeben" + "value" : "Kein Gespräch" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Καταχωρίστε το ID του αποθετηρίου Hugging Face" + "value" : "Καμία συνομιλία" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Please enter Hugging Face Repo ID" + "value" : "No Conversation" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Please enter Hugging Face Repository ID" + "value" : "No Conversation" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Please enter Hugging Face Repo ID" + "value" : "No Conversation" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Introduce el ID del repositorio de Hugging Face" + "value" : "Sin conversación" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Por favor, ingresa el ID del repositorio Hugging Face" + "value" : "Sin conversación" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Anna Hugging Face -repo ID" + "value" : "Ei keskusteluja" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez entrer l'ID du dépôt Hugging Face" + "value" : "Aucune conversation" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez entrer l'ID du dépôt Hugging Face" + "value" : "Aucune conversation" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אנא הזן את מזהה המאגר של Hugging Face" + "value" : "אין שיחה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "कृपया Hugging Face Repo ID दर्ज करें" + "value" : "कोई बातचीत नहीं" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Unesite Hugging Face ID spremišta" + "value" : "Nema razgovora" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Adja meg a Hugging Face tárház azonosítóját" + "value" : "Nincs beszélgetés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Masukkan Hugging Face Repo ID" + "value" : "Tidak Ada Percakapan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Inserisci l'ID del repository di Hugging Face" + "value" : "Nessuna conversazione" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face レポIDを入力してください" + "value" : "会話なし" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 리포지토리 ID를 입력하세요" + "value" : "대화 없음" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Sila masukkan ID Repo Hugging Face" + "value" : "Tiada Perbualan" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Vennligst skriv inn Hugging Face-repo-ID" + "value" : "Ingen samtaler" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Voer alstublieft Hugging Face-opslag-ID in" + "value" : "Geen gesprek" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Proszę wprowadzić ID repozytorium Hugging Face" + "value" : "Brak rozmowy" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Insira o ID do Repositório Hugging Face" + "value" : "Sem conversa" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Insira o ID do Repositório Hugging Face" + "value" : "Sem Conversa" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Introduceți Hugging Face Repo ID" + "value" : "Nicio conversație" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Введите ID репозитория Hugging Face" + "value" : "Нет беседы" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Zadajte ID úložiska Hugging Face" + "value" : "Žiadna konverzácia" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Ange Hugging Face Repo-ID" + "value" : "Ingen konversation" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "โปรดป้อน Hugging Face Repo ID" + "value" : "ไม่มีการสนทนา" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Lütfen Hugging Face Repo Kimliğini girin" + "value" : "Sohbet Yok" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Введіть ID репозиторія Hugging Face" + "value" : "Немає розмов" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Vui lòng nhập ID Kho Hugging Face" + "value" : "Không có hội thoại" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "請輸入 Hugging Face Repo ID" + "value" : "无对话" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "请输入 Hugging Face 仓库 ID" + "value" : "尚未有對話" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "請輸入 Hugging Face Repo ID" + "value" : "沒有對話" } } } }, - "Please select a new chat" : { - "extractionState" : "manual", + "Not selected" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "يرجى اختيار دردشة جديدة" + "value" : "غير محدد" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccioneu un xat nou" + "value" : "No seleccionat" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Vyberte nový chat" + "value" : "Nevybráno" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Vælg venligst en ny chat" + "value" : "Ikke valgt" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neuen Chat auswählen" + "value" : "Nicht ausgewählt" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Επιλέξτε νέα συνομιλία" + "value" : "Δεν έχει επιλεγεί" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Please select a new conversation" + "value" : "Not selected" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Please select a new conversation" + "value" : "Not selected" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Please select a new chat" + "value" : "Not selected" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccione un nuevo chat" + "value" : "No seleccionado" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccione un nuevo chat" + "value" : "No seleccionado" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Valitse uusi keskustelu" + "value" : "Ei valittu" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez sélectionner une nouvelle conversation" + "value" : "Non sélectionné" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez sélectionner une nouvelle conversation" + "value" : "Non sélectionné" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "בחר/י צ'אט חדש" + "value" : "לא נבחר" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "कृपया एक नया चैट चुनें" + "value" : "चयनित नहीं" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Odaberite novi chat" + "value" : "Nije odabrano" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Válassz egy új csevegést" + "value" : "Nincs kiválasztva" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Pilih obrolan baru" + "value" : "Belum dipilih" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona una nuova chat" + "value" : "Non selezionato" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "新しいチャットを選択してください" + "value" : "未選択" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "새 채팅을 선택하십시오" + "value" : "선택 안 됨" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Sila pilih sembang baru" + "value" : "Tidak dipilih" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Velg en ny chat" + "value" : "Ikke valgt" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Selecteer een nieuw gesprek" + "value" : "Niet geselecteerd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz nowy czat" + "value" : "Nie wybrano" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Selecione um novo chat" + "value" : "Não selecionado" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Selecione uma nova conversa" + "value" : "Não selecionado" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Selectează un chat nou" + "value" : "Neselectat" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выберите новый чат" + "value" : "Не выбрано" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Vyberte novú konverzáciu" + "value" : "Nevybraté" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Välj en ny chatt" + "value" : "Inte valt" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "โปรดเลือกแชทใหม่" + "value" : "ไม่ได้เลือก" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Yeni sohbet seçin" + "value" : "Seçilmedi" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Виберіть новий чат" + "value" : "Не вибрано" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Vui lòng chọn cuộc trò chuyện mới" + "value" : "Không được chọn" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "請選擇新的聊天" + "value" : "无选择" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "请选择一个新的对话" + "value" : "未選取" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "請選擇新的聊天" + "value" : "未選擇" } } } }, - "Please select a new conversation" : { + "OK" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "يرجى اختيار محادثة جديدة" + "value" : "حسنًا" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccioneu una nova conversa" + "value" : "D'acord" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Vyberte novou konverzaci" + "value" : "OK" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Vælg venligst en ny samtale" + "value" : "OK" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Bitte wählen Sie ein neues Gespräch aus" + "value" : "OK" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Επιλέξτε μια νέα συνομιλία" + "value" : "OK" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Please select a new chat" + "value" : "OK" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Please select a new chat" + "value" : "OK" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Please select a new chat conversation" + "value" : "OK" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Selecciona una nueva conversación" + "value" : "OK" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Seleccione una nueva conversación" + "value" : "OK" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Valitse uusi keskustelu" + "value" : "OK" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez sélectionner une nouvelle conversation" + "value" : "OK" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Veuillez sélectionner une nouvelle conversation" + "value" : "OK" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אנא בחר שיחה חדשה" + "value" : "אישור" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "कृपया नई बातचीत चुनें" + "value" : "ठीक है" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Odaberite novi razgovor" + "value" : "U redu" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Válassz egy új beszélgetést" + "value" : "OK" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Harap pilih percakapan baru" + "value" : "OK" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Seleziona una nuova conversazione" + "value" : "OK" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "新しい会話を選択してください" + "value" : "OK" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "새 대화를 선택하세요" + "value" : "확인" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Sila pilih perbualan baharu" + "value" : "OK" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Velg en ny samtale" + "value" : "OK" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Selecteer een nieuw gesprek" + "value" : "OK" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wybierz nową rozmowę" + "value" : "OK" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Selecione uma nova conversa" + "value" : "OK" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Selecione uma nova conversa" + "value" : "OK" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Selectează o conversație nouă" + "value" : "OK" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выберите новый разговор" + "value" : "ОК" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Vyberte novú konverzáciu" + "value" : "OK" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Välj en ny konversation" + "value" : "OK" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "โปรดเลือกการสนทนาใหม่" + "value" : "ตกลง" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Yeni bir sohbet seçin" + "value" : "Tamam" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Будь ласка, виберіть нову розмову" + "value" : "Гаразд" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Vui lòng chọn cuộc trò chuyện mới" + "value" : "Đồng ý" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "請選擇新的對話" + "value" : "确定" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "请选择一个新的对话" + "value" : "好的" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "請選擇新的對話" + "value" : "確定" } } } }, - "Preferences and model settings" : { + "Please enter Hugging Face Repo ID" : { "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "التفضيلات وإعدادات النموذج" + "value" : "يرجى إدخال معرف مستودع Hugging Face" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Preferències i configuració del model" + "value" : "Introduïu l'ID del repositori de Hugging Face" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Předvolby a nastavení modelu" + "value" : "Zadejte ID repozitáře Hugging Face" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Præferencer og modelindstillinger" + "value" : "Indtast venligst Hugging Face Repo ID" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einstellungen und Modelloptionen" + "value" : "Bitte Hugging Face Repo-ID eingeben" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Προτιμήσεις και ρυθμίσεις μοντέλου" + "value" : "Καταχωρίστε το ID του αποθετηρίου Hugging Face" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Preferences and model settings" + "value" : "Please enter Hugging Face Repo ID" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Preferences and model settings" + "value" : "Please enter Hugging Face Repository ID" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Preferences and model settings" + "value" : "Please enter Hugging Face Repo ID" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Preferencias y configuración del modelo" + "value" : "Introduce el ID del repositorio de Hugging Face" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Preferencias y configuración del modelo" + "value" : "Por favor, ingresa el ID del repositorio Hugging Face" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Asetukset ja mallin asetukset" + "value" : "Anna Hugging Face -repo ID" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Préférences et paramètres du modèle" + "value" : "Veuillez entrer l'ID du dépôt Hugging Face" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Préférences et paramètres du modèle" + "value" : "Veuillez entrer l'ID du dépôt Hugging Face" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "העדפות והגדרות מודל" + "value" : "אנא הזן את מזהה המאגר של Hugging Face" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "प्राथमिकताएँ और मॉडल सेटिंग्स" + "value" : "कृपया Hugging Face Repo ID दर्ज करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Postavke i postavke modela" + "value" : "Unesite Hugging Face ID spremišta" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Preferenciák és modellbeállítások" + "value" : "Adja meg a Hugging Face tárház azonosítóját" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Preferensi dan pengaturan model" + "value" : "Masukkan Hugging Face Repo ID" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Preferenze e impostazioni del modello" + "value" : "Inserisci l'ID del repository di Hugging Face" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "環境設定とモデル設定" + "value" : "Hugging Face レポIDを入力してください" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "환경설정 및 모델 설정" + "value" : "Hugging Face 리포지토리 ID를 입력하세요" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Keutamaan dan tetapan model" + "value" : "Sila masukkan ID Repo Hugging Face" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Preferanser og modellinnstillinger" + "value" : "Vennligst skriv inn Hugging Face-repo-ID" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Voorkeuren en modelinstellingen" + "value" : "Voer alstublieft Hugging Face-opslag-ID in" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Preferencje i ustawienia modelu" + "value" : "Proszę wprowadzić ID repozytorium Hugging Face" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Preferências e configurações do modelo" + "value" : "Insira o ID do Repositório Hugging Face" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Preferências e definições do modelo" + "value" : "Insira o ID do Repositório Hugging Face" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Preferințe și setări model" + "value" : "Introduceți Hugging Face Repo ID" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки и параметры модели" + "value" : "Введите ID репозитория Hugging Face" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Predvoľby a nastavenia modelu" + "value" : "Zadajte ID úložiska Hugging Face" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Inställningar och modellanpassningar" + "value" : "Ange Hugging Face Repo-ID" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "การตั้งค่าและการตั้งค่าโมเดล" + "value" : "โปรดป้อน Hugging Face Repo ID" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Tercihler ve model ayarları" + "value" : "Lütfen Hugging Face Repo Kimliğini girin" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Налаштування і параметри моделі" + "value" : "Введіть ID репозиторія Hugging Face" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Tùy chọn và cài đặt mô hình" + "value" : "Vui lòng nhập ID Kho Hugging Face" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "喜好設定和模型設定" + "value" : "请输入 Hugging Face 仓库 ID" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "偏好和模型设置" + "value" : "請輸入 Hugging Face Repo ID" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "偏好設定與模型設定" + "value" : "請輸入 Hugging Face Repo ID" } } } }, - "Prompt Time" : { + "Please select a new chat" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "وقت التنبيه" + "value" : "يرجى اختيار دردشة جديدة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Hora de la notificació" + "value" : "Seleccioneu un xat nou" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Čas výzvy" + "value" : "Vyberte nový chat" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Tidsforspørgsel" + "value" : "Vælg venligst en ny chat" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Eingabezeit" + "value" : "Neuen Chat auswählen" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Χρόνος Προτροπής" + "value" : "Επιλέξτε νέα συνομιλία" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Prompt Time" + "value" : "Please select a new conversation" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Prompt Time" + "value" : "Please select a new conversation" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Prompt Time" + "value" : "Please select a new chat" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Hora del aviso" + "value" : "Seleccione un nuevo chat" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Hora de aviso" + "value" : "Seleccione un nuevo chat" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Pyyntöaika" + "value" : "Valitse uusi keskustelu" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Heure d'invite" + "value" : "Veuillez sélectionner une nouvelle conversation" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Heure de rappel" + "value" : "Veuillez sélectionner une nouvelle conversation" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "זמן התראה" + "value" : "בחר/י צ'אט חדש" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "प्रॉम्प्ट समय" + "value" : "कृपया एक नया चैट चुनें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Vrijeme Podsjetnika" + "value" : "Odaberite novi chat" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Válaszidő" + "value" : "Válassz egy új csevegést" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Waktu Prompt" + "value" : "Pilih obrolan baru" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Tempo di Prompt" + "value" : "Seleziona una nuova chat" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "プロンプト時間" + "value" : "新しいチャットを選択してください" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "실행 시간" + "value" : "새 채팅을 선택하십시오" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Masa Arahan" + "value" : "Sila pilih sembang baru" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Varslingstid" + "value" : "Velg en ny chat" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Montagetijd" + "value" : "Selecteer een nieuw gesprek" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Czas monitowania" + "value" : "Wybierz nowy czat" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Tempo do Prompt" + "value" : "Selecione um novo chat" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Tempo de Prompt" + "value" : "Selecione uma nova conversa" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Ora solicitării" + "value" : "Selectează un chat nou" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Время напоминания" + "value" : "Выберите новый чат" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Čas výzvy" + "value" : "Vyberte novú konverzáciu" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Frågetid" + "value" : "Välj en ny chatt" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ตั้งค่าการแจ้งเตือน" + "value" : "โปรดเลือกแชทใหม่" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Hızlandırma Saati" + "value" : "Yeni sohbet seçin" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Час запиту" + "value" : "Виберіть новий чат" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Thời gian nhắc nhở" + "value" : "Vui lòng chọn cuộc trò chuyện mới" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "提示時間" + "value" : "请选择一个新的对话" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "提示词处理时间" + "value" : "請選擇新的聊天" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "提示時間" + "value" : "請選擇新的聊天" } } } }, - "Prompt Tokens/second" : { + "Please select a new conversation" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "رموز التنبيه/الثانية" + "value" : "يرجى اختيار محادثة جديدة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Tokens d'indicació/segon" + "value" : "Seleccioneu una nova conversa" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Počet vstupních tokenů za sekundu" + "value" : "Vyberte novou konverzaci" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Prompt Tokens/sekund" + "value" : "Vælg venligst en ny samtale" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Prompt-Token/Sekunde" + "value" : "Bitte wählen Sie ein neues Gespräch aus" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Προτροπή διακριτά/δευτερόλεπτο" + "value" : "Επιλέξτε μια νέα συνομιλία" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Prompt Tokens/second" + "value" : "Please select a new chat" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Prompt Tokens/second" + "value" : "Please select a new chat" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Prompt Tokens/second" + "value" : "Please select a new chat conversation" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Tokens de indicación/segundo" + "value" : "Selecciona una nueva conversación" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Tokens de solicitud/segundo" + "value" : "Seleccione una nueva conversación" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Kehotetokeneita/sekunti" + "value" : "Valitse uusi keskustelu" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Jetons d'invite/seconde" + "value" : "Veuillez sélectionner une nouvelle conversation" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Jetons d'invite/seconde" + "value" : "Veuillez sélectionner une nouvelle conversation" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אסימוני הנחיה לשנייה" + "value" : "אנא בחר שיחה חדשה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "प्रॉम्प्ट टोकन्स/सेकंड" + "value" : "कृपया नई बातचीत चुनें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Podaci upita/sekundi" + "value" : "Odaberite novi razgovor" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Felszólító tokenek/másodperc" + "value" : "Válassz egy új beszélgetést" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Token Perintah/detik" + "value" : "Harap pilih percakapan baru" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Token Prompt/secondo" + "value" : "Seleziona una nuova conversazione" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "プロンプトトークン/秒" + "value" : "新しい会話を選択してください" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "프롬프트 토큰/초당" + "value" : "새 대화를 선택하세요" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Token Prompt/saat" + "value" : "Sila pilih perbualan baharu" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Starttokener/sekund" + "value" : "Velg en ny samtale" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Prompt-tokens/seconde" + "value" : "Selecteer een nieuw gesprek" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Tokeny monitu/sekundę" + "value" : "Wybierz nową rozmowę" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Tokens de Prompt/segundo" + "value" : "Selecione uma nova conversa" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Tokens de Prompt/segundo" + "value" : "Selecione uma nova conversa" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Marcaje de solicitare/secundă" + "value" : "Selectează o conversație nouă" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Токены запроса/секунда" + "value" : "Выберите новый разговор" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Počet vstupných tokenov za sekundu" + "value" : "Vyberte novú konverzáciu" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Uppmaningstoken/sekund" + "value" : "Välj en ny konversation" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "โทเค็นพร้อมท์/วินาที" + "value" : "โปรดเลือกการสนทนาใหม่" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Uyarı Jetonları/saniye" + "value" : "Yeni bir sohbet seçin" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Токени підказки/секунда" + "value" : "Будь ласка, виберіть нову розмову" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Phím nhắc/Giây" + "value" : "Vui lòng chọn cuộc trò chuyện mới" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "提示字元/秒" + "value" : "请选择一个新的对话" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "提示词处理令牌/秒" + "value" : "請選擇新的對話" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "提示 Token/每秒" + "value" : "請選擇新的對話" } } } }, - "Regenerate" : { + "Preferences and model settings" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "إعادة إنشاء" + "value" : "التفضيلات وإعدادات النموذج" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Regenera" + "value" : "Preferències i configuració del model" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Regenerovat" + "value" : "Předvolby a nastavení modelu" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Generer igen" + "value" : "Præferencer og modelindstillinger" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Erneut generieren" + "value" : "Einstellungen und Modelloptionen" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αναδημιουργία" + "value" : "Προτιμήσεις και ρυθμίσεις μοντέλου" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Regenerate" + "value" : "Preferences and model settings" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Regenerate" + "value" : "Preferences and model settings" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Regenerate" + "value" : "Preferences and model settings" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Regenerar" + "value" : "Preferencias y configuración del modelo" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Regenerar" + "value" : "Preferencias y configuración del modelo" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Luo uudelleen" + "value" : "Asetukset ja mallin asetukset" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Régénérer" + "value" : "Préférences et paramètres du modèle" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Régénérer" + "value" : "Préférences et paramètres du modèle" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "צור מחדש" + "value" : "העדפות והגדרות מודל" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "फिर से जनरेट करें" + "value" : "प्राथमिकताएँ और मॉडल सेटिंग्स" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Ponovi generiranje" + "value" : "Postavke i postavke modela" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Újragenerálás" + "value" : "Preferenciák és modellbeállítások" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Regenerasi" + "value" : "Preferensi dan pengaturan model" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Rigenera" + "value" : "Preferenze e impostazioni del modello" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "再生成" + "value" : "環境設定とモデル設定" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "다시 생성" + "value" : "환경설정 및 모델 설정" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Jana semula" + "value" : "Keutamaan dan tetapan model" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Generer på nytt" + "value" : "Preferanser og modellinnstillinger" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Opnieuw genereren" + "value" : "Voorkeuren en modelinstellingen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Regeneruj" + "value" : "Preferencje i ustawienia modelu" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Regenerar" + "value" : "Preferências e configurações do modelo" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Regenerar" + "value" : "Preferências e definições do modelo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Regenerează" + "value" : "Preferințe și setări model" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Генерировать заново" + "value" : "Настройки и параметры модели" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Obnoviť" + "value" : "Predvoľby a nastavenia modelu" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Regenerera" + "value" : "Inställningar och modellanpassningar" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "สร้างใหม่" + "value" : "การตั้งค่าและการตั้งค่าโมเดล" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Yeniden Oluştur" + "value" : "Tercihler ve model ayarları" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Оновити" + "value" : "Налаштування і параметри моделі" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Tạo lại" + "value" : "Tùy chọn và cài đặt mô hình" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "重新生成" + "value" : "偏好和模型设置" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "重新生成" + "value" : "偏好設定與模型設定" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "重新生成" + "value" : "喜好設定和模型設定" } } } }, - "Repetition Context Size" : { + "Prompt Time" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "حجم سياق التكرار" + "value" : "وقت التنبيه" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Mida del context de repetició" + "value" : "Hora de la notificació" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Velikost kontextu opakování" + "value" : "Čas výzvy" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Repetitionskontekststørrelse" + "value" : "Tidsforspørgsel" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wiederholungskontextgröße" + "value" : "Eingabezeit" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Μέγεθος Πλαισίου Επανάληψης" + "value" : "Χρόνος Προτροπής" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Repetition Context Size" + "value" : "Prompt Time" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Repetition Context Size" + "value" : "Prompt Time" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Repetition Context Size" + "value" : "Prompt Time" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Tamaño del Contexto de Repetición" + "value" : "Hora del aviso" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Tamaño del Contexto de Repetición" + "value" : "Hora de aviso" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Toiston kontekstin koko" + "value" : "Pyyntöaika" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Taille du contexte de répétition" + "value" : "Heure d'invite" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Taille du Contexte de Répétition" + "value" : "Heure de rappel" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "גודל הקשר לחזרה" + "value" : "זמן התראה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "पुनरावृत्ति संदर्भ आकार" + "value" : "प्रॉम्प्ट समय" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Veličina konteksta ponavljanja" + "value" : "Vrijeme Podsjetnika" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Ismétlés kontextusmérete" + "value" : "Válaszidő" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Ukuran Konteks Pengulangan" + "value" : "Waktu Prompt" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Dimensione contesto di ripetizione" + "value" : "Tempo di Prompt" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "繰り返しコンテキストサイズ" + "value" : "プロンプト時間" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "반복 컨텍스트 크기" + "value" : "실행 시간" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Saiz Konteks Pengulangan" + "value" : "Masa Arahan" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Størrelse på gjentakelsessammenheng" + "value" : "Varslingstid" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Herhalingscontextgrootte" + "value" : "Montagetijd" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Rozmiar kontekstu powtórzeń" + "value" : "Czas monitowania" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Tamanho do Contexto de Repetição" + "value" : "Tempo do Prompt" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Tamanho do Contexto de Repetição" + "value" : "Tempo de Prompt" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Dimensiunea contextului repetiției" + "value" : "Ora solicitării" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Размер контекста повторений" + "value" : "Время напоминания" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Veľkosť kontextu opakovania" + "value" : "Čas výzvy" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Repetitionskontextstorlek" + "value" : "Frågetid" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ขนาดบริบทการทำซ้ำ" + "value" : "ตั้งค่าการแจ้งเตือน" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Tekrar Bağlam Boyutu" + "value" : "Hızlandırma Saati" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Розмір контексту повторення" + "value" : "Час запиту" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Kích thước ngữ cảnh lặp lại" + "value" : "Thời gian nhắc nhở" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "重複上下文大小" + "value" : "提示词处理时间" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "重复上下文大小" + "value" : "提示時間" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "重複上下文大小" + "value" : "提示時間" } } } }, - "Repetition Penalty" : { + "Prompt Tokens/second" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "عقوبة التكرار" + "value" : "رموز التنبيه/الثانية" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Càstig de Repetició" + "value" : "Tokens d'indicació/segon" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Penalizace Opakování" + "value" : "Počet vstupních tokenů za sekundu" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Gentagelsesstraf" + "value" : "Prompt Tokens/sekund" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Wiederholungsstrafe" + "value" : "Prompt-Token/Sekunde" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Ποινή Επανάληψης" + "value" : "Προτροπή διακριτά/δευτερόλεπτο" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Repetition Penalty" + "value" : "Prompt Tokens/second" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Repetition Penalty" + "value" : "Prompt Tokens/second" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Repetition Penalty" + "value" : "Prompt Tokens/second" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Penalización por Repetición" + "value" : "Tokens de indicación/segundo" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Penalización por Repetición" + "value" : "Tokens de solicitud/segundo" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Toistopenalti" + "value" : "Kehotetokeneita/sekunti" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Pénalité de répétition" + "value" : "Jetons d'invite/seconde" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Pénalité de Répétition" + "value" : "Jetons d'invite/seconde" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "עונש על חזרה" + "value" : "אסימוני הנחיה לשנייה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "दोहराव दंड" + "value" : "प्रॉम्प्ट टोकन्स/सेकंड" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Kazna za ponavljanje" + "value" : "Podaci upita/sekundi" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Ismétlési Büntetés" + "value" : "Felszólító tokenek/másodperc" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Penalti Pengulangan" + "value" : "Token Perintah/detik" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Penalità di Ripetizione" + "value" : "Token Prompt/secondo" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "反復ペナルティ" + "value" : "プロンプトトークン/秒" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "반복 패널티" + "value" : "프롬프트 토큰/초당" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Penalti Pengulangan" + "value" : "Token Prompt/saat" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Repetisjonstraff" + "value" : "Starttokener/sekund" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Herhalingsstraf" + "value" : "Prompt-tokens/seconde" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Kara za powtórzenia" + "value" : "Tokeny monitu/sekundę" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Penalidade de Repetição" + "value" : "Tokens de Prompt/segundo" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Penalização de Repetição" + "value" : "Tokens de Prompt/segundo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Penalizare Repetare" + "value" : "Marcaje de solicitare/secundă" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Штраф за повторение" + "value" : "Токены запроса/секунда" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Trest za opakovanie" + "value" : "Počet vstupných tokenov za sekundu" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Repetitionsstraff" + "value" : "Uppmaningstoken/sekund" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "บทลงโทษการทำซ้ำ" + "value" : "โทเค็นพร้อมท์/วินาที" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Tekrar Cezası" + "value" : "Uyarı Jetonları/saniye" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Штраф за повторення" + "value" : "Токени підказки/секунда" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Hình phạt lặp lại" + "value" : "Phím nhắc/Giây" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "重複懲罰" + "value" : "提示词处理令牌/秒" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "重复惩罚" + "value" : "提示 Token/每秒" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "重複懲罰" + "value" : "提示字元/秒" } } } }, - "Reset All Settings" : { + "Regenerate" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "إعادة تعيين جميع الإعدادات" + "value" : "إعادة إنشاء" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Restableix tots els ajustos" + "value" : "Regenera" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Obnovit všechna nastavení" + "value" : "Regenerovat" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Nulstil alle indstillinger" + "value" : "Generer igen" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Alle Einstellungen zurücksetzen" + "value" : "Erneut generieren" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Επαναφορά όλων των ρυθμίσεων" + "value" : "Αναδημιουργία" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Reset All Settings" + "value" : "Regenerate" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Reset All Settings" + "value" : "Regenerate" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Reset All Settings" + "value" : "Regenerate" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Restablecer ajustes" + "value" : "Regenerar" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Restablecer Todos los Ajustes" + "value" : "Regenerar" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Palauta kaikki asetukset" + "value" : "Luo uudelleen" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réinitialiser tous les réglages" + "value" : "Régénérer" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Réinitialiser tous les réglages" + "value" : "Régénérer" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אפס את כל ההגדרות" + "value" : "צור מחדש" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "सभी सेटिंग रीसेट करें" + "value" : "फिर से जनरेट करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Poništi sve postavke" + "value" : "Ponovi generiranje" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Összes beállítás visszaállítása" + "value" : "Újragenerálás" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Atur Ulang Semua Pengaturan" + "value" : "Regenerasi" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Reimposta tutte le impostazioni" + "value" : "Rigenera" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "すべての設定をリセット" + "value" : "再生成" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "모든 설정 재설정" + "value" : "다시 생성" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Tetapkan Semula Semua Tetapan" + "value" : "Jana semula" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Tilbakestill alle innstillinger" + "value" : "Generer på nytt" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Stel alle instellingen opnieuw in" + "value" : "Opnieuw genereren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyzeruj wszystkie ustawienia" + "value" : "Regeneruj" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Redefinir Ajustes" + "value" : "Regenerar" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Redefinir Todos os Ajustes" + "value" : "Regenerar" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Resetați toate configurările" + "value" : "Regenerează" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сбросить все настройки" + "value" : "Генерировать заново" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Resetovať všetky nastavenia" + "value" : "Obnoviť" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Återställ alla inställningar" + "value" : "Regenerera" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "รีเซ็ตการตั้งค่าทั้งหมด" + "value" : "สร้างใหม่" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Tüm Ayarları Sıfırla" + "value" : "Yeniden Oluştur" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Скинути всі налаштування" + "value" : "Оновити" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Đặt Lại Tất Cả Cài Đặt" + "value" : "Tạo lại" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "重設所有設定" + "value" : "重新生成" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "重置所有设置" + "value" : "重新生成" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "重設所有設定" + "value" : "重新生成" } } } }, - "Search Conversation..." : { + "Repetition Context Size" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "البحث في المحادثة..." + "value" : "حجم سياق التكرار" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Cerca a la conversa..." + "value" : "Mida del context de repetició" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Hledat konverzaci..." + "value" : "Velikost kontextu opakování" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Søg i samtale ..." + "value" : "Repetitionskontekststørrelse" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Gespräch durchsuchen ..." + "value" : "Wiederholungskontextgröße" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αναζήτηση συνομιλίας..." + "value" : "Μέγεθος Πλαισίου Επανάληψης" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Search Conversation..." + "value" : "Repetition Context Size" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Search Conversation..." + "value" : "Repetition Context Size" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Search Conversation..." + "value" : "Repetition Context Size" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar conversación..." + "value" : "Tamaño del Contexto de Repetición" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar conversación..." + "value" : "Tamaño del Contexto de Repetición" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Etsi keskustelua..." + "value" : "Toiston kontekstin koko" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Rechercher dans la conversation..." + "value" : "Taille du contexte de répétition" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Rechercher dans la conversation..." + "value" : "Taille du Contexte de Répétition" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "חפש שיחה..." + "value" : "גודל הקשר לחזרה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "वार्ता खोजें..." + "value" : "पुनरावृत्ति संदर्भ आकार" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Pretraži razgovor..." + "value" : "Veličina konteksta ponavljanja" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Beszélgetés keresése..." + "value" : "Ismétlés kontextusmérete" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Cari Percakapan..." + "value" : "Ukuran Konteks Pengulangan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cerca Conversazione..." + "value" : "Dimensione contesto di ripetizione" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "会話を検索..." + "value" : "繰り返しコンテキストサイズ" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "대화 검색..." + "value" : "반복 컨텍스트 크기" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Cari Perbualan..." + "value" : "Saiz Konteks Pengulangan" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Søk i samtale ..." + "value" : "Størrelse på gjentakelsessammenheng" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Gesprek doorzoeken..." + "value" : "Herhalingscontextgrootte" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Szukaj w rozmowie..." + "value" : "Rozmiar kontekstu powtórzeń" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar Conversa..." + "value" : "Tamanho do Contexto de Repetição" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Procurar Conversa..." + "value" : "Tamanho do Contexto de Repetição" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Căutați în conversație..." + "value" : "Dimensiunea contextului repetiției" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Поиск по переписке..." + "value" : "Размер контекста повторений" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Hľadať v konverzácii..." + "value" : "Veľkosť kontextu opakovania" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Sök konversation..." + "value" : "Repetitionskontextstorlek" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ค้นหาบทสนทนา..." + "value" : "ขนาดบริบทการทำซ้ำ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Sohbeti Ara..." + "value" : "Tekrar Bağlam Boyutu" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Шукати розмову…" + "value" : "Розмір контексту повторення" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Tìm kiếm Cuộc trò chuyện..." + "value" : "Kích thước ngữ cảnh lặp lại" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "搜尋對話…" + "value" : "重复上下文大小" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "搜索对话..." + "value" : "重複上下文大小" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "搜尋對話..." + "value" : "重複上下文大小" } } } }, - "Search..." : { + "Repetition Penalty" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "بحث..." + "value" : "عقوبة التكرار" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Cerca..." + "value" : "Càstig de Repetició" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Hledat..." + "value" : "Penalizace Opakování" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Søg..." + "value" : "Gentagelsesstraf" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Suchen ..." + "value" : "Wiederholungsstrafe" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αναζήτηση..." + "value" : "Ποινή Επανάληψης" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Search..." + "value" : "Repetition Penalty" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Search..." + "value" : "Repetition Penalty" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Search..." + "value" : "Repetition Penalty" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar..." + "value" : "Penalización por Repetición" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar..." + "value" : "Penalización por Repetición" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Etsi..." + "value" : "Toistopenalti" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Recherche..." + "value" : "Pénalité de répétition" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Rechercher…" + "value" : "Pénalité de Répétition" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "חיפוש..." + "value" : "עונש על חזרה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "खोजें..." + "value" : "दोहराव दंड" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Pretraži..." + "value" : "Kazna za ponavljanje" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Keresés..." + "value" : "Ismétlési Büntetés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Cari..." + "value" : "Penalti Pengulangan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Cerca..." + "value" : "Penalità di Ripetizione" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "検索..." + "value" : "反復ペナルティ" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "검색..." + "value" : "반복 패널티" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Carian..." + "value" : "Penalti Pengulangan" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Søk..." + "value" : "Repetisjonstraff" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Zoeken..." + "value" : "Herhalingsstraf" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Szukaj..." + "value" : "Kara za powtórzenia" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Buscar..." + "value" : "Penalidade de Repetição" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Pesquisar..." + "value" : "Penalização de Repetição" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Căutare..." + "value" : "Penalizare Repetare" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Поиск..." + "value" : "Штраф за повторение" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Hľadať..." + "value" : "Trest za opakovanie" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Sök..." + "value" : "Repetitionsstraff" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ค้นหา..." + "value" : "บทลงโทษการทำซ้ำ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Ara..." + "value" : "Tekrar Cezası" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Пошук..." + "value" : "Штраф за повторення" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Tìm kiếm..." + "value" : "Hình phạt lặp lại" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "搜尋..." + "value" : "重复惩罚" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "搜索..." + "value" : "重複懲罰" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "搜尋..." + "value" : "重複懲罰" } } } }, - "Send" : { - "extractionState" : "manual", + "Reset All Settings" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "إرسال" + "value" : "إعادة تعيين جميع الإعدادات" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Envia" + "value" : "Restableix tots els ajustos" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Odeslat" + "value" : "Obnovit všechna nastavení" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Nulstil alle indstillinger" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Senden" + "value" : "Alle Einstellungen zurücksetzen" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αποστολή" + "value" : "Επαναφορά όλων των ρυθμίσεων" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Reset All Settings" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Reset All Settings" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Reset All Settings" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Restablecer ajustes" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Restablecer Todos los Ajustes" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Lähetä" + "value" : "Palauta kaikki asetukset" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Envoyer" + "value" : "Réinitialiser tous les réglages" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Envoyer" + "value" : "Réinitialiser tous les réglages" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "שלח" + "value" : "אפס את כל ההגדרות" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "भेजें" + "value" : "सभी सेटिंग रीसेट करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Pošalji" + "value" : "Poništi sve postavke" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Küldés" + "value" : "Összes beállítás visszaállítása" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Kirim" + "value" : "Atur Ulang Semua Pengaturan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Invia" + "value" : "Reimposta tutte le impostazioni" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "送信" + "value" : "すべての設定をリセット" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "보내기" + "value" : "모든 설정 재설정" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Hantar" + "value" : "Tetapkan Semula Semua Tetapan" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Send" + "value" : "Tilbakestill alle innstillinger" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verstuur" + "value" : "Stel alle instellingen opnieuw in" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wyślij" + "value" : "Wyzeruj wszystkie ustawienia" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Redefinir Ajustes" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Enviar" + "value" : "Redefinir Todos os Ajustes" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Trimite" + "value" : "Resetați toate configurările" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отправить" + "value" : "Сбросить все настройки" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Odoslať" + "value" : "Resetovať všetky nastavenia" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Skicka" + "value" : "Återställ alla inställningar" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ส่ง" + "value" : "รีเซ็ตการตั้งค่าทั้งหมด" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Gönder" + "value" : "Tüm Ayarları Sıfırla" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Надіслати" + "value" : "Скинути всі налаштування" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Gửi" + "value" : "Đặt Lại Tất Cả Cài Đặt" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "傳送" + "value" : "重置所有设置" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "发送" + "value" : "重設所有設定" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "發送" + "value" : "重設所有設定" } } } }, - "Settings" : { - "extractionState" : "manual", + "Search Conversation..." : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الإعدادات" + "value" : "البحث في المحادثة..." } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Configuració" + "value" : "Cerca a la conversa..." } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Nastavení" + "value" : "Hledat konverzaci..." } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Indstillinger" + "value" : "Søg i samtale ..." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Einstellungen" + "value" : "Gespräch durchsuchen ..." } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Ρυθμίσεις" + "value" : "Αναζήτηση συνομιλίας..." } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Settings" + "value" : "Search Conversation..." } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Settings" + "value" : "Search Conversation..." } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Settings" + "value" : "Search Conversation..." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Buscar conversación..." } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Configuración" + "value" : "Buscar conversación..." } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Asetukset" + "value" : "Etsi keskustelua..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réglages" + "value" : "Rechercher dans la conversation..." } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Réglages" + "value" : "Rechercher dans la conversation..." } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "הגדרות" + "value" : "חפש שיחה..." } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "सेटिंग्स" + "value" : "वार्ता खोजें..." } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Postavke" + "value" : "Pretraži razgovor..." } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Beállítások" + "value" : "Beszélgetés keresése..." } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Pengaturan" + "value" : "Cari Percakapan..." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impostazioni" + "value" : "Cerca Conversazione..." } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "設定" + "value" : "会話を検索..." } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "설정" + "value" : "대화 검색..." } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Tetapan" + "value" : "Cari Perbualan..." } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Innstillinger" + "value" : "Søk i samtale ..." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Instellingen" + "value" : "Gesprek doorzoeken..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustawienia" + "value" : "Szukaj w rozmowie..." } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes" + "value" : "Buscar Conversa..." } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Definições" + "value" : "Procurar Conversa..." } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Configurări" + "value" : "Căutați în conversație..." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки" + "value" : "Поиск по переписке..." } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Nastavenia" + "value" : "Hľadať v konverzácii..." } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Inställningar" + "value" : "Sök konversation..." } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "การตั้งค่า" + "value" : "ค้นหาบทสนทนา..." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Ayarlar" + "value" : "Sohbeti Ara..." } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Налаштування" + "value" : "Шукати розмову…" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Cài đặt" + "value" : "Tìm kiếm Cuộc trò chuyện..." } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "設定" + "value" : "搜索对话..." } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "设置" + "value" : "搜尋對話..." } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "設定" + "value" : "搜尋對話…" } } } }, - "System Prompt" : { + "Search..." : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "التنبيه النظامي" + "value" : "بحث..." } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Indicació del sistema" + "value" : "Cerca..." } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Systémová výzva" + "value" : "Hledat..." } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Systemprompt" + "value" : "Søg..." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Systemaufforderung" + "value" : "Suchen ..." } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Προτροπή Συστήματος" + "value" : "Αναζήτηση..." } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "System Prompt" + "value" : "Search..." } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "System Prompt" + "value" : "Search..." } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "System Prompt" + "value" : "Search..." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Indicador del sistema" + "value" : "Buscar..." } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Mensaje del sistema" + "value" : "Buscar..." } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Järjestelmäkehotus" + "value" : "Etsi..." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Invite système" + "value" : "Recherche..." } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Invite du système" + "value" : "Rechercher…" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "הנחיית מערכת" + "value" : "חיפוש..." } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "सिस्टम प्रॉम्प्ट" + "value" : "खोजें..." } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Sistemski upit" + "value" : "Pretraži..." } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Rendszerüzenet" + "value" : "Keresés..." } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Prompt Sistem" + "value" : "Cari..." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Prompt di Sistema" + "value" : "Cerca..." } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "システムプロンプト" + "value" : "検索..." } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "시스템 프롬프트" + "value" : "검색..." } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Arahan Sistem" + "value" : "Carian..." } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Systemprompt" + "value" : "Søk..." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Systeemprompt" + "value" : "Zoeken..." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Komunikat systemowy" + "value" : "Szukaj..." } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Prompt do Sistema" + "value" : "Buscar..." } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Mensagem do Sistema" + "value" : "Pesquisar..." } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Indicație Sistem" + "value" : "Căutare..." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Системная подсказка" + "value" : "Поиск..." } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Systemová výzva" + "value" : "Hľadať..." } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Systemuppmaning" + "value" : "Sök..." } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "พร้อมท์ของระบบ" + "value" : "ค้นหา..." } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Sistem İstemi" + "value" : "Ara..." } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Системний запит" + "value" : "Пошук..." } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Nhắc Hệ Thống" + "value" : "Tìm kiếm..." } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "系統提示" + "value" : "搜索..." } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "系统提示词" + "value" : "搜尋..." } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "系統提示" + "value" : "搜尋..." } } } }, - "System Settings" : { + "Send" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "إعدادات النظام" + "value" : "إرسال" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Configuració del sistema" + "value" : "Envia" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Nastavení systému" + "value" : "Odeslat" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Indstillinger" + "value" : "Send" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "System­einstellungen" + "value" : "Senden" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Ρυθμίσεις συστήματος" + "value" : "Αποστολή" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "System Settings" + "value" : "Send" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "System Settings" + "value" : "Send" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "System Settings" + "value" : "Send" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes del sistema" + "value" : "Enviar" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Configuración del sistema" + "value" : "Enviar" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Järjestelmäasetukset" + "value" : "Lähetä" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Réglages Système" + "value" : "Envoyer" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Réglages du système" + "value" : "Envoyer" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "הגדרות מערכת" + "value" : "שלח" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "सिस्टम सेटिंग्स" + "value" : "भेजें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Postavke sustava" + "value" : "Pošalji" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Beállítások" + "value" : "Küldés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Pengaturan Sistem" + "value" : "Kirim" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Impostazioni di Sistema" + "value" : "Invia" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "システム設定" + "value" : "送信" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "시스템 설정" + "value" : "보내기" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Tetapan Sistem" + "value" : "Hantar" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Systeminnstillinger" + "value" : "Send" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Systeeminstellingen" + "value" : "Verstuur" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustawienia systemu" + "value" : "Wyślij" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Ajustes do Sistema" + "value" : "Enviar" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Definições do Sistema" + "value" : "Enviar" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Setări Sistem" + "value" : "Trimite" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Настройки системы" + "value" : "Отправить" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Systémové nastavenia" + "value" : "Odoslať" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Systeminställningar" + "value" : "Skicka" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "การตั้งค่าระบบ" + "value" : "ส่ง" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Sistem Ayarları" + "value" : "Gönder" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Параметри системи" + "value" : "Надіслати" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Cài đặt hệ thống" + "value" : "Gửi" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "系統設定" + "value" : "发送" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "系统设置" + "value" : "發送" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "系統設定" + "value" : "傳送" } } } }, - "Temperature" : { + "Settings" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "درجة الحرارة" + "value" : "الإعدادات" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatura" + "value" : "Configuració" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Teplota" + "value" : "Nastavení" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatur" + "value" : "Indstillinger" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatur" + "value" : "Einstellungen" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Θερμοκρασία" + "value" : "Ρυθμίσεις" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Temperature" + "value" : "Settings" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Temperature" + "value" : "Settings" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Temperature" + "value" : "Settings" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatura" + "value" : "Ajustes" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatura" + "value" : "Configuración" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Lämpötila" + "value" : "Asetukset" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Température" + "value" : "Réglages" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Température" + "value" : "Réglages" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "טמפרטורה" + "value" : "הגדרות" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "टेम्परेचर" + "value" : "सेटिंग्स" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatura" + "value" : "Postavke" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Hőmérséklet" + "value" : "Beállítások" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Suhu" + "value" : "Pengaturan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatura" + "value" : "Impostazioni" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "気温" + "value" : "設定" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "온도" + "value" : "설정" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Suhu" + "value" : "Tetapan" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatur" + "value" : "Innstillinger" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatuur" + "value" : "Instellingen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatura" + "value" : "Ustawienia" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatura" + "value" : "Ajustes" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatura" + "value" : "Definições" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatură" + "value" : "Configurări" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Температура" + "value" : "Настройки" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Teplota" + "value" : "Nastavenia" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Temperatur" + "value" : "Inställningar" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "อุณหภูมิ" + "value" : "การตั้งค่า" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Sıcaklık" + "value" : "Ayarlar" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Температура" + "value" : "Налаштування" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Nhiệt độ" + "value" : "Cài đặt" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "溫度" + "value" : "设置" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "温度" + "value" : "設定" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "溫度" + "value" : "設定" } } } }, - "Title" : { + "System Prompt" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "العنوان" + "value" : "التنبيه النظامي" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Títol" + "value" : "Indicació del sistema" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Název" + "value" : "Systémová výzva" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Titel" + "value" : "Systemprompt" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Titel" + "value" : "Systemaufforderung" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Τίτλος" + "value" : "Προτροπή Συστήματος" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Title" + "value" : "System Prompt" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Title" + "value" : "System Prompt" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Title" + "value" : "System Prompt" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Título" + "value" : "Indicador del sistema" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Título" + "value" : "Mensaje del sistema" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Otsikko" + "value" : "Järjestelmäkehotus" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Titre" + "value" : "Invite système" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Titre" + "value" : "Invite du système" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "כותרת" + "value" : "הנחיית מערכת" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "शीर्षक" + "value" : "सिस्टम प्रॉम्प्ट" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Naslov" + "value" : "Sistemski upit" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Cím" + "value" : "Rendszerüzenet" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Judul" + "value" : "Prompt Sistem" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Titolo" + "value" : "Prompt di Sistema" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "タイトル" + "value" : "システムプロンプト" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "제목" + "value" : "시스템 프롬프트" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Tajuk" + "value" : "Arahan Sistem" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Tittel" + "value" : "Systemprompt" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Titel" + "value" : "Systeemprompt" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Tytuł" + "value" : "Komunikat systemowy" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Título" + "value" : "Prompt do Sistema" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Título" + "value" : "Mensagem do Sistema" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Titlu" + "value" : "Indicație Sistem" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Название" + "value" : "Системная подсказка" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Názov" + "value" : "Systemová výzva" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Titel" + "value" : "Systemuppmaning" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ชื่อเรื่อง" + "value" : "พร้อมท์ของระบบ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Başlık" + "value" : "Sistem İstemi" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Назва" + "value" : "Системний запит" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Tiêu đề" + "value" : "Nhắc Hệ Thống" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "標題" + "value" : "系统提示词" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "标题" + "value" : "系統提示" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "標題" + "value" : "系統提示" } } } }, - "Token" : { + "System Settings" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "رمز" + "value" : "إعدادات النظام" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Configuració del sistema" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Nastavení systému" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Indstillinger" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "System­einstellungen" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Διακριτικό" + "value" : "Ρυθμίσεις συστήματος" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "System Settings" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "System Settings" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "System Settings" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Ajustes del sistema" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Configuración del sistema" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Varmenne" + "value" : "Järjestelmäasetukset" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Jeton" + "value" : "Réglages Système" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Jeton" + "value" : "Réglages du système" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אסימון" + "value" : "הגדרות מערכת" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "टोकन" + "value" : "सिस्टम सेटिंग्स" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Postavke sustava" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Biztonsági kód" + "value" : "Beállítások" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Pengaturan Sistem" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Impostazioni di Sistema" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "トークン" + "value" : "システム設定" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "토큰" + "value" : "시스템 설정" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Tetapan Sistem" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Systeminnstillinger" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Systeeminstellingen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Ustawienia systemu" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Ajustes do Sistema" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Definições do Sistema" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Jeton" + "value" : "Setări Sistem" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Токен" + "value" : "Настройки системы" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Systémové nastavenia" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Token" + "value" : "Systeminställningar" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "โทเค็น" + "value" : "การตั้งค่าระบบ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Jeton" + "value" : "Sistem Ayarları" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Токен" + "value" : "Параметри системи" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Mã thông báo" + "value" : "Cài đặt hệ thống" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "憑證" + "value" : "系统设置" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "令牌" + "value" : "系統設定" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "代幣" + "value" : "系統設定" } } } }, - "Top P" : { + "Temperature" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "أعلى P" + "value" : "درجة الحرارة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Principal P" + "value" : "Temperatura" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Nejlepší P" + "value" : "Teplota" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Top P" + "value" : "Temperatur" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Top P" + "value" : "Temperatur" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Κορυφαία π" + "value" : "Θερμοκρασία" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Top P" + "value" : "Temperature" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Top P" + "value" : "Temperature" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Top P" + "value" : "Temperature" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Superior P" + "value" : "Temperatura" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Mejor P" + "value" : "Temperatura" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Ylä-P" + "value" : "Lämpötila" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Top P" + "value" : "Température" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Haut P" + "value" : "Température" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "דירוג גבוה" + "value" : "טמפרטורה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "शीर्ष P" + "value" : "टेम्परेचर" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Vrh P" + "value" : "Temperatura" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Csúcs P" + "value" : "Hőmérséklet" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Teratas P" + "value" : "Suhu" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "In Evidenza" + "value" : "Temperatura" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "上位P" + "value" : "気温" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "상위 P" + "value" : "온도" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Paling Atas" + "value" : "Suhu" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Topp P" + "value" : "Temperatur" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Top-P" + "value" : "Temperatuur" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Najlepsze P" + "value" : "Temperatura" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Top P" + "value" : "Temperatura" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "P Principal" + "value" : "Temperatura" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "De sus P" + "value" : "Temperatură" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Самый популярный" + "value" : "Температура" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Najlepšie P" + "value" : "Teplota" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Topp P" + "value" : "Temperatur" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "บนสุด P" + "value" : "อุณหภูมิ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "En Üst P" + "value" : "Sıcaklık" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Топ P" + "value" : "Температура" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Top P" + "value" : "Nhiệt độ" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "熱門 P" + "value" : "温度" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "核采样" + "value" : "溫度" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "熱門 P" + "value" : "溫度" } } } }, - "Type your message…" : { - "extractionState" : "manual", + "Title" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "اكتب رسالتك…" + "value" : "العنوان" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Escriu el teu missatge…" + "value" : "Títol" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Napište zprávu…" + "value" : "Název" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Indtast din besked…" + "value" : "Titel" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nachricht eingeben …" + "value" : "Titel" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Πληκτρολογήστε το μήνυμά σας…" + "value" : "Τίτλος" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Type your message…" + "value" : "Title" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Type your message…" + "value" : "Title" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Type your message…" + "value" : "Title" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Escribe tu mensaje…" + "value" : "Título" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Escribe tu mensaje…" + "value" : "Título" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Kirjoita viestisi…" + "value" : "Otsikko" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Tapez votre message…" + "value" : "Titre" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Tapez votre message…" + "value" : "Titre" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "הקלידו את ההודעה שלכם…" + "value" : "כותרת" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "अपना संदेश टाइप करें…" + "value" : "शीर्षक" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Unesite poruku…" + "value" : "Naslov" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Írja be üzenetét…" + "value" : "Cím" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Ketik pesan Anda…" + "value" : "Judul" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Digita il tuo messaggio…" + "value" : "Titolo" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "メッセージを入力…" + "value" : "タイトル" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "메시지를 입력하세요…" + "value" : "제목" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Taip mesej anda…" + "value" : "Tajuk" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Skriv meldingen din…" + "value" : "Tittel" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Typ je bericht…" + "value" : "Titel" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wpisz swoją wiadomość…" + "value" : "Tytuł" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Digite sua mensagem…" + "value" : "Título" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Escreva a sua mensagem…" + "value" : "Título" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Scrieți mesajul…" + "value" : "Titlu" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Введите сообщение…" + "value" : "Название" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Napíšte svoju správu…" + "value" : "Názov" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Skriv ditt meddelande..." + "value" : "Titel" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "พิมพ์ข้อความของคุณ…" + "value" : "ชื่อเรื่อง" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Mesajınızı yazın…" + "value" : "Başlık" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Введіть повідомлення…" + "value" : "Назва" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Nhập tin nhắn của bạn…" + "value" : "Tiêu đề" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "輸入您的訊息……" + "value" : "标题" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "输入您的消息…" + "value" : "標題" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "鍵入您的消息…" + "value" : "標題" } } } }, - "Unknown" : { - "extractionState" : "manual", + "Token" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "غير معروف" + "value" : "رمز" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Desconegut" + "value" : "Token" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Neznámé" + "value" : "Token" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Ukendt" + "value" : "Token" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Unbekannt" + "value" : "Token" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Άγνωστο" + "value" : "Διακριτικό" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Unknown" + "value" : "Token" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Unknown" + "value" : "Token" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Unknown" + "value" : "Token" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Desconocido" + "value" : "Token" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Desconocido" + "value" : "Token" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Tuntematon" + "value" : "Varmenne" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Inconnu" + "value" : "Jeton" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Inconnu" + "value" : "Jeton" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "לא ידוע" + "value" : "אסימון" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "अज्ञात" + "value" : "टोकन" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Nepoznato" + "value" : "Token" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Ismeretlen" + "value" : "Biztonsági kód" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Tidak Diketahui" + "value" : "Token" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Sconosciuto" + "value" : "Token" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "不明" + "value" : "トークン" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "알 수 없음" + "value" : "토큰" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Tidak diketahui" + "value" : "Token" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ukjent" + "value" : "Token" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Onbekend" + "value" : "Token" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieznany" + "value" : "Token" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Desconhecido" + "value" : "Token" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Desconhecido" + "value" : "Token" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Necunoscut" + "value" : "Jeton" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Неизвестно" + "value" : "Токен" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Neznáme" + "value" : "Token" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Okänd" + "value" : "Token" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ไม่ทราบ" + "value" : "โทเค็น" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Bilinmeyen" + "value" : "Jeton" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Невідомо" + "value" : "Токен" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Không xác định" + "value" : "Mã thông báo" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "不明" + "value" : "令牌" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "未知" + "value" : "代幣" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "未知" + "value" : "憑證" } } } }, - "Use Custom Endpoint" : { + "Top P" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "استخدام نقطة نهاية مخصصة" + "value" : "أعلى P" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Utilitza el punt final personalitzat" + "value" : "Principal P" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Použít vlastní koncový bod" + "value" : "Nejlepší P" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Brug brugerdefineret slutpunkt" + "value" : "Top P" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Benutzerdefinierten Endpunkt verwenden" + "value" : "Top P" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Χρήση Προσαρμοσμένου Τερματικού Σημείου" + "value" : "Κορυφαία π" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Use Custom Endpoint" + "value" : "Top P" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Use Custom Endpoint" + "value" : "Top P" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Use Custom Endpoint" + "value" : "Top P" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Usar punto final personalizado" + "value" : "Superior P" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Usar punto de acceso personalizado" + "value" : "Mejor P" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Käytä mukautettua päätepistettä" + "value" : "Ylä-P" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Utiliser un point de terminaison personnalisé" + "value" : "Top P" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Utilisateur un point de terminaison personnalisé" + "value" : "Haut P" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "השתמש בקצה מותאם אישית" + "value" : "דירוג גבוה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "कस्टम एंडपॉइंट का उपयोग करें" + "value" : "शीर्ष P" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Upotrijebi prilagođenu krajnju točku" + "value" : "Vrh P" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Egyéni végpont használata" + "value" : "Csúcs P" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Gunakan Titik Akhir Kustom" + "value" : "Teratas P" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Usa endpoint personalizzato" + "value" : "In Evidenza" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "カスタムエンドポイントを使用" + "value" : "上位P" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "사용자 지정 엔드포인트 사용" + "value" : "상위 P" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Gunakan Titik Akhir Tersuai" + "value" : "Paling Atas" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Bruk egendefinert endepunkt" + "value" : "Topp P" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Aangepast eindpunt gebruiken" + "value" : "Top-P" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Użyj niestandardowego punktu końcowego" + "value" : "Najlepsze P" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Usar Ponto de Extremidade Personalizado" + "value" : "Top P" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Usar Endpoint Personalizado" + "value" : "P Principal" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Utilizați Endpoint Personalizat" + "value" : "De sus P" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Использовать пользовательский конечный узел" + "value" : "Самый популярный" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Použiť vlastný koncový bod" + "value" : "Najlepšie P" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Använd anpassad slutpunkt" + "value" : "Topp P" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ใช้ปลายทางที่กำหนดเอง" + "value" : "บนสุด P" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Özel Uç Nokta Kullan" + "value" : "En Üst P" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Використовувати користувацьку кінцеву точку" + "value" : "Топ P" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Sử Dụng Điểm Kết Nối Tùy Chỉnh" + "value" : "Top P" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "使用自定端點" + "value" : "核采样" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "使用自定义节点" + "value" : "熱門 P" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "使用自訂端點" + "value" : "熱門 P" } } } }, - "Use Max Length" : { + "Type your message…" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "استخدام الحد الأقصى للطول" + "value" : "اكتب رسالتك…" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Usa longitud màxima" + "value" : "Escriu el teu missatge…" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Použít maximální délku" + "value" : "Napište zprávu…" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Brug Max-længde" + "value" : "Indtast din besked…" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Maximale Länge verwenden" + "value" : "Nachricht eingeben …" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Χρήση Μέγιστου Μήκους" + "value" : "Πληκτρολογήστε το μήνυμά σας…" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Use Max Length" + "value" : "Type your message…" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Use Max Length" + "value" : "Type your message…" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Use Max Length" + "value" : "Type your message…" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Usar longitud máxima" + "value" : "Escribe tu mensaje…" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Usar longitud máxima" + "value" : "Escribe tu mensaje…" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Käytä maksimipituutta" + "value" : "Kirjoita viestisi…" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Utiliser la longueur max" + "value" : "Tapez votre message…" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Utiliser la longueur max" + "value" : "Tapez votre message…" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "השתמש באורך מרבי" + "value" : "הקלידו את ההודעה שלכם…" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "अधिकतम लंबाई का उपयोग करें" + "value" : "अपना संदेश टाइप करें…" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Koristi maksimalnu duljinu" + "value" : "Unesite poruku…" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Maximális hossz használata" + "value" : "Írja be üzenetét…" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Gunakan Panjang Maksimum" + "value" : "Ketik pesan Anda…" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Usa lunghezza massima" + "value" : "Digita il tuo messaggio…" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "最大長を使用" + "value" : "メッセージを入力…" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "최대 길이 사용" + "value" : "메시지를 입력하세요…" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Gunakan Panjang Maksimum" + "value" : "Taip mesej anda…" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Bruk maks lengde" + "value" : "Skriv meldingen din…" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Maximale lengte gebruiken" + "value" : "Typ je bericht…" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Użyj maks. długości" + "value" : "Wpisz swoją wiadomość…" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Usar comprimento máximo" + "value" : "Digite sua mensagem…" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Usar Comprimento Máximo" + "value" : "Escreva a sua mensagem…" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Folosește Lungime Maximă" + "value" : "Scrieți mesajul…" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Использовать максимальную длину" + "value" : "Введите сообщение…" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Použiť maximálnu dĺžku" + "value" : "Napíšte svoju správu…" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Använd Maxlängd" + "value" : "Skriv ditt meddelande..." } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ใช้ความยาวสูงสุด" + "value" : "พิมพ์ข้อความของคุณ…" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Maksimum Uzunluğu Kullan" + "value" : "Mesajınızı yazın…" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Використовувати максимальну довжину" + "value" : "Введіть повідомлення…" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Sử dụng độ dài tối đa" + "value" : "Nhập tin nhắn của bạn…" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "使用最大長度" + "value" : "输入您的消息…" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "使用最大长度" + "value" : "鍵入您的消息…" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "使用最大長度" + "value" : "輸入您的訊息……" } } } }, - "Use Max Messages Limit" : { + "Unknown" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "استخدم الحد الأقصى للرسائل" + "value" : "غير معروف" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Utilitza el límit màxim de missatges" + "value" : "Desconegut" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Použít maximální limit zpráv" + "value" : "Neznámé" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Brug maksimal grænse for beskeder" + "value" : "Ukendt" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Maximale Nachrichtenanzahl verwenden" + "value" : "Unbekannt" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Χρήση Ορίου Μέγιστων Μηνυμάτων" + "value" : "Άγνωστο" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Use Max Messages Limit" + "value" : "Unknown" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Use Max Messages Limit" + "value" : "Unknown" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Enable Max Messages Limit" + "value" : "Unknown" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Usar límite máximo de mensajes" + "value" : "Desconocido" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Usar Límite Máximo de Mensajes" + "value" : "Desconocido" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Käytä viestien enimmäisrajaa" + "value" : "Tuntematon" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Utiliser la limite maximale de messages" + "value" : "Inconnu" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Utiliser la limite maximale de messages" + "value" : "Inconnu" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "השתמש בגבלת הודעות מקסימלית" + "value" : "לא ידוע" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "अधिकतम संदेश सीमा का उपयोग करें" + "value" : "अज्ञात" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Koristi Ograničenje Maksimalnih Poruka" + "value" : "Nepoznato" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Maximális üzenetlimit használata" + "value" : "Ismeretlen" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Gunakan Batas Maksimum Pesan" + "value" : "Tidak Diketahui" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Usa limite massimo messaggi" + "value" : "Sconosciuto" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "最大メッセージ制限を使用" + "value" : "不明" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "최대 메시지 제한 사용" + "value" : "알 수 없음" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Gunakan Had Maksimum Mesej" + "value" : "Tidak diketahui" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Bruk maksgrense for meldinger" + "value" : "Ukjent" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Maximaal aantal berichten gebruiken" + "value" : "Onbekend" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ustaw maksymalny limit wiadomości" + "value" : "Nieznany" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Usar limite máximo de mensagens" + "value" : "Desconhecido" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Usar Limite Máximo de Mensagens" + "value" : "Desconhecido" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Folosește limita maximă de mesaje" + "value" : "Necunoscut" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Использовать максимальное ограничение сообщений" + "value" : "Неизвестно" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Použiť maximálny limit správ" + "value" : "Neznáme" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Använd maxgräns för meddelanden" + "value" : "Okänd" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ใช้ขีดจำกัดข้อความสูงสุด" + "value" : "ไม่ทราบ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Maksimum Mesaj Sınırını Kullan" + "value" : "Bilinmeyen" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Використовувати максимальну кількість повідомлень" + "value" : "Невідомо" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Sử dụng Giới hạn Tin nhắn Tối đa" + "value" : "Không xác định" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "使用最大消息限制" + "value" : "未知" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "使用最大消息限制" + "value" : "未知" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "使用最大訊息限制" + "value" : "不明" } } } }, - "Use Repetition Penalty" : { + "Use Custom Endpoint" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "استخدام عقوبة التكرار" + "value" : "استخدام نقطة نهاية مخصصة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Utilitza la pena de repetició" + "value" : "Utilitza el punt final personalitzat" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Použít postih opakování" + "value" : "Použít vlastní koncový bod" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Brug gentagelsesstraf" + "value" : "Brug brugerdefineret slutpunkt" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Strafparameter verwenden" + "value" : "Benutzerdefinierten Endpunkt verwenden" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Χρήση Ποινής Επανάληψης" + "value" : "Χρήση Προσαρμοσμένου Τερματικού Σημείου" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Use Repetition Penalty" + "value" : "Use Custom Endpoint" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Use Repetition Penalty" + "value" : "Use Custom Endpoint" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Use Repetition Penalty" + "value" : "Use Custom Endpoint" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Usar Penalización por Repetición" + "value" : "Usar punto final personalizado" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Usar penalización de repetición" + "value" : "Usar punto de acceso personalizado" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Käytä toistopenalttia" + "value" : "Käytä mukautettua päätepistettä" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Utiliser la pénalité de répétition" + "value" : "Utiliser un point de terminaison personnalisé" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Utiliser la Pénalité de Répétition" + "value" : "Utilisateur un point de terminaison personnalisé" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "השתמש בקנס חזרה" + "value" : "השתמש בקצה מותאם אישית" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "दोहराव जुर्माना का उपयोग करें" + "value" : "कस्टम एंडपॉइंट का उपयोग करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Primijeni Kaznu za Ponavljanje" + "value" : "Upotrijebi prilagođenu krajnju točku" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Használjon ismétlési büntetést" + "value" : "Egyéni végpont használata" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Gunakan Penalti Pengulangan" + "value" : "Gunakan Titik Akhir Kustom" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Usa penalità ripetizione" + "value" : "Usa endpoint personalizzato" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "反復ペナルティを使用" + "value" : "カスタムエンドポイントを使用" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "반복 페널티 사용" + "value" : "사용자 지정 엔드포인트 사용" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Gunakan Penalti Pengulangan" + "value" : "Gunakan Titik Akhir Tersuai" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Bruk gjentakelsesstraff" + "value" : "Bruk egendefinert endepunkt" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Gebruik Herhalingsboete" + "value" : "Aangepast eindpunt gebruiken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Użyj kary powtórzeń" + "value" : "Użyj niestandardowego punktu końcowego" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Usar Penalidade de Repetição" + "value" : "Usar Ponto de Extremidade Personalizado" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Usar Penalização de Repetição" + "value" : "Usar Endpoint Personalizado" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Folosește Penalizare Repetiție" + "value" : "Utilizați Endpoint Personalizat" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Использовать штраф за повторение" + "value" : "Использовать пользовательский конечный узел" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Použiť trest za opakovanie" + "value" : "Použiť vlastný koncový bod" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Använd repeteringsstraff" + "value" : "Använd anpassad slutpunkt" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ใช้บทลงโทษการทำซ้ำ" + "value" : "ใช้ปลายทางที่กำหนดเอง" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Tekrar Cezasını Kullan" + "value" : "Özel Uç Nokta Kullan" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Використовувати штраф за повторення" + "value" : "Використовувати користувацьку кінцеву точку" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Sử dụng Phạt Lặp Lại" + "value" : "Sử Dụng Điểm Kết Nối Tùy Chỉnh" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "使用重複懲罰" + "value" : "使用自定义节点" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "使用重复惩罚" + "value" : "使用自訂端點" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "使用重複懲罰" + "value" : "使用自定端點" } } } }, - "Use System Prompt" : { + "Use Max Length" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "استخدام موجه النظام" + "value" : "استخدام الحد الأقصى للطول" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Utilitza l'indicador del sistema" + "value" : "Usa longitud màxima" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Použít systémovou výzvu" + "value" : "Použít maximální délku" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Brug systemprompt" + "value" : "Brug Max-længde" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Systemaufforderung verwenden" + "value" : "Maximale Länge verwenden" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Χρήση Προτροπής Συστήματος" + "value" : "Χρήση Μέγιστου Μήκους" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Use System Prompt" + "value" : "Use Max Length" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Use System Prompt" + "value" : "Use Max Length" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Use System Prompt" + "value" : "Use Max Length" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Usar aviso del sistema" + "value" : "Usar longitud máxima" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Usar aviso del sistema" + "value" : "Usar longitud máxima" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Käytä järjestelmän kehotetta" + "value" : "Käytä maksimipituutta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Utiliser l'invite système" + "value" : "Utiliser la longueur max" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Utiliser l'invite système" + "value" : "Utiliser la longueur max" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "השתמש בהנחיית המערכת" + "value" : "השתמש באורך מרבי" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "सिस्टम प्रॉम्प्ट का उपयोग करें" + "value" : "अधिकतम लंबाई का उपयोग करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Koristi sistemske upute" + "value" : "Koristi maksimalnu duljinu" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Rendszerprompt használata" + "value" : "Maximális hossz használata" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Gunakan Prompt Sistem" + "value" : "Gunakan Panjang Maksimum" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Usa Richiesta di Sistema" + "value" : "Usa lunghezza massima" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "システムプロンプトを使用" + "value" : "最大長を使用" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "시스템 프롬프트 사용" + "value" : "최대 길이 사용" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Gunakan Prompt Sistem" + "value" : "Gunakan Panjang Maksimum" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Bruk systemforespørsel" + "value" : "Bruk maks lengde" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Systeemprompt gebruiken" + "value" : "Maximale lengte gebruiken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Użyj Monitu Systemowego" + "value" : "Użyj maks. długości" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Usar Prompt do Sistema" + "value" : "Usar comprimento máximo" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Usar Indicação do Sistema" + "value" : "Usar Comprimento Máximo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Utilizați Comanda Sistemului" + "value" : "Folosește Lungime Maximă" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Использовать системную подсказку" + "value" : "Использовать максимальную длину" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Použiť systémovú výzvu" + "value" : "Použiť maximálnu dĺžku" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Använd systemsignal" + "value" : "Använd Maxlängd" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ใช้พร้อมท์ระบบ" + "value" : "ใช้ความยาวสูงสุด" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Sistem İstemi Kullan" + "value" : "Maksimum Uzunluğu Kullan" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Використовувати системний запит" + "value" : "Використовувати максимальну довжину" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Sử dụng nhắc nhở hệ thống" + "value" : "Sử dụng độ dài tối đa" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "使用系統提示" + "value" : "使用最大长度" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "使用系统提示词" + "value" : "使用最大長度" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "使用系統提示" + "value" : "使用最大長度" } } } }, - "Version %@" : { + "Use Max Messages Limit" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الإصدار %@" + "value" : "استخدم الحد الأقصى للرسائل" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Versió %@\n" + "value" : "Utilitza el límit màxim de missatges" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Verze %@" + "value" : "Použít maximální limit zpráv" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Version %@" + "value" : "Brug maksimal grænse for beskeder" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Version %@" + "value" : "Maximale Nachrichtenanzahl verwenden" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Έκδοση %@\"" + "value" : "Χρήση Ορίου Μέγιστων Μηνυμάτων" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Version %@" + "value" : "Use Max Messages Limit" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Version %@" + "value" : "Use Max Messages Limit" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Version %@" + "value" : "Enable Max Messages Limit" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Versión %@\n" + "value" : "Usar límite máximo de mensajes" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Versión %@" + "value" : "Usar Límite Máximo de Mensajes" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Versio %@" + "value" : "Käytä viestien enimmäisrajaa" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Version %@" + "value" : "Utiliser la limite maximale de messages" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Version %@" + "value" : "Utiliser la limite maximale de messages" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "גרסה %@" + "value" : "השתמש בגבלת הודעות מקסימלית" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "संस्करण %@\n" + "value" : "अधिकतम संदेश सीमा का उपयोग करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Verzija %@" + "value" : "Koristi Ograničenje Maksimalnih Poruka" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Verzió %@" + "value" : "Maximális üzenetlimit használata" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Versi %@" + "value" : "Gunakan Batas Maksimum Pesan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Versione %@" + "value" : "Usa limite massimo messaggi" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "バージョン %@" + "value" : "最大メッセージ制限を使用" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "버전 %@" + "value" : "최대 메시지 제한 사용" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Versi %@" + "value" : "Gunakan Had Maksimum Mesej" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Versjon %@" + "value" : "Bruk maksgrense for meldinger" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Versie %@\"" + "value" : "Maximaal aantal berichten gebruiken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wersja %@" + "value" : "Ustaw maksymalny limit wiadomości" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Versão %@" + "value" : "Usar limite máximo de mensagens" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Versão %@" + "value" : "Usar Limite Máximo de Mensagens" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Versiunea %@\n" + "value" : "Folosește limita maximă de mesaje" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Версия %@" + "value" : "Использовать максимальное ограничение сообщений" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Verzia %@" + "value" : "Použiť maximálny limit správ" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Version %@" + "value" : "Använd maxgräns för meddelanden" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "เวอร์ชัน %@" + "value" : "ใช้ขีดจำกัดข้อความสูงสุด" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Sürüm %@" + "value" : "Maksimum Mesaj Sınırını Kullan" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Версія %@" + "value" : "Використовувати максимальну кількість повідомлень" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Phiên bản %@" + "value" : "Sử dụng Giới hạn Tin nhắn Tối đa" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "版本 %@" + "value" : "使用最大消息限制" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "版本 %@" + "value" : "使用最大訊息限制" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "版本 %@" + "value" : "使用最大消息限制" } } } }, - "Warning" : { + "Use Repetition Penalty" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "تحذير" + "value" : "استخدام عقوبة التكرار" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Advertència" + "value" : "Utilitza la pena de repetició" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Upozornění" + "value" : "Použít postih opakování" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Advarsel" + "value" : "Brug gentagelsesstraf" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Warnung" + "value" : "Strafparameter verwenden" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Προειδοποίηση" + "value" : "Χρήση Ποινής Επανάληψης" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Warning" + "value" : "Use Repetition Penalty" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Warning" + "value" : "Use Repetition Penalty" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Caution" + "value" : "Use Repetition Penalty" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Advertencia" + "value" : "Usar Penalización por Repetición" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Advertencia" + "value" : "Usar penalización de repetición" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Varoitus" + "value" : "Käytä toistopenalttia" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Avertissement" + "value" : "Utiliser la pénalité de répétition" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Avertissement" + "value" : "Utiliser la Pénalité de Répétition" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אזהרה" + "value" : "השתמש בקנס חזרה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "चेतावनी" + "value" : "दोहराव जुर्माना का उपयोग करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Upozorenje" + "value" : "Primijeni Kaznu za Ponavljanje" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Figyelmeztetés" + "value" : "Használjon ismétlési büntetést" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Peringatan" + "value" : "Gunakan Penalti Pengulangan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Avviso" + "value" : "Usa penalità ripetizione" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "警告" + "value" : "反復ペナルティを使用" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "경고" + "value" : "반복 페널티 사용" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Amaran" + "value" : "Gunakan Penalti Pengulangan" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Advarsel" + "value" : "Bruk gjentakelsesstraff" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Waarschuwing" + "value" : "Gebruik Herhalingsboete" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ostrzeżenie" + "value" : "Użyj kary powtórzeń" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Aviso" + "value" : "Usar Penalidade de Repetição" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Aviso" + "value" : "Usar Penalização de Repetição" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Avertisment" + "value" : "Folosește Penalizare Repetiție" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Предупреждение" + "value" : "Использовать штраф за повторение" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Upozornenie" + "value" : "Použiť trest za opakovanie" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Varning" + "value" : "Använd repeteringsstraff" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "คำเตือน" + "value" : "ใช้บทลงโทษการทำซ้ำ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Uyarı" + "value" : "Tekrar Cezasını Kullan" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Попередження" + "value" : "Використовувати штраф за повторення" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Cảnh báo" + "value" : "Sử dụng Phạt Lặp Lại" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "警告" + "value" : "使用重复惩罚" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "警告" + "value" : "使用重複懲罰" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "警告" + "value" : "使用重複懲罰" } } } }, - "Window Appearance" : { + "Use System Prompt" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "مظهر النافذة" + "value" : "استخدام موجه النظام" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Aparença de la finestra" + "value" : "Utilitza l'indicador del sistema" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Zobrazení okna" + "value" : "Použít systémovou výzvu" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Vinduesudseende" + "value" : "Brug systemprompt" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Darstellung des Fensters" + "value" : "Systemaufforderung verwenden" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Εμφάνιση παραθύρου" + "value" : "Χρήση Προτροπής Συστήματος" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Window Appearance" + "value" : "Use System Prompt" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Window Appearance" + "value" : "Use System Prompt" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Window Appearance" + "value" : "Use System Prompt" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Apariencia de la Ventana" + "value" : "Usar aviso del sistema" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Apariencia de la ventana" + "value" : "Usar aviso del sistema" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Ikkunan ulkoasu" + "value" : "Käytä järjestelmän kehotetta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Apparence de la fenêtre" + "value" : "Utiliser l'invite système" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Apparence de la fenêtre" + "value" : "Utiliser l'invite système" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "מראה החלון" + "value" : "השתמש בהנחיית המערכת" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "विंडो उपस्थिति" + "value" : "सिस्टम प्रॉम्प्ट का उपयोग करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Prikaz prozora" + "value" : "Koristi sistemske upute" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Ablak megjelenés" + "value" : "Rendszerprompt használata" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Tampilan Jendela" + "value" : "Gunakan Prompt Sistem" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Aspetto della finestra" + "value" : "Usa Richiesta di Sistema" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "ウィンドウ表示" + "value" : "システムプロンプトを使用" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "창 모양" + "value" : "시스템 프롬프트 사용" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Penampilan Tetingkap" + "value" : "Gunakan Prompt Sistem" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Vinduets utseende" + "value" : "Bruk systemforespørsel" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Vensterweergave" + "value" : "Systeemprompt gebruiken" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wygląd okna" + "value" : "Użyj Monitu Systemowego" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Aparência da Janela" + "value" : "Usar Prompt do Sistema" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Aparência da Janela" + "value" : "Usar Indicação do Sistema" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Aspectul ferestrei" + "value" : "Utilizați Comanda Sistemului" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Оформление окна" + "value" : "Использовать системную подсказку" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Vzhľad okna" + "value" : "Použiť systémovú výzvu" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Fönstrets utseende" + "value" : "Använd systemsignal" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ลักษณะหน้าต่าง" + "value" : "ใช้พร้อมท์ระบบ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Pencere Görünümü" + "value" : "Sistem İstemi Kullan" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Оформлення вікна" + "value" : "Використовувати системний запит" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Hiển thị cửa sổ" + "value" : "Sử dụng nhắc nhở hệ thống" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "視窗外觀" + "value" : "使用系统提示词" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "窗口样式" + "value" : "使用系統提示" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "視窗外觀" + "value" : "使用系統提示" } } } }, - "https://hf-mirror.com" : { + "Version %@" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "الإصدار %@" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Versió %@\n" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Verze %@" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Version %@" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Version %@" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Έκδοση %@\"" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Version %@" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Version %@" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Version %@" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Versión %@\n" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Versión %@" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Versio %@" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Version %@" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Version %@" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "גרסה %@" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "संस्करण %@\n" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Verzija %@" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Verzió %@" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Versi %@" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Versione %@" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "バージョン %@" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "버전 %@" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Versi %@" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Versjon %@" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Versie %@\"" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Wersja %@" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Versão %@" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Versão %@" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Versiunea %@\n" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Версия %@" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Verzia %@" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Version %@" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "เวอร์ชัน %@" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Sürüm %@" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Версія %@" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "Phiên bản %@" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "版本 %@" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "版本 %@" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "https://hf-mirror.com" + "value" : "版本 %@" } } } }, - "https://huggingface.co" : { + "Warning" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "تحذير" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Advertència" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Upozornění" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Advarsel" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Warnung" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Προειδοποίηση" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Warning" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Warning" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Caution" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Advertencia" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Advertencia" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Varoitus" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Avertissement" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Avertissement" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "אזהרה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "चेतावनी" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Upozorenje" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Figyelmeztetés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Peringatan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Avviso" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "警告" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "경고" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Amaran" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Advarsel" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Waarschuwing" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Ostrzeżenie" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Aviso" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Aviso" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Avertisment" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Предупреждение" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Upozornenie" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Varning" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "คำเตือน" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Uyarı" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Попередження" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "Cảnh báo" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "警告" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "警告" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "https://huggingface.co" + "value" : "警告" } } } }, - "mlx-community/OpenELM-3B" : { + "Window Appearance" : { + "extractionState" : "stale", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "مظهر النافذة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Aparença de la finestra" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Zobrazení okna" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Vinduesudseende" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Darstellung des Fensters" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Εμφάνιση παραθύρου" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Window Appearance" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Window Appearance" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Window Appearance" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Apariencia de la Ventana" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Apariencia de la ventana" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Ikkunan ulkoasu" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Apparence de la fenêtre" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Apparence de la fenêtre" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "מראה החלון" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "विंडो उपस्थिति" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Prikaz prozora" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Ablak megjelenés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Tampilan Jendela" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Aspetto della finestra" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "ウィンドウ表示" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "창 모양" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Penampilan Tetingkap" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Vinduets utseende" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Vensterweergave" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Wygląd okna" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Aparência da Janela" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Aparência da Janela" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Aspectul ferestrei" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Оформление окна" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Vzhľad okna" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Fönstrets utseende" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "ลักษณะหน้าต่าง" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Pencere Görünümü" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Оформлення вікна" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "Hiển thị cửa sổ" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "窗口样式" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "視窗外觀" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "mlx-community/OpenELM-3B" + "value" : "視窗外觀" } } } } }, "version" : "1.0" -} +} \ No newline at end of file diff --git a/ChatMLX/Models/AppleIntelligenceEffectDisplay.swift b/ChatMLX/Models/AppleIntelligenceEffectDisplay.swift deleted file mode 100644 index f762d25..0000000 --- a/ChatMLX/Models/AppleIntelligenceEffectDisplay.swift +++ /dev/null @@ -1,18 +0,0 @@ -// -// AnimationDisplayOption.swift -// ChatMLX -// -// Created by John Mai on 2024/10/7. -// - -import Defaults -import SwiftUI - -enum AppleIntelligenceEffectDisplay: String, CaseIterable, Identifiable, Defaults.Serializable { - case global = "Global" - case appInternal = "Internal" - - var id: String { rawValue } - - var localized: LocalizedStringKey { LocalizedStringKey(rawValue) } -} diff --git a/ChatMLX/Models/ChatMLX.xcdatamodeld/ChatMLX.xcdatamodel/contents b/ChatMLX/Models/ChatMLX.xcdatamodeld/ChatMLX.xcdatamodel/contents deleted file mode 100644 index d7ee4c8..0000000 --- a/ChatMLX/Models/ChatMLX.xcdatamodeld/ChatMLX.xcdatamodel/contents +++ /dev/null @@ -1,34 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ChatMLX/Models/Conversation+CoreDataClass.swift b/ChatMLX/Models/Conversation+CoreDataClass.swift deleted file mode 100644 index 6557201..0000000 --- a/ChatMLX/Models/Conversation+CoreDataClass.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Conversation+CoreDataClass.swift -// ChatMLX -// -// Created by John Mai on 2024/10/2. -// -// - -import CoreData -import Foundation - -@objc(Conversation) -public class Conversation: NSManagedObject { - -} diff --git a/ChatMLX/Models/Conversation+CoreDataProperties.swift b/ChatMLX/Models/Conversation+CoreDataProperties.swift deleted file mode 100644 index 3de83fb..0000000 --- a/ChatMLX/Models/Conversation+CoreDataProperties.swift +++ /dev/null @@ -1,115 +0,0 @@ -// -// Conversation+CoreDataProperties.swift -// ChatMLX -// -// Created by John Mai on 2024/10/2. -// -// - -import CoreData -import Defaults -import Foundation - -extension Conversation { - @nonobjc public class func fetchRequest() -> NSFetchRequest { - NSFetchRequest(entityName: "Conversation") - } - - @NSManaged public var title: String - @NSManaged public var model: String - @NSManaged public var createdAt: Date - @NSManaged public var updatedAt: Date - @NSManaged public var temperature: Float - @NSManaged public var topP: Float - @NSManaged public var useMaxLength: Bool - @NSManaged public var maxLength: Int64 - @NSManaged public var repetitionContextSize: Int - @NSManaged public var maxMessagesLimit: Int32 - @NSManaged public var useMaxMessagesLimit: Bool - @NSManaged public var useRepetitionPenalty: Bool - @NSManaged public var repetitionPenalty: Float - @NSManaged public var useSystemPrompt: Bool - @NSManaged public var systemPrompt: String - @NSManaged public var promptTime: TimeInterval - @NSManaged public var generateTime: TimeInterval - @NSManaged public var promptTokensPerSecond: Double - @NSManaged public var tokensPerSecond: Double - @NSManaged public var messages: [Message] - - public override func awakeFromInsert() { - super.awakeFromInsert() - - setPrimitiveValue(Defaults[.defaultTitle], forKey: #keyPath(Conversation.title)) - setPrimitiveValue(Defaults[.defaultModel], forKey: #keyPath(Conversation.model)) - - setPrimitiveValue(Defaults[.defaultTemperature], forKey: #keyPath(Conversation.temperature)) - setPrimitiveValue(Defaults[.defaultTopP], forKey: #keyPath(Conversation.topP)) - setPrimitiveValue( - Defaults[.defaultRepetitionContextSize], - forKey: #keyPath(Conversation.repetitionContextSize)) - - setPrimitiveValue( - Defaults[.defaultUseRepetitionPenalty], - forKey: #keyPath(Conversation.useRepetitionPenalty)) - setPrimitiveValue( - Defaults[.defaultRepetitionPenalty], forKey: #keyPath(Conversation.repetitionPenalty)) - - setPrimitiveValue( - Defaults[.defaultUseMaxLength], forKey: #keyPath(Conversation.useMaxLength)) - setPrimitiveValue(Defaults[.defaultMaxLength], forKey: #keyPath(Conversation.maxLength)) - setPrimitiveValue( - Defaults[.defaultMaxMessagesLimit], forKey: #keyPath(Conversation.maxMessagesLimit)) - setPrimitiveValue( - Defaults[.defaultUseMaxMessagesLimit], - forKey: #keyPath(Conversation.useMaxMessagesLimit)) - - setPrimitiveValue( - Defaults[.defaultUseSystemPrompt], forKey: #keyPath(Conversation.useSystemPrompt)) - setPrimitiveValue( - Defaults[.defaultSystemPrompt], forKey: #keyPath(Conversation.systemPrompt)) - - setPrimitiveValue(Date.now, forKey: #keyPath(Conversation.createdAt)) - setPrimitiveValue(Date.now, forKey: #keyPath(Conversation.updatedAt)) - } - - public override func willSave() { - super.willSave() - setPrimitiveValue(Date.now, forKey: #keyPath(Conversation.updatedAt)) - } -} - -// MARK: Generated accessors for messages - -extension Conversation { - @objc(insertObject:inMessagesAtIndex:) - @NSManaged public func insertIntoMessages(_ value: Message, at idx: Int) - - @objc(removeObjectFromMessagesAtIndex:) - @NSManaged public func removeFromMessages(at idx: Int) - - @objc(insertMessages:atIndexes:) - @NSManaged public func insertIntoMessages(_ values: [Message], at indexes: NSIndexSet) - - @objc(removeMessagesAtIndexes:) - @NSManaged public func removeFromMessages(at indexes: NSIndexSet) - - @objc(replaceObjectInMessagesAtIndex:withObject:) - @NSManaged public func replaceMessages(at idx: Int, with value: Message) - - @objc(replaceMessagesAtIndexes:withMessages:) - @NSManaged public func replaceMessages(at indexes: NSIndexSet, with values: [Message]) - - @objc(addMessagesObject:) - @NSManaged public func addToMessages(_ value: Message) - - @objc(removeMessagesObject:) - @NSManaged public func removeFromMessages(_ value: Message) - - @objc(addMessages:) - @NSManaged public func addToMessages(_ values: [Message]) - - @objc(removeMessages:) - @NSManaged public func removeFromMessages(_ values: [Message]) -} - -extension Conversation: Identifiable {} diff --git a/ChatMLX/Models/DisplayStyle.swift b/ChatMLX/Models/DisplayStyle.swift deleted file mode 100644 index 61eb6dd..0000000 --- a/ChatMLX/Models/DisplayStyle.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// DisplayStyle.swift -// ChatMLX -// -// Created by John Mai on 2024/4/7. -// - -import Foundation - -enum DisplayStyle: String, CaseIterable, Identifiable { - case plain, markdown - var id: Self { self } -} diff --git a/ChatMLX/Models/DownloadTask.swift b/ChatMLX/Models/DownloadTask.swift deleted file mode 100644 index c401865..0000000 --- a/ChatMLX/Models/DownloadTask.swift +++ /dev/null @@ -1,100 +0,0 @@ -// -// DownloadTask.swift -// ChatMLX -// -// Created by John Mai on 2024/8/11. -// - -import Defaults -import Foundation -import Logging - -@Observable -class DownloadTask: Identifiable, Equatable { - let logger = Logger(label: Bundle.main.bundleIdentifier!) - - static func == (lhs: DownloadTask, rhs: DownloadTask) -> Bool { - lhs.id == rhs.id - } - - let id: UUID - let repoId: String - var progress: Double = 0 - var isDownloading = false - var isCompleted = false - var error: Error? - var hub: HubApi? - var totalUnitCount: Int64 = 0 - var completedUnitCount: Int64 = 0 - - init(_ repoId: String) { - self.id = UUID() - self.repoId = repoId - } - - func start() { - self.isDownloading = true - self.error = nil - self.progress = 0 - let currentEndpoint = Defaults[.huggingFaceEndpoint] - self.hub = HubApi( - downloadBase: FileManager.default.temporaryDirectory, endpoint: currentEndpoint) - - Task { [self] in - do { - let repo = Hub.Repo(id: self.repoId) - let temporaryModelDirectory = try await self.hub!.snapshot( - from: repo, matching: ["*.safetensors", "*.json"] - ) { progress in - Task { @MainActor in - self.progress = progress.fractionCompleted - self.totalUnitCount = progress.totalUnitCount - self.completedUnitCount = progress.completedUnitCount - } - } - - self.hub = nil - - try await moveToDocumentsDirectory(from: temporaryModelDirectory) - - await MainActor.run { - self.isDownloading = false - self.isCompleted = true - self.progress = 1.0 - } - } catch { - logger.error("DownloadTask Error: \(error.localizedDescription)") - self.hub = nil - await MainActor.run { - self.error = error - self.isDownloading = false - } - } - } - } - - func stop() { - if let hub { - hub.cancelCurrentDownload() - self.isDownloading = false - self.hub = nil - } - } - - private func moveToDocumentsDirectory(from temporaryModelDirectory: URL) async throws { - let fileManager = FileManager.default - let documents = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first! - let downloadBase = documents.appending(component: "huggingface").appending(path: "models") - - let destinationPath = downloadBase.appendingPathComponent(self.repoId) - try fileManager.createDirectory(at: destinationPath, withIntermediateDirectories: true) - - if fileManager.fileExists(atPath: destinationPath.path) { - try fileManager.removeItem(at: destinationPath) - } - - try fileManager.copyItem(at: temporaryModelDirectory, to: destinationPath) - - logger.info("Model moved to: \(destinationPath.path)") - } -} diff --git a/ChatMLX/Models/Language.swift b/ChatMLX/Models/Language.swift deleted file mode 100644 index e6cf078..0000000 --- a/ChatMLX/Models/Language.swift +++ /dev/null @@ -1,98 +0,0 @@ -// -// Language.swift -// ChatMLX -// -// Created by John Mai on 2024/8/19. -// - -import Defaults - -enum Language: String, CaseIterable, Identifiable, Defaults.Serializable { - case english = "en" - case arabic = "ar" - case chineseHongKong = "zh-HK" - case simplifiedChinese = "zh-Hans" - case traditionalChinese = "zh-Hant" - case catalan = "ca" - case croatian = "hr" - case czech = "cs" - case danish = "da" - case dutch = "nl" - case enAU = "en-AU" - case enGB = "en-GB" - case enIN = "en-IN" - case finnish = "fi" - case french = "fr" - case frenchCanadian = "fr-CA" - case de = "de" - case greek = "el" - case hebrew = "he" - case hindi = "hi" - case hungarian = "hu" - case indonesian = "id" - case italian = "it" - case japanese = "ja" - case korean = "ko" - case malay = "ms" - case norwegian = "nb" - case polish = "pl" - case portuguese = "pt-PT" - case portugueseBrazilian = "pt-BR" - case romanian = "ro" - case russian = "ru" - case slovak = "sk" - case spanish = "es" - case spanishLatinAmerica = "es-419" - case swedish = "sv" - case thai = "th" - case turkish = "tr" - case ukrainian = "uk" - case vietnamese = "vi" - - var id: String { self.rawValue } - - var displayName: String { - switch self { - case .english: return "English" - case .arabic: return "العربية" - case .chineseHongKong: return "中文(香港)" - case .simplifiedChinese: return "简体中文" - case .traditionalChinese: return "繁體中文" - case .catalan: return "Català" - case .croatian: return "Hrvatski" - case .czech: return "Čeština" - case .danish: return "Dansk" - case .dutch: return "Nederlands" - case .enAU: return "Australian English" - case .enGB: return "British English" - case .enIN: return "Indian English" - case .finnish: return "Suomi" - case .french: return "Français" - case .frenchCanadian: return "Français Canadien" - case .de: return "Deutsch" - case .greek: return "Ελληνικά" - case .hebrew: return "עברית" - case .hindi: return "हिन्दी" - case .hungarian: return "Magyar" - case .indonesian: return "Bahasa Indonesia" - case .italian: return "Italiano" - case .japanese: return "日本語" - case .korean: return "한국어" - case .malay: return "Bahasa Melayu" - case .norwegian: return "Norsk" - case .polish: return "Polski" - case .portuguese: return "Português" - case .portugueseBrazilian: return "Português Brasileiro" - case .romanian: return "Română" - case .russian: return "Русский" - case .slovak: return "Slovenčina" - case .spanish: return "Español" - case .spanishLatinAmerica: return "Español Latinoamericano" - case .swedish: return "Svenska" - case .thai: return "ไทย" - case .turkish: return "Türkçe" - case .ukrainian: return "Українська" - case .vietnamese: return "Tiếng Việt" - } - } -} diff --git a/ChatMLX/Models/LocalModel.swift b/ChatMLX/Models/LocalModel.swift deleted file mode 100644 index b5be2b9..0000000 --- a/ChatMLX/Models/LocalModel.swift +++ /dev/null @@ -1,20 +0,0 @@ -// -// LocalModel.swift -// ChatMLX -// -// Created by John Mai on 2024/8/14. -// - -import Defaults -import Foundation - -struct LocalModel: Identifiable { - let id = UUID() - let group: String - let name: String - let url: URL - - var origin: String { - "\(group)/\(name)" - } -} diff --git a/ChatMLX/Models/LocalModelGroup.swift b/ChatMLX/Models/LocalModelGroup.swift deleted file mode 100644 index 5287b76..0000000 --- a/ChatMLX/Models/LocalModelGroup.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// LocalModelGroup.swift -// ChatMLX -// -// Created by John Mai on 2024/8/14. -// - -import Foundation - -struct LocalModelGroup: Identifiable { - let id = UUID() - let name: String - var models: [LocalModel] -} diff --git a/ChatMLX/Models/Message+CoreDataClass.swift b/ChatMLX/Models/Message+CoreDataClass.swift deleted file mode 100644 index f87083e..0000000 --- a/ChatMLX/Models/Message+CoreDataClass.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// Message+CoreDataClass.swift -// ChatMLX -// -// Created by John Mai on 2024/10/2. -// -// - -import CoreData -import Foundation - -@objc(Message) -public class Message: NSManagedObject { - @discardableResult - func user(content: String, conversation: Conversation?) -> Self { - self.role = .user - self.content = content - if let conversation { - self.conversation = conversation - } - return self - } - - @discardableResult - func assistant(conversation: Conversation?) -> Self { - self.role = .assistant - self.inferring = true - self.content = "" - if let conversation { - self.conversation = conversation - } - return self - } - - func format() -> [String: String] { - [ - "role": self.roleRaw, - "content": self.content, - ] - } -} diff --git a/ChatMLX/Models/Message+CoreDataProperties.swift b/ChatMLX/Models/Message+CoreDataProperties.swift deleted file mode 100644 index b0ef408..0000000 --- a/ChatMLX/Models/Message+CoreDataProperties.swift +++ /dev/null @@ -1,46 +0,0 @@ -// -// Message+CoreDataProperties.swift -// ChatMLX -// -// Created by John Mai on 2024/10/2. -// -// - -import CoreData -import Foundation - -extension Message { - @nonobjc public class func fetchRequest() -> NSFetchRequest { - NSFetchRequest(entityName: "Message") - } - - @NSManaged public var roleRaw: String - - public var role: Role { - set { - roleRaw = newValue.rawValue - } - get { - Role(rawValue: roleRaw) ?? .assistant - } - } - - @NSManaged public var content: String - @NSManaged public var createdAt: Date - @NSManaged public var inferring: Bool - @NSManaged public var updatedAt: Date - @NSManaged public var error: String? - @NSManaged public var conversation: Conversation - - public override func awakeFromInsert() { - setPrimitiveValue(Date.now, forKey: #keyPath(Message.createdAt)) - setPrimitiveValue(Date.now, forKey: #keyPath(Message.updatedAt)) - } - - public override func willSave() { - super.willSave() - setPrimitiveValue(Date.now, forKey: #keyPath(Message.updatedAt)) - } -} - -extension Message: Identifiable {} diff --git a/ChatMLX/Models/RemoteModel.swift b/ChatMLX/Models/RemoteModel.swift deleted file mode 100644 index 44c9ec2..0000000 --- a/ChatMLX/Models/RemoteModel.swift +++ /dev/null @@ -1,63 +0,0 @@ -// -// RemoteModel.swift -// ChatMLX -// -// Created by John Mai on 2024/8/11. -// - -import Foundation - -struct RemoteModel: Codable, Identifiable { - let id: String - let repoId: String - let modelId: String - let likes: Int - let trendingScore: Int? - let isPrivate: Bool - let downloads: Int - let tags: [String] - let pipelineTag: String? - let libraryName: String? - let createdAt: Date - - private enum CodingKeys: String, CodingKey { - case id = "_id" - case repoId = "id" - case likes - case trendingScore - case isPrivate = "private" - case downloads - case tags - case pipelineTag = "pipeline_tag" - case libraryName = "library_name" - case createdAt - case modelId - } - - init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - - id = try container.decode(String.self, forKey: .id) - repoId = try container.decode(String.self, forKey: .repoId) - modelId = try container.decode(String.self, forKey: .modelId) - likes = try container.decode(Int.self, forKey: .likes) - trendingScore = try container.decodeIfPresent(Int.self, forKey: .trendingScore) ?? 0 - isPrivate = try container.decode(Bool.self, forKey: .isPrivate) - downloads = try container.decode(Int.self, forKey: .downloads) - tags = try container.decode([String].self, forKey: .tags) - pipelineTag = try container.decodeIfPresent(String.self, forKey: .pipelineTag) - libraryName = try container.decodeIfPresent(String.self, forKey: .libraryName) - - let dateString = try container.decode(String.self, forKey: .createdAt) - let dateFormatter = DateFormatter() - - dateFormatter.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZ" - if let date = dateFormatter.date(from: dateString) { - createdAt = date - } else { - throw DecodingError.dataCorruptedError( - forKey: .createdAt, in: container, - debugDescription: "Date string does not match expected format") - } - } -} diff --git a/ChatMLX/Models/Role.swift b/ChatMLX/Models/Role.swift deleted file mode 100644 index 14819da..0000000 --- a/ChatMLX/Models/Role.swift +++ /dev/null @@ -1,16 +0,0 @@ -// -// Role.swift -// ChatMLX -// -// Created by John Mai on 2024/10/3. -// - -public enum Role: String, Codable { - case user - case assistant - case system - - var description: String { - "\(self)" - } -} diff --git a/ChatMLX/Models/SettingsTab.swift b/ChatMLX/Models/SettingsTab.swift deleted file mode 100644 index 61629ea..0000000 --- a/ChatMLX/Models/SettingsTab.swift +++ /dev/null @@ -1,55 +0,0 @@ -// -// SettingsTab.swift -// ChatMLX -// -// Created by John Mai on 2024/8/10. -// - -import SwiftUI - -struct SettingsTab: Identifiable, Equatable { - public static func == (lhs: SettingsTab, rhs: SettingsTab) -> Bool { - rhs.id == lhs.id - } - - enum ID: String { - case general = "General" - case defaultConversation = "Default Conversation" - case huggingFace = "Hugging Face" - case models = "Models" - case mlxCommunity = "MLX Community" - case downloadManager = "Download Manager" - case experimentalFeatures = "Experimental Features" - case about = "About" - } - - let id: ID - let icon: Image - let showIndicator: ((SettingsViewModel) -> Bool)? - - init(_ id: ID, _ icon: Image, showIndicator: ((SettingsViewModel) -> Bool)? = nil) { - self.id = id - self.icon = icon - self.showIndicator = showIndicator - } - - func iconView() -> some View { - Rectangle() - .opacity(0) - .overlay { - icon - .resizable() - .scaledToFit() - .frame(width: 18, height: 18) - } - .aspectRatio(1, contentMode: .fit) - .padding(10) - .fixedSize() - .background(.quinary) - .clipShape(.rect(cornerRadius: 8)) - .overlay { - RoundedRectangle(cornerRadius: 8) - .strokeBorder(.quaternary, lineWidth: 1) - } - } -} diff --git a/ChatMLX/Models/SettingsTabGroup.swift b/ChatMLX/Models/SettingsTabGroup.swift deleted file mode 100644 index f7a842e..0000000 --- a/ChatMLX/Models/SettingsTabGroup.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// SettingsTabGroup.swift -// ChatMLX -// -// Created by John Mai on 2024/8/10. -// - -import SwiftUI - -struct SettingsTabGroup: Identifiable { - public var id: UUID = .init() - - let title: LocalizedStringKey? - let tabs: [SettingsTab] - - init(_ title: LocalizedStringKey, _ tabs: [SettingsTab]) { - self.title = title - self.tabs = tabs - } - - init(_ tabs: [SettingsTab]) { - self.title = nil - self.tabs = tabs - } -} diff --git a/ChatMLX/Models/Styles.swift b/ChatMLX/Models/Styles.swift deleted file mode 100644 index 37295cd..0000000 --- a/ChatMLX/Models/Styles.swift +++ /dev/null @@ -1,12 +0,0 @@ -// -// Styles.swift -// ChatMLX -// -// Created by John Mai on 2024/8/31. -// - -import Foundation - -class Styles { - static let iconButtonSize: CGFloat = 15 -} diff --git a/ChatMLX/Utilities/Huggingface/Downloader.swift b/ChatMLX/Utilities/Huggingface/Downloader.swift deleted file mode 100644 index 25a7469..0000000 --- a/ChatMLX/Utilities/Huggingface/Downloader.swift +++ /dev/null @@ -1,142 +0,0 @@ -// -// Downloader.swift -// -// Adapted from https://github.com/huggingface/swift-coreml-diffusers/blob/d041577b9f5e201baa3465bc60bc5d0a1cf7ed7f/Diffusion/Common/Downloader.swift -// Created by Pedro Cuenca on December 2022. -// See LICENSE at https://github.com/huggingface/swift-coreml-diffusers/LICENSE -// - -import Combine -import Foundation - -class Downloader: NSObject, ObservableObject { - private(set) var destination: URL - - enum DownloadState { - case notStarted - case downloading(Double) - case completed(URL) - case failed(Error) - } - - enum DownloadError: Error { - case invalidDownloadLocation - case unexpectedError - } - - private(set) lazy var downloadState: CurrentValueSubject = - CurrentValueSubject(.notStarted) - private var stateSubscriber: Cancellable? - - private var urlSession: URLSession? = nil - - init( - from url: URL, to destination: URL, using authToken: String? = nil, - inBackground: Bool = false - ) { - self.destination = destination - super.init() - let sessionIdentifier = "swift-transformers.hub.downloader" - - var config = URLSessionConfiguration.default - if inBackground { - config = URLSessionConfiguration.background(withIdentifier: sessionIdentifier) - config.isDiscretionary = false - config.sessionSendsLaunchEvents = true - } - - self.urlSession = URLSession(configuration: config, delegate: self, delegateQueue: nil) - - setupDownload(from: url, with: authToken) - } - - private func setupDownload(from url: URL, with authToken: String?) { - downloadState.value = .downloading(0) - urlSession?.getAllTasks { tasks in - // If there's an existing pending background task with the same URL, let it proceed. - if let existing = tasks.filter({ $0.originalRequest?.url == url }).first { - switch existing.state { - case .running: - return - case .suspended: - existing.resume() - return - case .canceling: - break - case .completed: - break - @unknown default: - existing.cancel() - } - } - var request = URLRequest(url: url) - if let authToken = authToken { - request.setValue("Bearer \(authToken)", forHTTPHeaderField: "Authorization") - } - - self.urlSession?.downloadTask(with: request).resume() - } - } - - @discardableResult - func waitUntilDone() throws -> URL { - // It's either this, or stream the bytes ourselves (add to a buffer, save to disk, etc; boring and finicky) - let semaphore = DispatchSemaphore(value: 0) - stateSubscriber = downloadState.sink { state in - switch state { - case .completed: semaphore.signal() - case .failed: semaphore.signal() - default: break - } - } - semaphore.wait() - - switch downloadState.value { - case .completed(let url): return url - case .failed(let error): throw error - default: throw DownloadError.unexpectedError - } - } - - func cancel() { - urlSession?.invalidateAndCancel() - } -} - -extension Downloader: URLSessionDownloadDelegate { - func urlSession( - _: URLSession, downloadTask: URLSessionDownloadTask, didWriteData _: Int64, - totalBytesWritten: Int64, totalBytesExpectedToWrite: Int64 - ) { - downloadState.value = .downloading( - Double(totalBytesWritten) / Double(totalBytesExpectedToWrite)) - } - - func urlSession( - _: URLSession, downloadTask _: URLSessionDownloadTask, didFinishDownloadingTo location: URL - ) { - do { - // If the downloaded file already exists on the filesystem, overwrite it - try FileManager.default.moveDownloadedFile(from: location, to: self.destination) - downloadState.value = .completed(destination) - } catch { - downloadState.value = .failed(error) - } - } - - func urlSession(_ session: URLSession, task: URLSessionTask, didCompleteWithError error: Error?) - { - if let error = error { - downloadState.value = .failed(error) - } - } -} - -extension FileManager { - func moveDownloadedFile(from srcURL: URL, to dstURL: URL) throws { - if fileExists(atPath: dstURL.path) { - try removeItem(at: dstURL) - } - try moveItem(at: srcURL, to: dstURL) - } -} diff --git a/ChatMLX/Utilities/Huggingface/Hub.swift b/ChatMLX/Utilities/Huggingface/Hub.swift deleted file mode 100644 index 84b50e8..0000000 --- a/ChatMLX/Utilities/Huggingface/Hub.swift +++ /dev/null @@ -1,222 +0,0 @@ -// -// Hub.swift -// -// -// Created by Pedro Cuenca on 18/5/23. -// - -import Foundation - -public struct Hub {} - -extension Hub { - public enum HubClientError: Error { - case parse - case authorizationRequired - case unexpectedError - case httpStatusCode(Int) - } - - public enum RepoType: String { - case models - case datasets - case spaces - } - - public struct Repo { - let id: String - let type: RepoType - - public init(id: String, type: RepoType = .models) { - self.id = id - self.type = type - } - } -} - -// MARK: - Configuration files with dynamic lookup - -@dynamicMemberLookup -public struct Config { - public private(set) var dictionary: [String: Any] - - public init(_ dictionary: [String: Any]) { - self.dictionary = dictionary - } - - func camelCase(_ string: String) -> String { - return - string - .split(separator: "_") - .enumerated() - .map { $0.offset == 0 ? $0.element.lowercased() : $0.element.capitalized } - .joined() - } - - func uncamelCase(_ string: String) -> String { - let scalars = string.unicodeScalars - var result = "" - - var previousCharacterIsLowercase = false - for scalar in scalars { - if CharacterSet.uppercaseLetters.contains(scalar) { - if previousCharacterIsLowercase { - result += "_" - } - let lowercaseChar = Character(scalar).lowercased() - result += lowercaseChar - previousCharacterIsLowercase = false - } else { - result += String(scalar) - previousCharacterIsLowercase = true - } - } - - return result - } - - public subscript(dynamicMember member: String) -> Config? { - let key = dictionary[member] != nil ? member : uncamelCase(member) - if let value = dictionary[key] as? [String: Any] { - return Config(value) - } else if let value = dictionary[key] { - return Config(["value": value]) - } - return nil - } - - public var value: Any? { - return dictionary["value"] - } - - public var intValue: Int? { value as? Int } - public var boolValue: Bool? { value as? Bool } - public var stringValue: String? { value as? String } - - // Instead of doing this we could provide custom classes and decode to them - public var arrayValue: [Config]? { - guard let list = value as? [Any] else { return nil } - return list.map { Config($0 as! [String: Any]) } - } - - /// Tuple of token identifier and string value - public var tokenValue: (UInt, String)? { value as? (UInt, String) } -} - -public class LanguageModelConfigurationFromHub { - struct Configurations { - var modelConfig: Config - var tokenizerConfig: Config? - var tokenizerData: Config - } - - private var configPromise: Task? = nil - - public init( - modelName: String, - hubApi: HubApi = .shared - ) { - self.configPromise = Task.init { - return try await self.loadConfig(modelName: modelName, hubApi: hubApi) - } - } - - public init( - modelFolder: URL, - hubApi: HubApi = .shared - ) { - self.configPromise = Task { - return try await self.loadConfig(modelFolder: modelFolder, hubApi: hubApi) - } - } - - public var modelConfig: Config { - get async throws { - try await configPromise!.value.modelConfig - } - } - - public var tokenizerConfig: Config? { - get async throws { - if let hubConfig = try await configPromise!.value.tokenizerConfig { - // Try to guess the class if it's not present and the modelType is - if let _ = hubConfig.tokenizerClass?.stringValue { return hubConfig } - guard let modelType = try await modelType else { return hubConfig } - - // If the config exists but doesn't contain a tokenizerClass, use a fallback config if we have it - if let fallbackConfig = Self.fallbackTokenizerConfig(for: modelType) { - let configuration = fallbackConfig.dictionary.merging( - hubConfig.dictionary, uniquingKeysWith: { current, _ in current }) - return Config(configuration) - } - - // Guess by capitalizing - var configuration = hubConfig.dictionary - configuration["tokenizer_class"] = "\(modelType.capitalized)Tokenizer" - return Config(configuration) - } - - // Fallback tokenizer config, if available - guard let modelType = try await modelType else { return nil } - return Self.fallbackTokenizerConfig(for: modelType) - } - } - - public var tokenizerData: Config { - get async throws { - try await configPromise!.value.tokenizerData - } - } - - public var modelType: String? { - get async throws { - try await modelConfig.modelType?.stringValue - } - } - - func loadConfig( - modelName: String, - hubApi: HubApi = .shared - ) async throws -> Configurations { - let filesToDownload = ["config.json", "tokenizer_config.json", "tokenizer.json"] - let repo = Hub.Repo(id: modelName) - let downloadedModelFolder = try await hubApi.snapshot(from: repo, matching: filesToDownload) - - return try await loadConfig(modelFolder: downloadedModelFolder, hubApi: hubApi) - } - - func loadConfig( - modelFolder: URL, - hubApi: HubApi = .shared - ) async throws -> Configurations { - // Note tokenizerConfig may be nil (does not exist in all models) - let modelConfig = try hubApi.configuration( - fileURL: modelFolder.appending(path: "config.json")) - let tokenizerConfig = try? hubApi.configuration( - fileURL: modelFolder.appending(path: "tokenizer_config.json")) - let tokenizerVocab = try hubApi.configuration( - fileURL: modelFolder.appending(path: "tokenizer.json")) - - let configs = Configurations( - modelConfig: modelConfig, - tokenizerConfig: tokenizerConfig, - tokenizerData: tokenizerVocab - ) - return configs - } - - static func fallbackTokenizerConfig(for modelType: String) -> Config? { - guard - let url = Bundle.main.url( - forResource: "\(modelType)_tokenizer_config", withExtension: "json") - else { return nil } - do { - let data = try Data(contentsOf: url) - let parsed = try JSONSerialization.jsonObject(with: data, options: []) - guard let dictionary = parsed as? [String: Any] else { return nil } - return Config(dictionary) - } catch { - return nil - } - } -} diff --git a/ChatMLX/Utilities/Huggingface/HubApi.swift b/ChatMLX/Utilities/Huggingface/HubApi.swift deleted file mode 100644 index f419f43..0000000 --- a/ChatMLX/Utilities/Huggingface/HubApi.swift +++ /dev/null @@ -1,359 +0,0 @@ -// -// HubApi.swift -// -// -// Created by Pedro Cuenca on 20231230. -// - -import Foundation - -public class HubApi { - var downloadBase: URL - var hfToken: String? - var endpoint: String - var useBackgroundSession: Bool - - private var currentTask: Task? - - public typealias RepoType = Hub.RepoType - public typealias Repo = Hub.Repo - - public init( - downloadBase: URL? = nil, hfToken: String? = nil, - endpoint: String = "https://huggingface.co", useBackgroundSession: Bool = false - ) { - self.hfToken = hfToken - if let downloadBase { - self.downloadBase = downloadBase - } else { - let documents = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask) - .first! - self.downloadBase = documents.appending(component: "huggingface") - } - self.endpoint = endpoint - self.useBackgroundSession = useBackgroundSession - } - - public static let shared = HubApi() -} - -/// File retrieval -extension HubApi { - /// Model data for parsed filenames - public struct Sibling: Codable { - let rfilename: String - } - - public struct SiblingsResponse: Codable { - let siblings: [Sibling] - } - - /// Throws error if the response code is not 20X - public func httpGet(for url: URL) async throws -> (Data, HTTPURLResponse) { - var request = URLRequest(url: url) - if let hfToken = hfToken { - request.setValue("Bearer \(hfToken)", forHTTPHeaderField: "Authorization") - } - let (data, response) = try await URLSession.shared.data(for: request) - guard let response = response as? HTTPURLResponse else { - throw Hub.HubClientError.unexpectedError - } - - switch response.statusCode { - case 200 ..< 300: break - case 400 ..< 500: throw Hub.HubClientError.authorizationRequired - default: throw Hub.HubClientError.httpStatusCode(response.statusCode) - } - - return (data, response) - } - - public func getFilenames(from repo: Repo, matching globs: [String] = []) async throws - -> [String] - { - // Read repo info and only parse "siblings" - let url = URL(string: "\(endpoint)/api/\(repo.type)/\(repo.id)")! - let (data, _) = try await httpGet(for: url) - let response = try JSONDecoder().decode(SiblingsResponse.self, from: data) - let filenames = response.siblings.map { $0.rfilename } - guard globs.count > 0 else { return filenames } - - var selected: Set = [] - for glob in globs { - selected = selected.union(filenames.matching(glob: glob)) - } - return Array(selected) - } - - public func getFilenames(from repoId: String, matching globs: [String] = []) async throws - -> [String] - { - return try await getFilenames(from: Repo(id: repoId), matching: globs) - } - - public func getFilenames(from repo: Repo, matching glob: String) async throws -> [String] { - return try await getFilenames(from: repo, matching: [glob]) - } - - public func getFilenames(from repoId: String, matching glob: String) async throws -> [String] { - return try await getFilenames(from: Repo(id: repoId), matching: [glob]) - } -} - -/// Configuration loading helpers -extension HubApi { - /// Assumes the file has already been downloaded. - /// `filename` is relative to the download base. - public func configuration(from filename: String, in repo: Repo) throws -> Config { - let fileURL = localRepoLocation(repo).appending(path: filename) - return try configuration(fileURL: fileURL) - } - - /// Assumes the file is already present at local url. - /// `fileURL` is a complete local file path for the given model - public func configuration(fileURL: URL) throws -> Config { - let data = try Data(contentsOf: fileURL) - let parsed = try JSONSerialization.jsonObject(with: data, options: []) - guard let dictionary = parsed as? [String: Any] else { throw Hub.HubClientError.parse } - return Config(dictionary) - } -} - -/// Whoami -extension HubApi { - public func whoami() async throws -> Config { - guard hfToken != nil else { throw Hub.HubClientError.authorizationRequired } - - let url = URL(string: "\(endpoint)/api/whoami-v2")! - let (data, _) = try await httpGet(for: url) - - let parsed = try JSONSerialization.jsonObject(with: data, options: []) - guard let dictionary = parsed as? [String: Any] else { throw Hub.HubClientError.parse } - return Config(dictionary) - } -} - -/// Snaphsot download -extension HubApi { - public func localRepoLocation(_ repo: Repo) -> URL { - downloadBase.appending(component: repo.type.rawValue).appending(component: repo.id) - } - - public struct HubFileDownloader { - let repo: Repo - let repoDestination: URL - let relativeFilename: String - let hfToken: String? - let endpoint: String? - let backgroundSession: Bool - - var source: URL { - // https://huggingface.co/coreml-projects/Llama-2-7b-chat-coreml/resolve/main/tokenizer.json?download=true - var url = URL(string: endpoint ?? "https://huggingface.co")! - if repo.type != .models { - url = url.appending(component: repo.type.rawValue) - } - url = url.appending(path: repo.id) - url = url.appending(path: "resolve/main") // TODO: revisions - url = url.appending(path: relativeFilename) - return url - } - - var destination: URL { - repoDestination.appending(path: relativeFilename) - } - - var downloaded: Bool { - FileManager.default.fileExists(atPath: destination.path) - } - - func prepareDestination() throws { - let directoryURL = destination.deletingLastPathComponent() - try FileManager.default.createDirectory( - at: directoryURL, withIntermediateDirectories: true, attributes: nil) - } - - // Note we go from Combine in Downloader to callback-based progress reporting - // We'll probably need to support Combine as well to play well with Swift UI - // (See for example PipelineLoader in swift-coreml-diffusers) - // @discardableResult - // func download(progressHandler: @escaping (Double) -> Void) async throws -> URL { - // guard !downloaded else { return destination } - // - // try prepareDestination() - // let downloader = Downloader(from: source, to: destination, using: hfToken, inBackground: backgroundSession) - // let downloadSubscriber = downloader.downloadState.sink { state in - // if case .downloading(let progress) = state { - // progressHandler(progress) - // } - // } - // _ = try withExtendedLifetime(downloadSubscriber) { - // try downloader.waitUntilDone() - // } - // return destination - // } - @discardableResult - func download(progressHandler: @escaping (Double) throws -> Void) async throws -> URL { - guard !downloaded else { return destination } - - try prepareDestination() - let downloader = Downloader( - from: source, to: destination, using: hfToken, inBackground: backgroundSession) - let downloadSubscriber = downloader.downloadState.sink { state in - if case .downloading(let progress) = state { - do { - try progressHandler(progress) - } catch { - downloader.cancel() - } - } - } - return try await withTaskCancellationHandler { - try withExtendedLifetime(downloadSubscriber) { - try downloader.waitUntilDone() - } - } onCancel: { - downloader.cancel() - } - } - } - - @discardableResult - public func snapshot( - from repoId: String, matching globs: [String] = [], - progressHandler: @escaping (Progress) -> Void = { _ in } - ) async throws -> URL { - return try await snapshot( - from: Repo(id: repoId), matching: globs, progressHandler: progressHandler) - } - - @discardableResult - public func snapshot( - from repo: Repo, matching glob: String, - progressHandler: @escaping (Progress) -> Void = { _ in } - ) async throws -> URL { - return try await snapshot(from: repo, matching: [glob], progressHandler: progressHandler) - } - - @discardableResult - public func snapshot( - from repoId: String, matching glob: String, - progressHandler: @escaping (Progress) -> Void = { _ in } - ) async throws -> URL { - return try await snapshot( - from: Repo(id: repoId), matching: [glob], progressHandler: progressHandler) - } -} - -/// Stateless wrappers that use `HubApi` instances -extension Hub { - public static func getFilenames(from repo: Hub.Repo, matching globs: [String] = []) async throws - -> [String] - { - return try await HubApi.shared.getFilenames(from: repo, matching: globs) - } - - public static func getFilenames(from repoId: String, matching globs: [String] = []) async throws - -> [String] - { - return try await HubApi.shared.getFilenames(from: Repo(id: repoId), matching: globs) - } - - public static func getFilenames(from repo: Repo, matching glob: String) async throws -> [String] - { - return try await HubApi.shared.getFilenames(from: repo, matching: glob) - } - - public static func getFilenames(from repoId: String, matching glob: String) async throws - -> [String] - { - return try await HubApi.shared.getFilenames(from: Repo(id: repoId), matching: glob) - } - - public static func snapshot( - from repo: Repo, matching globs: [String] = [], - progressHandler: @escaping (Progress) -> Void = { _ in } - ) async throws -> URL { - return try await HubApi.shared.snapshot( - from: repo, matching: globs, progressHandler: progressHandler) - } - - public static func snapshot( - from repoId: String, matching globs: [String] = [], - progressHandler: @escaping (Progress) -> Void = { _ in } - ) async throws -> URL { - return try await HubApi.shared.snapshot( - from: Repo(id: repoId), matching: globs, progressHandler: progressHandler) - } - - public static func snapshot( - from repo: Repo, matching glob: String, - progressHandler: @escaping (Progress) -> Void = { _ in } - ) async throws -> URL { - return try await HubApi.shared.snapshot( - from: repo, matching: glob, progressHandler: progressHandler) - } - - public static func snapshot( - from repoId: String, matching glob: String, - progressHandler: @escaping (Progress) -> Void = { _ in } - ) async throws -> URL { - return try await HubApi.shared.snapshot( - from: Repo(id: repoId), matching: glob, progressHandler: progressHandler) - } - - public static func whoami(token: String) async throws -> Config { - return try await HubApi(hfToken: token).whoami() - } -} - -extension [String] { - public func matching(glob: String) -> [String] { - filter { fnmatch(glob, $0, 0) == 0 } - } -} - -extension HubApi { - @discardableResult - public func snapshot( - from repo: Repo, matching globs: [String] = [], - progressHandler: @escaping (Progress) -> Void = { _ in } - ) async throws -> URL { - self.currentTask = Task { - let filenames = try await getFilenames(from: repo, matching: globs) - let progress = Progress(totalUnitCount: Int64(filenames.count)) - let repoDestination = localRepoLocation(repo) - for filename in filenames { - if Task.isCancelled { throw CancellationError() } - let fileProgress = Progress( - totalUnitCount: 100, parent: progress, pendingUnitCount: 1) - let downloader = HubFileDownloader( - repo: repo, - repoDestination: repoDestination, - relativeFilename: filename, - hfToken: hfToken, - endpoint: endpoint, - backgroundSession: useBackgroundSession - ) - - try await downloader.download { fractionDownloaded in - do { - if Task.isCancelled { throw CancellationError() } - fileProgress.completedUnitCount = Int64(100 * fractionDownloaded) - progressHandler(progress) - } catch { - throw error - } - } - fileProgress.completedUnitCount = 100 - } - progressHandler(progress) - return repoDestination - } - return try await currentTask!.value - } - - public func cancelCurrentDownload() { - currentTask?.cancel() - } -} diff --git a/ChatMLX/Utilities/LLMRunner.swift b/ChatMLX/Utilities/LLMRunner.swift deleted file mode 100644 index 92df6d1..0000000 --- a/ChatMLX/Utilities/LLMRunner.swift +++ /dev/null @@ -1,219 +0,0 @@ -// -// LLMRunner.swift -// ChatMLX -// -// Created by John Mai on 2024/8/24. -// - -import Defaults -import MLX -import MLXLLM -import MLXRandom -import Metal -import SwiftUI -import Tokenizers - -@Observable -@MainActor -class LLMRunner { - var running = false - var model: String? - - enum LoadState { - case idle - case loaded(ModelContainer) - } - - var loadState: LoadState = .idle - - var modelConfiguration: ModelConfiguration? - - var gpuActiveMemory: Int = 0 - - let displayEveryNTokens = 4 - - init() {} - - private func load() async throws -> ModelContainer? { - guard let modelConfiguration else { - throw LLMRunnerError.modelConfigurationNotSet - } - - switch loadState { - case .idle: - let cacheLimit = - UserDefaults.standard.integer( - forKey: Defaults.Keys.gpuCacheLimit.name) * 1024 * 1024 - MLX.GPU.set(cacheLimit: cacheLimit) - - let modelContainer = try await MLXLLM.loadModelContainer( - configuration: modelConfiguration - ) - - withAnimation { - gpuActiveMemory = MLX.GPU.activeMemory / 1024 / 1024 - } - - loadState = .loaded(modelContainer) - return modelContainer - - case .loaded(let modelContainer): - return modelContainer - } - } - - private func switchModel(_ conversation: Conversation) { - if conversation.model != modelConfiguration?.name { - loadState = .idle - modelConfiguration = ModelConfiguration.configuration( - id: conversation.model) - } - } - - func prepare(_ conversation: Conversation) -> [[String: String]] { - var messages = conversation.messages - if conversation.useMaxMessagesLimit { - let maxCount = conversation.maxMessagesLimit + 1 - if messages.count > maxCount { - messages = Array(messages.suffix(Int(maxCount))) - if messages.first?.role != .user { - messages = Array(messages.dropFirst()) - } - } - } - - var dictionary = messages[..<(messages.count - 1)].map { - message -> [String: String] in - message.format() - } - - if conversation.useSystemPrompt, !conversation.systemPrompt.isEmpty { - dictionary.insert( - formatMessage( - role: .system, - content: conversation.systemPrompt - ), - at: 0 - ) - } - - return dictionary - } - - func formatMessage(role: Role, content: String) -> [String: String] { - [ - "role": role.rawValue, - "content": content, - ] - } - - func generate( - conversation: Conversation, in context: NSManagedObjectContext, - progressing: @escaping () -> Void = {}, - completion: (() -> Void)? - ) { - guard !running else { return } - withAnimation { - running = true - } - - let assistantMessage = Message(context: context).assistant(conversation: conversation) - - let parameters = GenerateParameters( - temperature: conversation.temperature, - topP: conversation.topP, - repetitionPenalty: conversation.useRepetitionPenalty - ? conversation.repetitionPenalty : nil, - repetitionContextSize: Int(conversation.repetitionContextSize) - ) - - let useMaxLength = conversation.useMaxLength - let maxLength = conversation.maxLength - - Task { - do { - switchModel(conversation) - - if let modelConfiguration { - guard let modelContainer = try await load() else { - throw LLMRunnerError.failedToLoadModel - } - - let messages = prepare(conversation) - - logger.info("prepare messages -> \(messages)") - - let tokens = try await modelContainer.perform { _, tokenizer in - try tokenizer.applyChatTemplate(messages: messages) - } - - MLXRandom.seed(UInt64(Date.timeIntervalSinceReferenceDate * 1000)) - - let result = await modelContainer.perform { model, tokenizer in - MLXLLM.generate( - promptTokens: tokens, - parameters: parameters, - model: model, - tokenizer: tokenizer, - extraEOSTokens: modelConfiguration.extraEOSTokens.union([ - "<|im_end|>", "<|end|>", - ]) - ) { tokens in - if tokens.count % displayEveryNTokens == 0 { - let text = tokenizer.decode(tokens: tokens) - Task { @MainActor in - assistantMessage.content = text - progressing() - } - } - - if useMaxLength, tokens.count >= maxLength { - return .stop - } - - return .more - } - } - - conversation.promptTime = result.promptTime - conversation.generateTime = result.generateTime - conversation.promptTokensPerSecond = result.promptTokensPerSecond - conversation.tokensPerSecond = result.tokensPerSecond - - await MainActor.run { - if result.output != assistantMessage.content { - assistantMessage.content = result.output - } - - assistantMessage.inferring = false - running = false - } - } - } catch { - logger.error("LLM Generate Failed: \(error.localizedDescription)") - await MainActor.run { - assistantMessage.inferring = false - assistantMessage.error = error.localizedDescription - withAnimation { - running = false - } - } - } - - Task(priority: .background) { - await context.perform { - if context.hasChanges { - try? context.save() - } - } - } - - completion?() - } - } -} - -enum LLMRunnerError: Error { - case modelConfigurationNotSet - case failedToLoadModel -} diff --git a/ChatMLX/Utilities/Logger.swift b/ChatMLX/Utilities/Logger.swift deleted file mode 100644 index 098a56a..0000000 --- a/ChatMLX/Utilities/Logger.swift +++ /dev/null @@ -1,11 +0,0 @@ -// -// Logger.swift -// ChatMLX -// -// Created by John Mai on 2024/8/29. -// - -import Foundation -import Logging - -let logger = Logger(label: Bundle.main.bundleIdentifier!) diff --git a/ChatMLX/Utilities/PersistenceController.swift b/ChatMLX/Utilities/PersistenceController.swift deleted file mode 100644 index f57a70d..0000000 --- a/ChatMLX/Utilities/PersistenceController.swift +++ /dev/null @@ -1,74 +0,0 @@ -// -// Persistence.swift -// ChatMLX -// -// Created by John Mai on 2024/10/2. -// - -import CoreData - -struct PersistenceController { - static let shared = PersistenceController() - - let container: NSPersistentContainer - - init(inMemory: Bool = false) { - container = NSPersistentContainer(name: "ChatMLX") - if inMemory { - container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") - } - container.loadPersistentStores(completionHandler: { _, error in - if let error = error as NSError? { - fatalError("Unresolved error \(error), \(error.userInfo)") - } - }) - container.viewContext.automaticallyMergesChangesFromParent = true - } - - func exisits( - _ model: T, - in context: NSManagedObjectContext - ) -> T? { - try? context.existingObject(with: model.objectID) as? T - } - - func delete(_ model: some NSManagedObject) throws { - if let existingContact = exisits(model, in: container.viewContext) { - container.viewContext.delete(existingContact) - Task(priority: .background) { - try await container.viewContext.perform { - try container.viewContext.save() - } - } - } - } - - func clear(_ entityName: String) throws -> [NSManagedObjectID] { - let fetchRequest: NSFetchRequest = NSFetchRequest( - entityName: entityName) - let batchDeteleRequest = NSBatchDeleteRequest(fetchRequest: fetchRequest) - batchDeteleRequest.resultType = .resultTypeObjectIDs - - if let fetchResult = try container.viewContext.execute(batchDeteleRequest) - as? NSBatchDeleteResult, - let deletedManagedObjectIds = fetchResult.result as? [NSManagedObjectID], - !deletedManagedObjectIds.isEmpty - { - return deletedManagedObjectIds - } - - return [] - } - - func save() throws { - Task(priority: .background) { - let context = container.viewContext - - try await context.perform { - if context.hasChanges { - try context.save() - } - } - } - } -} diff --git a/Packages/Common/.gitignore b/Packages/Common/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Packages/Common/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/Common/Package.swift b/Packages/Common/Package.swift new file mode 100644 index 0000000..16292aa --- /dev/null +++ b/Packages/Common/Package.swift @@ -0,0 +1,36 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Common", + platforms: [.macOS(.v14)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "Common", + targets: ["Common"]) + ], + dependencies: [ + .package(name: "HuggingfaceHub", path: "../../../../maiqingqiang/HuggingfaceHub"), + .package(name: "Models", path: "../Models"), + .package(url: "https://github.com/sindresorhus/Defaults", from: "9.0.2"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "Common", + dependencies: [ + "Models", + "Defaults", + "HuggingfaceHub", + ] + ), + .testTarget( + name: "CommonTests", + dependencies: ["Common"] + ), + ] +) diff --git a/Packages/Common/Sources/Common/AppStore.swift b/Packages/Common/Sources/Common/AppStore.swift new file mode 100644 index 0000000..df4af86 --- /dev/null +++ b/Packages/Common/Sources/Common/AppStore.swift @@ -0,0 +1,15 @@ +// +// AppStore.swift +// Common +// +// Created by John Mai on 2025/2/27. +// + +import Foundation +import Models + +@MainActor +@Observable +final class AppStore { + var models: [Model] = [] +} diff --git a/ChatMLX/Extensions/Binding+Extensions.swift b/Packages/Common/Sources/Common/Extensions/Binding+Extensions.swift similarity index 55% rename from ChatMLX/Extensions/Binding+Extensions.swift rename to Packages/Common/Sources/Common/Extensions/Binding+Extensions.swift index 3d058a1..a303fc8 100644 --- a/ChatMLX/Extensions/Binding+Extensions.swift +++ b/Packages/Common/Sources/Common/Extensions/Binding+Extensions.swift @@ -1,15 +1,14 @@ // // Binding+Extensions.swift -// ChatMLX +// Common // -// Created by John Mai on 2024/10/2. +// Created by John Mai on 2025/3/1. // -import Foundation import SwiftUI extension Binding { - func toUnwrapped(defaultValue: T) -> Binding where Value == T? { + public func toUnwrapped(defaultValue: T) -> Binding where Value == T? { Binding(get: { self.wrappedValue ?? defaultValue }, set: { self.wrappedValue = $0 }) } } diff --git a/Packages/Common/Sources/Common/Extensions/Date+Extensions.swift b/Packages/Common/Sources/Common/Extensions/Date+Extensions.swift new file mode 100644 index 0000000..2a203a9 --- /dev/null +++ b/Packages/Common/Sources/Common/Extensions/Date+Extensions.swift @@ -0,0 +1,32 @@ +// +// Date+Extensions.swift +// UltraUI +// +// Created by John Mai on 2025/2/22. +// + +import Foundation + +extension Date { + func formatted() -> String { + let now = Date() + let calendar = Calendar.current + + let yearDiff = calendar.dateComponents([.year], from: self, to: now).year ?? 0 + let dayDiff = calendar.dateComponents([.day], from: self, to: now).day ?? 0 + + if dayDiff < 3 { + let formatter = RelativeDateTimeFormatter() + formatter.unitsStyle = .short + return formatter.localizedString(for: self, relativeTo: now) + } else if yearDiff < 1 { + let formatter = DateFormatter() + formatter.dateFormat = "MM-dd HH:mm" + return formatter.string(from: self) + } else { + let formatter = DateFormatter() + formatter.dateFormat = "yyyy-MM-dd HH:mm" + return formatter.string(from: self) + } + } +} diff --git a/Packages/Common/Sources/Common/Extensions/Defaults+Extensions.swift b/Packages/Common/Sources/Common/Extensions/Defaults+Extensions.swift new file mode 100644 index 0000000..8b28d96 --- /dev/null +++ b/Packages/Common/Sources/Common/Extensions/Defaults+Extensions.swift @@ -0,0 +1,23 @@ +// +// Defaults+Extensions.swift +// Common +// +// Created by John Mai on 2025/3/1. +// + +import Defaults +import Foundation +import Models + +extension Defaults.Keys { + // MARK: - General + public static let language = Key("language", default: .english) + + // MARK: - Appearance + + // MARK: - HuggingFace + public static let huggingFaceToken = Key("huggingFaceToken") + public static let huggingFaceEndpoint = Key( + "huggingFaceEndpoint", default: .hugfaceFace) + public static let huggingFaceCachePath = Key("huggingFaceCachePath", default: nil) +} diff --git a/Packages/Common/Sources/Common/Services/HuggingfaceHubService.swift b/Packages/Common/Sources/Common/Services/HuggingfaceHubService.swift new file mode 100644 index 0000000..cdcf77e --- /dev/null +++ b/Packages/Common/Sources/Common/Services/HuggingfaceHubService.swift @@ -0,0 +1,54 @@ +// +// HuggingfaceHubService.swift +// Common +// +// Created by John Mai on 2025/2/19. +// +import HuggingfaceHub +import Models + +struct HuggingfaceHubService { + func scanMLXModels() throws -> [Model] { + let hfCacheInfo = try CacheManager().scanCacheDir() + + return hfCacheInfo.repos.filter { repo in + repo.repoId.hasPrefix("mlx-community/") || repo.repoId.contains("-MLX") + || isMLX(repo: repo) + }.map { repo in + + let file = try? HuggingfaceHub.Utility.tryToLoadFromCache( + repoId: repo.repoId, filename: "") + + return Model( + provider: .mlx, + name: repo.repoId, + model: .local(file ?? repo.repoPath) + ) + } + } + + private func isMLX(repo: CachedRepoInfo) -> Bool { + if repo.repoId.hasPrefix("mlx-community/") { + return true + } + + let file = try? HuggingfaceHub.Utility.tryToLoadFromCache( + repoId: repo.repoId, filename: "README.md") + + guard let file else { + return false + } + + let markdown = try? String(contentsOf: file) + + guard let markdown else { + return false + } + + let metadata = MarkdownMetadata(from: markdown) + + let tags = metadata.array(for: "tags") + + return tags.contains("mlx") + } +} diff --git a/Packages/Common/Sources/Common/Utilities/MarkdownMetadata.swift b/Packages/Common/Sources/Common/Utilities/MarkdownMetadata.swift new file mode 100644 index 0000000..7da98c7 --- /dev/null +++ b/Packages/Common/Sources/Common/Utilities/MarkdownMetadata.swift @@ -0,0 +1,81 @@ +// +// MarkdownMetadata.swift +// Common +// +// Created by John Mai on 2025/2/27. +// + +struct MarkdownMetadata { + private(set) var values: [String: Any] = [:] + + init(from markdown: String) { + let lines = markdown.split(separator: "\n") + guard lines.first == "---" else { return } + + var currentKey: String? + var arrayItems: [String] = [] + var isCollectingArray = false + + for line in lines.dropFirst() { + if line == "---" { + if isCollectingArray, let key = currentKey { + values[key] = arrayItems + } + return + } + + let trimmedLine = line.trimmingCharacters(in: .whitespaces) + guard !trimmedLine.isEmpty else { continue } + + if trimmedLine.hasPrefix("-") { + let item = trimmedLine.dropFirst().trimmingCharacters(in: .whitespaces) + if !isCollectingArray { + isCollectingArray = true + arrayItems = [] + } + arrayItems.append(item) + if let key = currentKey { + values[key] = arrayItems + } + continue + } + + if let colonIndex = trimmedLine.firstIndex(of: ":") { + if isCollectingArray { + isCollectingArray = false + arrayItems = [] + } + + let key = String(trimmedLine[.. String? { + values[key] as? String + } + + func array(for key: String) -> [String] { + (values[key] as? [String]) ?? [] + } +} diff --git a/Packages/Common/Tests/CommonTests/CommonTests.swift b/Packages/Common/Tests/CommonTests/CommonTests.swift new file mode 100644 index 0000000..579abbc --- /dev/null +++ b/Packages/Common/Tests/CommonTests/CommonTests.swift @@ -0,0 +1,7 @@ +import Testing + +@testable import Common + +@Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. +} diff --git a/Packages/Conversation/.gitignore b/Packages/Conversation/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Packages/Conversation/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/Conversation/Package.swift b/Packages/Conversation/Package.swift new file mode 100644 index 0000000..96ab6b3 --- /dev/null +++ b/Packages/Conversation/Package.swift @@ -0,0 +1,36 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Conversation", + platforms: [.macOS(.v14)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "Conversation", + targets: ["Conversation"]) + ], + dependencies: [ + .package(name: "UltraUI", path: "../UltraUI"), + .package(name: "Models", path: "../Models"), + .package(url: "https://github.com/markiv/SwiftUI-Shimmer", from: "1.5.1"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "Conversation", + dependencies: [ + "Models", + "UltraUI", + .product(name: "Shimmer", package: "SwiftUI-Shimmer"), + ] + ), + .testTarget( + name: "ConversationTests", + dependencies: ["Conversation"] + ), + ] +) diff --git a/Packages/Conversation/Sources/Conversation/ChatView.swift b/Packages/Conversation/Sources/Conversation/ChatView.swift new file mode 100644 index 0000000..3e0beb8 --- /dev/null +++ b/Packages/Conversation/Sources/Conversation/ChatView.swift @@ -0,0 +1,164 @@ +// +// Message.swift +// Conversation +// +// Created by John Mai on 2025/2/23. +// + +import SwiftUI + +// 消息模型 +struct Message2: Identifiable { + let id = UUID() + let content: String + let isFromMe: Bool + let timestamp: Date +} + +struct BubbleShape: Shape { + let isFromMe: Bool + let cornerRadius: CGFloat + + func path(in rect: CGRect) -> Path { + var path = Path() + + if isFromMe { + path.move(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius)) + + path.addArc( + center: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + cornerRadius), + radius: cornerRadius, + startAngle: Angle(degrees: 180), + endAngle: Angle(degrees: 270), + clockwise: false) + + path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY)) + + path.addArc( + center: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY + cornerRadius), + radius: cornerRadius, + startAngle: Angle(degrees: 270), + endAngle: Angle(degrees: 0), + clockwise: false) + + path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) + path.addLine(to: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY)) + path.addArc( + center: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY - cornerRadius), + radius: cornerRadius, + startAngle: Angle(degrees: 90), + endAngle: Angle(degrees: 180), + clockwise: false) + + } else { + path.move(to: CGPoint(x: rect.minX, y: rect.maxY)) + path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius)) + path.addArc( + center: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + cornerRadius), + radius: cornerRadius, + startAngle: Angle(degrees: 180), + endAngle: Angle(degrees: 270), + clockwise: false) + + path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY)) + + path.addArc( + center: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY + cornerRadius), + radius: cornerRadius, + startAngle: Angle(degrees: 270), + endAngle: Angle(degrees: 0), + clockwise: false) + + path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius)) + + path.addArc( + center: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - cornerRadius), + radius: cornerRadius, + startAngle: Angle(degrees: 0), + endAngle: Angle(degrees: 90), + clockwise: false) + + path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) + } + + path.closeSubpath() + return path + } +} + +struct MessageBubble: View { + @Environment(\.utlraViewBackground) var utlraViewBackground + @Environment(\.utlraSecondaryViewBackground) var utlraSecondaryViewBackground + + let message: Message2 + + var body: some View { + HStack { + if message.isFromMe { + Spacer() + } + + Text(message.content) + .padding(.horizontal, 12) + .padding(.vertical, 8) + .background( + BubbleShape(isFromMe: message.isFromMe, cornerRadius: 10) + .fill( + message.isFromMe + ? utlraViewBackground : utlraSecondaryViewBackground) + ) + .foregroundColor(message.isFromMe ? .white.opacity(0.8) : .white) + + if !message.isFromMe { + Spacer() + } + } + .frame(maxWidth: 765) + .padding(.horizontal, 8) + .padding(.vertical, 4) + } +} + +struct ChatView: View { + @State private var messages: [Message2] = [ + Message2(content: "Hello!", isFromMe: false, timestamp: Date()), + Message2(content: "What have you been up to lately?", isFromMe: true, timestamp: Date()), + Message2( + content: "I'm learning SwiftUI and implementing a chat interface.", isFromMe: false, + timestamp: Date()), + Message2(content: "Looks good!", isFromMe: true, timestamp: Date()), + Message2( + content: "Which one do you think is better, SwiftUI or UIKit?", isFromMe: false, + timestamp: Date()), + Message2( + content: "I think SwiftUI is more concise and easier to use, but UIKit is more mature.", + isFromMe: true, timestamp: Date()), + Message2(content: "Indeed!", isFromMe: false, timestamp: Date()), + Message2( + content: "Are you using SwiftUI for any projects?", isFromMe: true, timestamp: Date()), + Message2( + content: "Not right now, but I'm considering making a personal blog.", isFromMe: false, + timestamp: Date()), + Message2(content: "Sounds good!", isFromMe: true, timestamp: Date()), + Message2( + content: "What framework are you planning to use?", isFromMe: false, timestamp: Date()), + Message2(content: "I'm considering using Vapor.", isFromMe: true, timestamp: Date()), + Message2(content: "Vapor is pretty good!", isFromMe: false, timestamp: Date()), + ] + + var body: some View { + ScrollView { + LazyVStack { + ForEach(messages) { message in + MessageBubble(message: message) + } + } + } + .padding(.horizontal) + } +} + +// 预览 +#Preview { + ChatView() +} diff --git a/Packages/Conversation/Sources/Conversation/ConversationSidebarView.swift b/Packages/Conversation/Sources/Conversation/ConversationSidebarView.swift new file mode 100644 index 0000000..2143e89 --- /dev/null +++ b/Packages/Conversation/Sources/Conversation/ConversationSidebarView.swift @@ -0,0 +1,77 @@ +// +// ConversationSidebarView.swift +// Conversation +// +// Created by John Mai on 2025/2/21. +// + +import Models +import Shimmer +import SwiftUI +import UltraUI + +struct ConversationSidebarView: View { + @Binding var conversations: [Conversation] + @Binding var selectedConversation: Conversation? + + var body: some View { + VStack(spacing: 16) { + HStack { + Image("AppLogo") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 60, height: 60) + Text("ChatMLX") + .font(.title) + .fontWeight(.bold) + .shimmering( + animation: .linear(duration: 2.6).delay(0.25) + .repeatForever(autoreverses: false)) + } + .shadow() + + List { + ForEach(conversations) { conversation in + item(conversation: conversation) + } + } + .listStyle(.plain) + .scrollContentBackground(.hidden) + } + } + + @ViewBuilder + func item(conversation: Conversation) -> some View { + Button(action: { + selectedConversation = conversation + }) { + VStack(alignment: .leading, spacing: 8) { + HStack { + Text(conversation.title) + .font(.headline) + .lineLimit(1) + + Spacer() + + Text(conversation.updatedTime.formatted()) + .font(.caption) + .foregroundStyle(.secondary) + + } + + if let description = conversation.description { + Text(description) + .font(.subheadline) + .foregroundStyle(.secondary) + .lineLimit(2) + .truncationMode(.tail) + } + } + .padding(16) + } + .buttonStyle( + UltraSidebarButtonStyle(conversation == selectedConversation) + ) + + } +} diff --git a/Packages/Conversation/Sources/Conversation/ConversationStore.swift b/Packages/Conversation/Sources/Conversation/ConversationStore.swift new file mode 100644 index 0000000..af2b683 --- /dev/null +++ b/Packages/Conversation/Sources/Conversation/ConversationStore.swift @@ -0,0 +1,25 @@ +// +// ConversationStore.swift +// Conversation +// +// Created by John Mai on 2025/2/22. +// + +import Foundation +import Models + +@MainActor +@Observable +public final class ConversationStore { + public var conversations: [Conversation] + + public var selectedConversation: Conversation? + + public init( + conversations: [Conversation] = [], + selectedConversation: Conversation? = nil + ) { + self.conversations = conversations + self.selectedConversation = selectedConversation + } +} diff --git a/Packages/Conversation/Sources/Conversation/ConversationView.swift b/Packages/Conversation/Sources/Conversation/ConversationView.swift new file mode 100644 index 0000000..5df2026 --- /dev/null +++ b/Packages/Conversation/Sources/Conversation/ConversationView.swift @@ -0,0 +1,71 @@ +import Models +// +// ConversationView.swift +// Conversation +// +// Created by John Mai on 2025/2/21. +// +import SwiftUI +import UltraUI + +public struct ConversationView: View { + @State private var prompt = AttributedString("") + + @Environment(ConversationStore.self) var conversationStore + + public init() {} + + public var body: some View { + @Bindable var conversationStore = conversationStore + + UltraNavigationSplitView { + ConversationSidebarView( + conversations: $conversationStore.conversations, + selectedConversation: $conversationStore.selectedConversation + ) + } detail: { + VStack(spacing: .zero) { + if let conversations = conversationStore.selectedConversation { + ChatView() + .frame(maxHeight: .infinity) + .ultraNavigationTitle(conversations.title) + } else { + GreetingView() + } + + PromptEditorView(prompt: $prompt) { + + } trailingToolbar: { + + } + .frame(maxWidth: 765) + } + } + .frame(minWidth: 580, minHeight: 360) + .task { + conversationStore.conversations = [ + .init( + title: "Exploring SwiftUI and Vapor in Chat App Development", + description: + "This conversation delves into the nuances of using SwiftUI for building a chat interface, comparing it with UIKit, and considering Vapor for backend development. The dialogue showcases the developer's journey, from learning SwiftUI to planning a personal blog project, while highlighting the strengths of these frameworks.", + model: .init( + provider: .openAI, + name: "gpt-4o", + model: .id("gpt-4o") + ) + ), + .init( + title: "Summary of the legal advisory dialogue", + description: + "This conversation is about legal counseling and covers questions, answers and related advice on legal issues. As specific conversation content was not provided, the above titles and descriptions are generic templates that are applicable to most legal counseling scenarios. For a more tailored title and description, please provide the specific conversation content to customize a version that more accurately reflects the topic and focus of the conversation.", + model: .init( + provider: .openAI, + name: "gpt-4o", + model: .id("gpt-4o") + ) + ), + ] + } + .ultraWindowStyle() + } +} diff --git a/Packages/Conversation/Sources/Conversation/PromptEditorView.swift b/Packages/Conversation/Sources/Conversation/PromptEditorView.swift new file mode 100644 index 0000000..414988d --- /dev/null +++ b/Packages/Conversation/Sources/Conversation/PromptEditorView.swift @@ -0,0 +1,146 @@ +// +// PromptEditor.swift +// Conversation +// +// Created by John Mai on 2025/2/23. +// + +import Models +import STTextView +import SwiftUI +import UltraUI + +struct PromptEditorView: View { + @Environment(\.utlraViewBackground) var utlraViewBackground + + @Binding var prompt: AttributedString + + @ViewBuilder var leadingToolbar: () -> LeadingToolbar + @ViewBuilder var trailingToolbar: () -> TrailingToolbar + + @State private var height: CGFloat = 36 + @State private var selection: NSRange? = nil + + @State private var models: [Model] = [] + + @State var model: Model? = nil + + @Environment(\.utlraRadius) var ultraRadius + + let font: NSFont = .preferredFont(forTextStyle: .title3) + let minHeight: CGFloat = 36 + let maxHeight: CGFloat = 200 + + var body: some View { + VStack(spacing: .zero) { + TextareaView( + text: $prompt, + placeholder: "What do you want to know?", + plugins: [ + TextViewPlugin( + font: font, + onHeightChange: updateHeight + ) + ], + font: font + ) + .frame(minHeight: minHeight) + .frame(height: height) + .transition(.move(edge: .top)) + + HStack { + Button { + + } label: { + Image(systemName: "paperclip") + }.buttonStyle(.ultraIcon) + + // 网络搜索 + Button { + + } label: { + Image(systemName: "network") + }.buttonStyle(.ultraIcon) + + leadingToolbar() + Spacer() + trailingToolbar() + UltraPicker( + options: models, + selection: $model + ) + + Button("Send", systemImage: "paperplane.fill") { + print("Send") + }.buttonStyle(.ultra) + } + + } + .padding() + .background(utlraViewBackground) + .cornerRadius(ultraRadius) + .shadow() + .padding() + .task { + // do { + // models = try HuggingfaceHubService().scanMLXModels() + // } catch { + // print(error) + // } + } + } + + func updateHeight(height: CGFloat) { + if height != self.height && minHeight < height && height < maxHeight { + Task { @MainActor in + withAnimation { + self.height = height + } + } + } + } +} + +extension PromptEditorView { + struct TextViewPlugin: STPlugin { + let font: NSFont + let onHeightChange: (CGFloat) -> Void + + func setUp(context: any Context) { + let textView = context.textView + let textLayoutManager = textView.textLayoutManager + + calculateAndUpdateHeight(textLayoutManager) + + context.events.onDidChangeText { _, _ in + calculateAndUpdateHeight(textLayoutManager) + } + } + + private func calculateAndUpdateHeight( + _ textLayoutManager: NSTextLayoutManager? + ) { + var line = 0 + + if let viewportRange = textLayoutManager? + .textViewportLayoutController.viewportRange + { + textLayoutManager?.enumerateTextLayoutFragments( + in: viewportRange, + options: .ensuresLayout + ) { fragment in + line += fragment.textLineFragments.count + return true + } + } + + let layoutManager = NSLayoutManager() + let lineHeight = layoutManager.defaultLineHeight( + for: font) + let newHeight = CGFloat(max(1, line)) * lineHeight + + onHeightChange(newHeight) + } + } + +} diff --git a/Packages/Conversation/Tests/ConversationTests/ConversationTests.swift b/Packages/Conversation/Tests/ConversationTests/ConversationTests.swift new file mode 100644 index 0000000..6515240 --- /dev/null +++ b/Packages/Conversation/Tests/ConversationTests/ConversationTests.swift @@ -0,0 +1,7 @@ +import Testing + +@testable import Conversation + +@Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. +} diff --git a/Packages/Models/.gitignore b/Packages/Models/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Packages/Models/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/Models/Package.swift b/Packages/Models/Package.swift new file mode 100644 index 0000000..9a505e1 --- /dev/null +++ b/Packages/Models/Package.swift @@ -0,0 +1,32 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Models", + platforms: [.macOS(.v14)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "Models", + targets: ["Models"]) + ], + dependencies: [ + .package(url: "https://github.com/sindresorhus/Defaults", from: "9.0.2") + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "Models", + dependencies: [ + "Defaults" + ] + ), + .testTarget( + name: "ModelsTests", + dependencies: ["Models"] + ), + ] +) diff --git a/Packages/Models/Sources/Models/Conversation.swift b/Packages/Models/Sources/Models/Conversation.swift new file mode 100644 index 0000000..a1d5411 --- /dev/null +++ b/Packages/Models/Sources/Models/Conversation.swift @@ -0,0 +1,52 @@ +// +// Conversation.swift +// Models +// +// Created by John Mai on 2025/2/22. +// + +import Foundation + +public struct Conversation: Identifiable { + public var id = UUID() + + public var title: String + + public var description: String? + + public var model: Model + + public var messages: [Message] = [] + + public var current_node: Message.ID? + + public var createdTime: Date + + public var updatedTime: Date + + public init( + id: UUID = UUID(), + title: String, + description: String? = nil, + model: Model, + messages: [Message] = [], + current_node: Message.ID? = nil, + createdTime: Date = Date(), + updatedTime: Date = Date() + ) { + self.id = id + self.title = title + self.description = description + self.model = model + self.messages = messages + self.current_node = current_node + self.createdTime = createdTime + self.updatedTime = updatedTime + } +} + +extension Conversation: Equatable, Hashable { + public static func == (lhs: Conversation, rhs: Conversation) -> Bool { + return lhs.id == rhs.id + } +} diff --git a/Packages/Models/Sources/Models/HuggingFaceEndpoint.swift b/Packages/Models/Sources/Models/HuggingFaceEndpoint.swift new file mode 100644 index 0000000..35b858b --- /dev/null +++ b/Packages/Models/Sources/Models/HuggingFaceEndpoint.swift @@ -0,0 +1,19 @@ +import Defaults +// +// HuggingFaceEndpoint.swift +// Models +// +// Created by John Mai on 2025/3/1. +// +import Foundation + +public enum HuggingFaceEndpoint: String { + case hugfaceFace = "https://huggingface.co" + case hfMirror = "https://hf-mirror.com" +} + +extension HuggingFaceEndpoint: CaseIterable {} +extension HuggingFaceEndpoint: Identifiable { + public var id: String { rawValue } +} +extension HuggingFaceEndpoint: Defaults.Serializable {} diff --git a/Packages/Models/Sources/Models/Language.swift b/Packages/Models/Sources/Models/Language.swift new file mode 100644 index 0000000..dee7524 --- /dev/null +++ b/Packages/Models/Sources/Models/Language.swift @@ -0,0 +1,107 @@ +// +// Language.swift +// Common +// +// Created by John Mai on 2025/2/28. +// + +import Defaults +import Foundation + +public enum Language: String { + case english = "en" + case arabic = "ar" + case chineseHongKong = "zh-HK" + case simplifiedChinese = "zh-Hans" + case traditionalChinese = "zh-Hant" + case catalan = "ca" + case croatian = "hr" + case czech = "cs" + case danish = "da" + case dutch = "nl" + case enAU = "en-AU" + case enGB = "en-GB" + case enIN = "en-IN" + case finnish = "fi" + case french = "fr" + case frenchCanadian = "fr-CA" + case de = "de" + case greek = "el" + case hebrew = "he" + case hindi = "hi" + case hungarian = "hu" + case indonesian = "id" + case italian = "it" + case japanese = "ja" + case korean = "ko" + case malay = "ms" + case norwegian = "nb" + case polish = "pl" + case portuguese = "pt-PT" + case portugueseBrazilian = "pt-BR" + case romanian = "ro" + case russian = "ru" + case slovak = "sk" + case spanish = "es" + case spanishLatinAmerica = "es-419" + case swedish = "sv" + case thai = "th" + case turkish = "tr" + case ukrainian = "uk" + case vietnamese = "vi" + +} + +extension Language: Identifiable { + public var id: String { + self.rawValue + } +} +extension Language: CaseIterable {} +extension Language: Defaults.Serializable {} + +extension Language: CustomStringConvertible { + public var description: String { + let components = self.rawValue.components(separatedBy: "-") + let languageCode = components[0] + + var languageComponents = Locale.Language.Components(identifier: languageCode) + + var scriptName: String? = nil + var regionName: String? = nil + + if components.count > 1 { + let secondPart = components[1] + + if secondPart.count == 4 && secondPart.first?.isUppercase == true { + languageComponents.script = Locale.Script(secondPart) + let currentLocale = Locale.current + scriptName = currentLocale.localizedString(forScriptCode: secondPart) + } else { + languageComponents.region = Locale.Region(secondPart) + let currentLocale = Locale.current + regionName = currentLocale.localizedString(forRegionCode: secondPart) + } + } + + let nativeLocale = + Locale(languageComponents: languageComponents).localizedString( + forLanguageCode: languageCode)?.capitalized ?? "" + let currentLocale = + Locale.current.localizedString(forLanguageCode: languageCode)?.capitalized ?? "" + + var result = "\(nativeLocale)" + + if nativeLocale != currentLocale { + result += " \(currentLocale)" + } + + if let scriptName = scriptName { + result += " (\(scriptName))" + } else if let regionName = regionName { + result += " (\(regionName))" + } + + return result + } +} diff --git a/Packages/Models/Sources/Models/Message.swift b/Packages/Models/Sources/Models/Message.swift new file mode 100644 index 0000000..2b7fe85 --- /dev/null +++ b/Packages/Models/Sources/Models/Message.swift @@ -0,0 +1,27 @@ +// +// Message.swift +// Models +// +// Created by John Mai on 2025/2/22. +// + +import Foundation + +public struct Message: Identifiable, Equatable, Hashable { + public static func == (lhs: Message, rhs: Message) -> Bool { + return lhs.id == rhs.id + } + + public var id = UUID() + public var role: String + public var content: String + public var reasoning: String + public var model: Model + public var provider: String + public var error: String + public var tools: [String] + public var parent: Message.ID? + public var children: [Message.ID] + public var createdTime: Date + public var updatedTime: Date +} diff --git a/Packages/Models/Sources/Models/Model.swift b/Packages/Models/Sources/Models/Model.swift new file mode 100644 index 0000000..76e43d4 --- /dev/null +++ b/Packages/Models/Sources/Models/Model.swift @@ -0,0 +1,43 @@ +// +// Model.swift +// Models +// +// Created by John Mai on 2025/2/22. +// + +import Foundation + +public enum Provider { + case mlx + case openAI +} + +public enum ModelType: Equatable, Hashable, CustomStringConvertible { + case local(URL) + case id(String) + + public var description: String { + switch self { + case .local(let url): + return url.lastPathComponent + case .id(let id): + return id + } + } +} + +public struct Model: Hashable, CustomStringConvertible { + public let provider: Provider + public let name: String + public let model: ModelType + + public init(provider: Provider, name: String, model: ModelType) { + self.provider = provider + self.name = name + self.model = model + } + + public var description: String { + return name + } +} diff --git a/Packages/Models/Sources/Models/Role.swift b/Packages/Models/Sources/Models/Role.swift new file mode 100644 index 0000000..e4af818 --- /dev/null +++ b/Packages/Models/Sources/Models/Role.swift @@ -0,0 +1,13 @@ +// +// Role.swift +// Models +// +// Created by John Mai on 2025/2/22. +// + +enum Role { + case system + case user + case assistant + case tool +} diff --git a/Packages/Models/Sources/Models/SettingsTab.swift b/Packages/Models/Sources/Models/SettingsTab.swift new file mode 100644 index 0000000..f223d71 --- /dev/null +++ b/Packages/Models/Sources/Models/SettingsTab.swift @@ -0,0 +1,48 @@ +// +// SettingsTab.swift +// Models +// +// Created by John Mai on 2025/2/27. +// + +import SwiftUI + +public struct SettingsTab: Identifiable { + + public enum ID: String { + case general = "General" + case defaultConversation = "Default Conversation" + case search = "Search" + case mcp = "MCP Servers" + case huggingFace = "Hugging Face" + case models = "Models" + case providers = "Providers" + case mlxCommunity = "MLX Community" + case downloadManager = "Download Manager" + case experimentalFeatures = "Experimental Features" + case about = "About" + } + + public let id: ID + public let icon: Image + public let showIndicator: (() -> Bool)? + + public init(_ id: ID, _ icon: Image, showIndicator: (() -> Bool)? = nil) { + self.id = id + self.icon = icon + self.showIndicator = showIndicator + } + + public func iconView() -> some View { + icon + .resizable() + .scaledToFit() + .frame(width: 18, height: 18) + } +} + +extension SettingsTab: Equatable { + public static func == (lhs: SettingsTab, rhs: SettingsTab) -> Bool { + rhs.id == lhs.id + } +} diff --git a/Packages/Models/Tests/ModelsTests/LanguageTests.swift b/Packages/Models/Tests/ModelsTests/LanguageTests.swift new file mode 100644 index 0000000..684c7ca --- /dev/null +++ b/Packages/Models/Tests/ModelsTests/LanguageTests.swift @@ -0,0 +1,21 @@ +// +// LanguageTests.swift +// Common +// +// Created by John Mai on 2025/2/28. +// + +import Testing + +@testable import Models + +@Test func availableLanguages() async throws { + debugPrint(Language.allCases) + print("Available languages count: \(Language.allCases.count)") + + for language in Language.allCases { + print("Language: \(language)") + } + + #expect(Language.allCases.count == 40) +} diff --git a/Packages/Models/Tests/ModelsTests/ModelsTests.swift b/Packages/Models/Tests/ModelsTests/ModelsTests.swift new file mode 100644 index 0000000..089210c --- /dev/null +++ b/Packages/Models/Tests/ModelsTests/ModelsTests.swift @@ -0,0 +1,7 @@ +import Testing + +@testable import Models + +@Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. +} diff --git a/Packages/Settings/.gitignore b/Packages/Settings/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Packages/Settings/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/Settings/Package.swift b/Packages/Settings/Package.swift new file mode 100644 index 0000000..769ffbc --- /dev/null +++ b/Packages/Settings/Package.swift @@ -0,0 +1,36 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Settings", + platforms: [.macOS(.v14)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "Settings", + targets: ["Settings"]) + ], + dependencies: [ + .package(name: "Common", path: "../Common"), + .package(name: "UltraUI", path: "../UltraUI"), + .package(name: "Models", path: "../Models"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "Settings", + dependencies: [ + "Common", + "UltraUI", + "Models", + ] + ), + .testTarget( + name: "SettingsTests", + dependencies: ["Settings"] + ), + ] +) diff --git a/Packages/Settings/Sources/Settings/AboutView.swift b/Packages/Settings/Sources/Settings/AboutView.swift new file mode 100644 index 0000000..eb78ce8 --- /dev/null +++ b/Packages/Settings/Sources/Settings/AboutView.swift @@ -0,0 +1,14 @@ +// +// AboutView.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import SwiftUI + +struct AboutView: View { + var body: some View { + Text("About") + } +} diff --git a/Packages/Settings/Sources/Settings/DefaultConversationView.swift b/Packages/Settings/Sources/Settings/DefaultConversationView.swift new file mode 100644 index 0000000..85dc74c --- /dev/null +++ b/Packages/Settings/Sources/Settings/DefaultConversationView.swift @@ -0,0 +1,14 @@ +// +// DefaultConversationView.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import SwiftUI + +struct DefaultConversationView: View { + var body: some View { + Text("Default Conversation") + } +} diff --git a/Packages/Settings/Sources/Settings/DownloadManagerView.swift b/Packages/Settings/Sources/Settings/DownloadManagerView.swift new file mode 100644 index 0000000..e728987 --- /dev/null +++ b/Packages/Settings/Sources/Settings/DownloadManagerView.swift @@ -0,0 +1,14 @@ +// +// DownloadManagerView.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import SwiftUI + +struct DownloadManagerView: View { + var body: some View { + Text("Download Manager") + } +} diff --git a/Packages/Settings/Sources/Settings/ExperimentalFeaturesView.swift b/Packages/Settings/Sources/Settings/ExperimentalFeaturesView.swift new file mode 100644 index 0000000..64c80b0 --- /dev/null +++ b/Packages/Settings/Sources/Settings/ExperimentalFeaturesView.swift @@ -0,0 +1,14 @@ +// +// ExperimentalFeaturesView.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import SwiftUI + +struct ExperimentalFeaturesView: View { + var body: some View { + Text("Experimental Features") + } +} diff --git a/Packages/Settings/Sources/Settings/GeneralView.swift b/Packages/Settings/Sources/Settings/GeneralView.swift new file mode 100644 index 0000000..8f1560a --- /dev/null +++ b/Packages/Settings/Sources/Settings/GeneralView.swift @@ -0,0 +1,50 @@ +// +// GeneralView.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import Common +import Defaults +import Models +import SwiftUI +import UltraUI + +struct GeneralView: View { + + @Default(.language) var language + + var body: some View { + VStack { + UltraSection { + LabeledContent("Language") { + Picker( + "Language", + selection: $language + ) { + ForEach(Language.allCases) { language in + Text(language.description).tag(language) + } + } + .buttonStyle(.borderless) + .tint(.white) + .labelsHidden() + } + } header: { + Text("Language") + } + + Spacer() + } + .padding() + } +} + +#Preview { + VStack { + GeneralView() + .labeledContentStyle(.horizontal) + .foregroundStyle(.white) + }.background(Color.black.opacity(0.5)) +} diff --git a/Packages/Settings/Sources/Settings/HuggingFaceView.swift b/Packages/Settings/Sources/Settings/HuggingFaceView.swift new file mode 100644 index 0000000..6d28bcc --- /dev/null +++ b/Packages/Settings/Sources/Settings/HuggingFaceView.swift @@ -0,0 +1,127 @@ +// +// HuggingFaceView.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import Defaults +import Models +import SwiftUI +import UltraUI + +struct HuggingFaceView: View { + + @Default(.huggingFaceToken) var token + @Default(.huggingFaceEndpoint) var endpoint + @Default(.huggingFaceCachePath) var cachePath + + @State private var isShowingFilePicker = false + @State private var isShowingResetConfirmation = false + + var body: some View { + VStack { + UltraSection { + UltraSecureTextField( + text: $token.toUnwrapped(defaultValue: ""), + placeholder: "Enter your Hugging Face token" + ) + } header: { + Text("Hugging Face Token") + } + + UltraSection { + LabeledContent("Endpoint") { + Picker( + "Endpoint", + selection: $endpoint + ) { + ForEach(HuggingFaceEndpoint.allCases) { endpoint in + Text(endpoint.rawValue).tag(endpoint) + } + } + .buttonStyle(.borderless) + .tint(.white) + .labelsHidden() + } + } header: { + Text("Hugging Face Endpoint") + } + + UltraSection { + VStack(alignment: .leading, spacing: 8) { + LabeledContent("Cache Path") { + HStack { + Button { + isShowingFilePicker = true + } label: { + Image(systemName: "folder.badge.gearshape") + .foregroundStyle(.white) + } + .buttonStyle(.plain) + + if cachePath != nil { + Button { + isShowingResetConfirmation = true + } label: { + Image(systemName: "arrow.uturn.backward") + .foregroundStyle(.red) + } + .buttonStyle(.plain) + } + } + } + + Text( + cachePath?.path + ?? FileManager.default.homeDirectoryForCurrentUser + .appendingPathComponent(".cache", isDirectory: true) + .appendingPathComponent("huggingface", isDirectory: true).path() + ) + .lineLimit(1) + .truncationMode(.middle) + .foregroundStyle(.secondary) + } + } header: { + Text("Hugging Face Cache Management") + } + + Spacer() + } + .padding() + .fileImporter( + isPresented: $isShowingFilePicker, + allowedContentTypes: [.folder], + allowsMultipleSelection: false + ) { result in + switch result { + case .success(let urls): + if let selectedURL = urls.first { + cachePath = selectedURL + } + case .failure(let error): + print("Error selecting directory: \(error.localizedDescription)") + } + } + .confirmationDialog( + "Reset Cache Path", + isPresented: $isShowingResetConfirmation, + titleVisibility: .visible + ) { + Button("Reset", role: .destructive) { + cachePath = nil + } + Button("Cancel", role: .cancel) {} + } message: { + Text("Are you sure you want to reset to the default cache path?") + } + } +} + +#Preview { + VStack { + HuggingFaceView() + .labeledContentStyle(.horizontal) + .foregroundStyle(.white) + }.background(Color.black.opacity(0.5)) +} diff --git a/Packages/Settings/Sources/Settings/MCPServersView.swift b/Packages/Settings/Sources/Settings/MCPServersView.swift new file mode 100644 index 0000000..c44c340 --- /dev/null +++ b/Packages/Settings/Sources/Settings/MCPServersView.swift @@ -0,0 +1,14 @@ +// +// MCPServersView.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import SwiftUI + +struct MCPServersView: View { + var body: some View { + Text("MCP Servers") + } +} diff --git a/Packages/Settings/Sources/Settings/MLXCommunityView.swift b/Packages/Settings/Sources/Settings/MLXCommunityView.swift new file mode 100644 index 0000000..20da571 --- /dev/null +++ b/Packages/Settings/Sources/Settings/MLXCommunityView.swift @@ -0,0 +1,14 @@ +// +// MLXCommunityView.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import SwiftUI + +struct MLXCommunityView: View { + var body: some View { + Text("MLX Community") + } +} diff --git a/Packages/Settings/Sources/Settings/ModelsView.swift b/Packages/Settings/Sources/Settings/ModelsView.swift new file mode 100644 index 0000000..8552c1d --- /dev/null +++ b/Packages/Settings/Sources/Settings/ModelsView.swift @@ -0,0 +1,14 @@ +// +// ModelsView.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import SwiftUI + +struct ModelsView: View { + var body: some View { + Text("Models") + } +} diff --git a/Packages/Settings/Sources/Settings/ProvidersView.swift b/Packages/Settings/Sources/Settings/ProvidersView.swift new file mode 100644 index 0000000..05bcac4 --- /dev/null +++ b/Packages/Settings/Sources/Settings/ProvidersView.swift @@ -0,0 +1,14 @@ +// +// ProvidersView.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import SwiftUI + +struct ProvidersView: View { + var body: some View { + Text("Providers") + } +} diff --git a/Packages/Settings/Sources/Settings/SearchView.swift b/Packages/Settings/Sources/Settings/SearchView.swift new file mode 100644 index 0000000..992f5fb --- /dev/null +++ b/Packages/Settings/Sources/Settings/SearchView.swift @@ -0,0 +1,14 @@ +// +// SearchView.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import SwiftUI + +struct SearchView: View { + var body: some View { + Text("Search") + } +} diff --git a/Packages/Settings/Sources/Settings/SettingsSidebarView.swift b/Packages/Settings/Sources/Settings/SettingsSidebarView.swift new file mode 100644 index 0000000..9b77cda --- /dev/null +++ b/Packages/Settings/Sources/Settings/SettingsSidebarView.swift @@ -0,0 +1,77 @@ +// +// SettingsSidebarView.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import Models +import SwiftUI +import UltraUI + +struct SettingsSidebarView: View { + + @Binding var selection: SettingsTab + + let tabs: [SettingsTab] + + var body: some View { + VStack(alignment: .leading, spacing: 0) { + VStack(alignment: .leading) { + Text("Settings") + .font(.title2) + Text("Preferences and model settings") + .font(.subheadline) + .foregroundStyle(.white.opacity(0.5)) + } + .padding() + + List { + ForEach(tabs) { tab in + item(tab).tag(tab.id) + } + } + .scrollContentBackground(.hidden) + .listStyle(.plain) + + Spacer() + } + + } + + @ViewBuilder + private func item(_ tab: SettingsTab) -> some View { + Button( + action: { + selection = tab + }, + label: { + HStack { + tab.iconView() + + Text(LocalizedStringKey(tab.id.rawValue)) + + if tab.showIndicator?() == true { + VStack { + Circle() + .foregroundStyle(.red) + .frame(width: 4, height: 4) + .padding(.top, 6) + .shadow(color: .red, radius: 4) + + Spacer() + } + } + Spacer() + } + .padding(.horizontal, 16) + .padding(.vertical, 8) + } + ) + + .buttonStyle( + UltraSidebarButtonStyle(selection == tab) + ) + .lineLimit(1) + } +} diff --git a/Packages/Settings/Sources/Settings/SettingsStore.swift b/Packages/Settings/Sources/Settings/SettingsStore.swift new file mode 100644 index 0000000..86512fa --- /dev/null +++ b/Packages/Settings/Sources/Settings/SettingsStore.swift @@ -0,0 +1,14 @@ +// +// SettingsStore.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import Foundation + +@MainActor +@Observable +final class SettingsStore { + +} diff --git a/Packages/Settings/Sources/Settings/SettingsView.swift b/Packages/Settings/Sources/Settings/SettingsView.swift new file mode 100644 index 0000000..0df5033 --- /dev/null +++ b/Packages/Settings/Sources/Settings/SettingsView.swift @@ -0,0 +1,74 @@ +// +// SettingsView.swift +// Sources +// +// Created by John Mai on 2025/2/27. +// + +import Models +import SwiftUI +import UltraUI + +public struct SettingsView: View { + @State private var selection: SettingsTab + + private static let tabs: [SettingsTab] = [ + .init(.general, Image(systemName: "gearshape")), + .init(.defaultConversation, Image(systemName: "person.bubble")), + .init(.huggingFace, Image("huggingface")), + .init(.models, Image(systemName: "brain")), + .init(.providers, Image(systemName: "brain")), + .init(.mcp, Image("MCP")), + .init(.mlxCommunity, Image("MLX")), + .init( + .downloadManager, Image(systemName: "arrow.down.circle"), + showIndicator: { true } + ), + .init(.experimentalFeatures, Image(systemName: "flask")), + .init(.about, Image(systemName: "info.circle")), + ] + + public init() { + self._selection = .init(initialValue: Self.tabs.first!) + } + + public var body: some View { + UltraNavigationSplitView(initialSidebarWidth: 220) { + SettingsSidebarView(selection: $selection, tabs: Self.tabs) + } detail: { + detailView + .ultraNavigationTitle(selection.id.rawValue) + .labeledContentStyle(.horizontal) + } + .ultraWindowStyle() + .frame(width: 650, height: 480) + } + + @ViewBuilder + private var detailView: some View { + switch selection.id { + case .general: + GeneralView() + case .defaultConversation: + DefaultConversationView() + case .huggingFace: + HuggingFaceView() + case .models: + ModelsView() + case .providers: + ProvidersView() + case .mcp: + MCPServersView() + case .mlxCommunity: + MLXCommunityView() + case .downloadManager: + DownloadManagerView() + case .experimentalFeatures: + ExperimentalFeaturesView() + case .about: + AboutView() + default: + EmptyView() + } + } +} diff --git a/Packages/Settings/Tests/SettingsTests/SettingsTests.swift b/Packages/Settings/Tests/SettingsTests/SettingsTests.swift new file mode 100644 index 0000000..31152a5 --- /dev/null +++ b/Packages/Settings/Tests/SettingsTests/SettingsTests.swift @@ -0,0 +1,7 @@ +import Testing + +@testable import Settings + +@Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. +} diff --git a/Packages/UltraUI/.gitignore b/Packages/UltraUI/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Packages/UltraUI/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/UltraUI/Package.swift b/Packages/UltraUI/Package.swift new file mode 100644 index 0000000..641435c --- /dev/null +++ b/Packages/UltraUI/Package.swift @@ -0,0 +1,34 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "UltraUI", + platforms: [.macOS(.v14)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "UltraUI", + targets: ["UltraUI"]) + ], + dependencies: [ + .package(url: "https://github.com/krzyzanowskim/STTextView", from: "2.0.0"), + .package(url: "https://github.com/siteline/swiftui-introspect", from: "1.0.0"), + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "UltraUI", + dependencies: [ + "STTextView", + .product(name: "SwiftUIIntrospect", package: "swiftui-introspect"), + ] + ), + .testTarget( + name: "UltraUITests", + dependencies: ["UltraUI"] + ), + ] +) diff --git a/Packages/UltraUI/Sources/UltraUI/Divided.swift b/Packages/UltraUI/Sources/UltraUI/Divided.swift new file mode 100644 index 0000000..ed60f85 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Divided.swift @@ -0,0 +1,32 @@ +// +// Divided.swift +// UltraUI +// +// Created by John Mai on 2025/2/28. +// + +import SwiftUI + +struct Divided: View { + @ViewBuilder let content: Content + + var body: some View { + _VariadicView.Tree(Root()) { content } + } + + struct Root: _VariadicView_MultiViewRoot { + @ViewBuilder + func body(children: _VariadicView.Children) -> some View { + let last = children.last?.id + + ForEach(children) { child in + child + + if child.id != last { + Divider() + .foregroundStyle(.secondary.opacity(0.2)) + } + } + } + } +} diff --git a/Packages/UltraUI/Sources/UltraUI/Extensions/EnvironmentValues+Extensions.swift b/Packages/UltraUI/Sources/UltraUI/Extensions/EnvironmentValues+Extensions.swift new file mode 100644 index 0000000..7b41df7 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Extensions/EnvironmentValues+Extensions.swift @@ -0,0 +1,26 @@ +// +// EnvironmentValues+Extensions.swift +// UltraUI +// +// Created by John Mai on 2025/2/28. +// + +import SwiftUI + +extension EnvironmentValues { + @Entry public var utlraRadius: CGFloat = 10 + + @Entry public var utlraWindowBlur: Int = 50 + @Entry public var utlraWindowBackgroundColor: Color = .black.opacity(0.6) + + @Entry public var utlraViewBackground: Color = .black.opacity(0.28) + @Entry public var utlraSecondaryViewBackground: Color = .white.opacity(0.12) + + @Entry public var utlraTint: Color = .init(red: 21 / 255, green: 146 / 255, blue: 250 / 255) + + @Entry public var utlraTitle: Color = .white + @Entry public var utlraSubtitle: Color = .white.opacity(0.7) + @Entry public var utlraText: Color = .white + @Entry public var utlraPlaceholder: Color = .white.opacity(0.7) + +} diff --git a/Packages/UltraUI/Sources/UltraUI/Extensions/NSWindow+Extensions.swift b/Packages/UltraUI/Sources/UltraUI/Extensions/NSWindow+Extensions.swift new file mode 100644 index 0000000..d057878 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Extensions/NSWindow+Extensions.swift @@ -0,0 +1,35 @@ +// +// NSWindow+Extensions.swift +// UltraUI +// +// Created by John Mai on 2025/2/21. +// + +import SwiftUI + +@_silgen_name("CGSDefaultConnectionForThread") +func CGSDefaultConnectionForThread() -> CGSConnection? + +@_silgen_name("CGSSetWindowBackgroundBlurRadius") +@discardableResult +func CGSSetWindowBackgroundBlurRadius( + _ connection: CGSConnection, _ windowNumber: CGWindowID, _ radius: Int +) -> CGError + +typealias CGSConnection = UInt32 +typealias CGWindowID = Int +typealias CGError = Int32 + +extension NSWindow { + func setWindowBackgroundBlurRadius(_ radius: Int = 50) { + let status = CGSSetWindowBackgroundBlurRadius( + CGSDefaultConnectionForThread()!, + windowNumber, + radius + ) + + if status != noErr { + NSLog("Error setting window background blur radius: \(status)") + } + } +} diff --git a/Packages/UltraUI/Sources/UltraUI/Extensions/View+Extensions.swift b/Packages/UltraUI/Sources/UltraUI/Extensions/View+Extensions.swift new file mode 100644 index 0000000..aec1e03 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Extensions/View+Extensions.swift @@ -0,0 +1,19 @@ +// +// View+Extensions.swift +// UltraUI +// +// Created by John Mai on 2025/2/23. +// + +import SwiftUI +import SwiftUIIntrospect + +extension View { + public func shadow() -> some View { + self.shadow(radius: 6) + } + + public func ultraWindowStyle() -> some View { + self.modifier(UltraWindowStyleViewModifier()) + } +} diff --git a/Packages/UltraUI/Sources/UltraUI/GreetingView.swift b/Packages/UltraUI/Sources/UltraUI/GreetingView.swift new file mode 100644 index 0000000..c480e9c --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/GreetingView.swift @@ -0,0 +1,46 @@ +// +// GreetingView.swift +// UltraUI +// +// Created by John Mai on 2025/2/23. +// + +import SwiftUI + +public struct GreetingView: View { + + @Environment(\.utlraTitle) private var utlraTitle + @Environment(\.utlraSubtitle) private var utlraSubtitle + + var greeting: String { + let hour = Calendar.current.component(.hour, from: Date()) + switch hour { + case 6 ..< 12: + return "Good Morning! ☀️" + case 12 ..< 18: + return "Good Afternoon! 🌇" + default: + return "Good Evening! 🌛" + } + } + + public init() {} + + public var body: some View { + VStack { + Text(greeting) + .font(.largeTitle) + .foregroundColor(utlraTitle) + + Text("How can I help you today?") + .font(.title) + .foregroundColor(utlraSubtitle) + } + .fontWeight(.bold) + .shadow() + } +} + +#Preview { + GreetingView() +} diff --git a/Packages/UltraUI/Sources/UltraUI/Modifiers/SidebarViewModifier.swift b/Packages/UltraUI/Sources/UltraUI/Modifiers/SidebarViewModifier.swift new file mode 100644 index 0000000..197dbd9 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Modifiers/SidebarViewModifier.swift @@ -0,0 +1,111 @@ +// +// SidebarViewModifier.swift +// UltraUI +// +// Created by John Mai on 2025/2/27. +// + +import SwiftUI + +struct SidebarViewModifier: ViewModifier { + + @Environment(\.utlraSecondaryViewBackground) var utlraSecondaryViewBackground + + @State private var isHovered = false + private let cornerRadius: CGFloat + private let isSelected: Bool + private let disableHoverEffect: Bool + + var highlighted: Bool { + isHovered || isSelected + } + + init( + isSelected: Bool = true, + disableScaleEffect: Bool = false, + cornerRadius: CGFloat = 10 + ) { + self.isSelected = isSelected + self.disableHoverEffect = disableScaleEffect + self.cornerRadius = cornerRadius + } + + init(_ isSelected: Bool) { + self.init( + isSelected: isSelected, + disableScaleEffect: true + ) + } + + func body(content: Content) -> some View { + content + .foregroundStyle(.white) + .background { + if highlighted { + RoundedRectangle(cornerRadius: cornerRadius) + .fill(utlraSecondaryViewBackground) + .overlay { + RoundedRectangle(cornerRadius: cornerRadius) + .fill( + LinearGradient( + colors: [ + .white.opacity(isHovered ? 0.1 : 0.05), + .clear, + + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + } + .shadow() + .shadow( + color: .white.opacity(isHovered ? 0.3 : 0.2), radius: 2, + x: -1, y: -1 + ) + .shadow( + color: .black.opacity(isHovered ? 0.3 : 0.2), radius: 2, + x: 1, y: 1 + ) + .shadow( + color: .white.opacity(isHovered ? 0.15 : 0.1), + radius: 8, x: 0, y: 0) + + RoundedRectangle(cornerRadius: cornerRadius) + .stroke( + LinearGradient( + colors: [ + .white.opacity(isHovered ? 0.6 : 0.5), + .clear, + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ), + lineWidth: 0.5 + ) + } + } + + .scaleEffect(isHovered && !disableHoverEffect ? 1.02 : 1.0) + .shadow( + color: .black.opacity( + isHovered ? 0.25 : 0.2 + ), + radius: isHovered ? 5 : 4, + x: 0, + y: isHovered ? 3 : 2 + ) + + .animation( + .spring( + duration: 0.2, + bounce: 0.3 + ), + value: isHovered + ) + .onHover { hovering in + isHovered = hovering + } + + } +} diff --git a/Packages/UltraUI/Sources/UltraUI/Modifiers/UltraWindowStyleViewModifier.swift b/Packages/UltraUI/Sources/UltraUI/Modifiers/UltraWindowStyleViewModifier.swift new file mode 100644 index 0000000..defc5a1 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Modifiers/UltraWindowStyleViewModifier.swift @@ -0,0 +1,34 @@ +// +// UltraWindowStyleViewModifier.swift +// UltraUI +// +// Created by John Mai on 2025/2/28. +// + +import SwiftUI + +struct UltraWindowStyleViewModifier: ViewModifier { + @Environment(\.utlraWindowBlur) var utlraWindowBlur + @Environment(\.utlraWindowBackgroundColor) var utlraWindowBackgroundColor + @Environment(\.utlraTint) var utlraTint + + func body(content: Content) -> some View { + content.introspect(.window, on: .macOS(.v15, .v14)) { window in + window.isOpaque = false + + window.setWindowBackgroundBlurRadius(utlraWindowBlur) + window.backgroundColor = NSColor(utlraWindowBackgroundColor) + window.toolbarStyle = .unified + + window.titlebarAppearsTransparent = true + window.titleVisibility = .hidden + + let toolbar = NSToolbar() + window.toolbar = toolbar + } + .background(UltraWindowBackgroundView()) + .foregroundStyle(.white, .white.opacity(0.7)) + .tint(utlraTint) + .ignoresSafeArea() + } +} diff --git a/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraButtonStyle.swift b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraButtonStyle.swift new file mode 100644 index 0000000..091cd7b --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraButtonStyle.swift @@ -0,0 +1,133 @@ +// +// UltraButtonStyle.swift +// UltraUI +// +// Created by John Mai on 2025/2/23. +// + +import SwiftUI + +extension ButtonStyle where Self == UltraButtonStyle { + public static var ultra: some ButtonStyle { + UltraButtonStyle() + } +} + +public struct UltraButtonStyle: ButtonStyle { + + @Environment(\.utlraSecondaryViewBackground) var utlraSecondaryViewBackground + + @State private var isHovered = false + private let cornerRadius: CGFloat + private let isSelected: Bool + private let disableHoverEffect: Bool + + var highlighted: Bool { + isHovered || isSelected + } + + init( + isSelected: Bool = true, + disableScaleEffect: Bool = false, + cornerRadius: CGFloat = 10 + ) { + self.isSelected = isSelected + self.disableHoverEffect = disableScaleEffect + self.cornerRadius = cornerRadius + } + + init(_ isSelected: Bool) { + self.init( + isSelected: isSelected, + disableScaleEffect: true + ) + } + + public func makeBody(configuration: Configuration) -> some View { + configuration.label + .padding(.horizontal, 16) + .padding(.vertical, 8) + .foregroundStyle(.white) + .background { + if highlighted { + RoundedRectangle(cornerRadius: cornerRadius) + .fill(utlraSecondaryViewBackground) + .overlay { + RoundedRectangle(cornerRadius: cornerRadius) + .fill( + LinearGradient( + colors: [ + .white.opacity(isHovered ? 0.3 : 0.2), + .clear, + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + } + .shadow( + color: .white.opacity(isHovered ? 0.3 : 0.2), radius: 2, + x: -1, y: -1 + ) + .shadow( + color: .black.opacity(isHovered ? 0.3 : 0.2), radius: 2, + x: 1, y: 1 + ) + .shadow( + color: .white.opacity(isHovered ? 0.15 : 0.1), + radius: 8, x: 0, y: 0) + + RoundedRectangle(cornerRadius: cornerRadius) + .stroke( + LinearGradient( + colors: [ + .white.opacity(isHovered ? 0.6 : 0.5), + .clear, + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ), + lineWidth: 0.5 + ) + } + } + .opacity(configuration.isPressed ? 0.8 : 1.0) + .scaleEffect(configuration.isPressed ? 0.98 : 1.0) + .scaleEffect(isHovered && !disableHoverEffect ? 1.02 : 1.0) + .shadow( + color: .black.opacity( + configuration.isPressed ? 0.1 : isHovered ? 0.25 : 0.2 + ), + radius: configuration.isPressed ? 2 : isHovered ? 5 : 4, + x: 0, + y: configuration.isPressed ? 1 : isHovered ? 3 : 2 + ) + .animation( + .spring( + duration: 0.2, + bounce: 0.3 + ), + value: configuration.isPressed + ) + .animation( + .spring( + duration: 0.2, + bounce: 0.3 + ), + value: isHovered + ) + .onHover { hovering in + isHovered = hovering + } + } +} + +#Preview { + VStack { + Button("Hello") {} + .buttonStyle(.ultra) + } + .frame(width: 200, height: 200) + .background(Color.gray) + +} diff --git a/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraDynamicButtonStyle.swift b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraDynamicButtonStyle.swift new file mode 100644 index 0000000..ecf05e0 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraDynamicButtonStyle.swift @@ -0,0 +1,168 @@ +// +// UltraDynamicButtonStyle.swift +// UltraUI +// +// Created by John Mai on 2025/2/23. +// + +import SwiftUI + +extension ButtonStyle where Self == UltraDynamicButtonStyle { + static var ultraDynamic: some ButtonStyle { + UltraDynamicButtonStyle() + } +} + +struct UltraDynamicButtonStyle: ButtonStyle { + @Environment(\.utlraSecondaryViewBackground) var utlraSecondaryViewBackground + + @State private var mouseLocation: CGPoint = .zero + @State private var buttonFrame: CGRect = .zero + + func makeBody(configuration: Configuration) -> some View { + GeometryReader { geometry in + configuration.label + .padding(.horizontal, 20) + .padding(.vertical, 12) + + .background { + ZStack { + RoundedRectangle(cornerRadius: 25) + .fill(utlraSecondaryViewBackground) + + RadialGradient( + colors: [ + .white.opacity(0.3), + .clear, + ], + center: calculateGradientCenter(), + startRadius: 0, + endRadius: 100 + ) + .opacity(mouseLocation == .zero ? 0 : 0.5) + .clipShape(RoundedRectangle(cornerRadius: 25)) + + RoundedRectangle(cornerRadius: 25) + .stroke( + LinearGradient( + colors: [ + .white.opacity(0.5), + .white.opacity(0.2), + ], + startPoint: calculateGradientCenter(), + endPoint: calculateOppositePoint() + ), + lineWidth: 0.5 + ) + } + } + + .shadow( + color: .white.opacity(0.2), + radius: calculateShadowRadius(), + x: calculateShadowOffset().width, + y: calculateShadowOffset().height + ) + .shadow( + color: .black.opacity(0.2), + radius: calculateShadowRadius(), + x: -calculateShadowOffset().width, + y: -calculateShadowOffset().height + ) + + .shadow( + color: .white.opacity(0.1), + radius: 8, + x: 0, + y: 0 + ) + + .scaleEffect(configuration.isPressed ? 0.98 : 1.0) + .opacity(configuration.isPressed ? 0.8 : 1.0) + .animation( + .spring(duration: 0.2, bounce: 0.3), + value: configuration.isPressed + ) + .animation(.easeOut(duration: 0.2), value: mouseLocation) + + .modifier( + MouseLocationTracker( + mouseLocation: .init( + get: { mouseLocation }, set: { mouseLocation = $0 }) + ) + ) + + .onAppear { + buttonFrame = geometry.frame(in: .local) + } + } + } + + private func calculateGradientCenter() -> UnitPoint { + guard mouseLocation != .zero else { + return .topLeading + } + + let x = mouseLocation.x / buttonFrame.width + let y = mouseLocation.y / buttonFrame.height + return UnitPoint(x: x, y: y) + } + + private func calculateOppositePoint() -> UnitPoint { + let center = calculateGradientCenter() + return UnitPoint(x: 1 - center.x, y: 1 - center.y) + } + + private func calculateShadowRadius() -> CGFloat { + guard mouseLocation != .zero else { return 4 } + + let distance = hypot( + mouseLocation.x - buttonFrame.midX, + mouseLocation.y - buttonFrame.midY + ) + + return max(2, min(6, 6 - (distance / 100))) + } + + private func calculateShadowOffset() -> CGSize { + guard mouseLocation != .zero else { + return CGSize(width: 0, height: 2) + } + + let deltaX = mouseLocation.x - buttonFrame.midX + let deltaY = mouseLocation.y - buttonFrame.midY + + let maxOffset: CGFloat = 2 + + return CGSize( + width: deltaX * maxOffset / buttonFrame.width, + height: deltaY * maxOffset / buttonFrame.height + ) + } +} + +struct MouseLocationTracker: ViewModifier { + @Binding var mouseLocation: CGPoint + + func body(content: Content) -> some View { + content + .onContinuousHover { phase in + switch phase { + case .active(let location): + mouseLocation = location + case .ended: + mouseLocation = .zero + } + } + } +} + +#Preview { + VStack { + Button("Hello") {} + .buttonStyle(UltraDynamicButtonStyle()) + } + .frame(width: 200, height: 200) + .background(Color.gray) + +} diff --git a/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraIconButtonStyle.swift b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraIconButtonStyle.swift new file mode 100644 index 0000000..5d0b860 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraIconButtonStyle.swift @@ -0,0 +1,126 @@ +// +// UltraIconButtonStyle.swift +// UltraUI +// +// Created by John Mai on 2025/2/23. +// + +import SwiftUI + +extension ButtonStyle where Self == UltraIconButtonStyle { + public static var ultraIcon: some ButtonStyle { + UltraIconButtonStyle() + } +} + +public struct UltraIconButtonStyle: ButtonStyle { + @Environment(\.utlraSecondaryViewBackground) var utlraSecondaryViewBackground + + @State private var isHovered = false + + public func makeBody(configuration: Configuration) -> some View { + HStack { + configuration.label + .font(.title2) + .foregroundColor(.white) + .padding(8) + } + .background { + if isHovered { + ZStack { + Circle() + .fill(utlraSecondaryViewBackground) + .overlay { + Circle() + .fill( + LinearGradient( + colors: [ + .white.opacity(0.2), + .clear, + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .opacity(0.5) + } + .shadow( + color: .white.opacity(0.3), + radius: 2, + x: -1, + y: -1 + ) + .shadow( + color: .black.opacity(0.3), + radius: 2, + x: 1, + y: 1 + ) + .shadow( + color: .white.opacity(0.15), + radius: 8, + x: 0, + y: 0 + ) + + Circle() + .stroke( + LinearGradient( + colors: [ + .white.opacity(0.6), + .clear, + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ), + lineWidth: 0.5 + ) + } + .shadow( + color: .white.opacity(0.2), + radius: 4, + x: -1, + y: -1 + ) + .shadow( + color: .black.opacity(0.2), + radius: 4, + x: 1, + y: 1 + ) + .shadow( + color: .white.opacity(0.1), + radius: 8, + x: 0, + y: 0 + ) + } + } + .opacity(configuration.isPressed ? 0.8 : 1.0) + .scaleEffect(configuration.isPressed ? 0.98 : 1.0) + .animation( + .spring(duration: 0.2, bounce: 0.3), value: configuration.isPressed + ) + .animation(.easeOut(duration: 0.2), value: isHovered) + .onHover { hovering in + isHovered = hovering + } + } +} + +#Preview { + VStack { + Button("Hello") {} + .buttonStyle(.ultraIcon) + + Button { + + } label: { + Image(systemName: "paperclip") + }.buttonStyle(.ultraIcon) + + } + .frame(width: 200, height: 200) + .background(Color.gray) + +} diff --git a/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraPlainButtonStyle.swift b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraPlainButtonStyle.swift new file mode 100644 index 0000000..ec503ed --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraPlainButtonStyle.swift @@ -0,0 +1,125 @@ +// +// UltraIconButtonStyle.swift +// UltraUI +// +// Created by John Mai on 2025/2/23. +// + +import SwiftUI + +extension ButtonStyle where Self == UltraPlainButtonStyle { + static var ultraPlain: some ButtonStyle { + UltraPlainButtonStyle() + } +} + +struct UltraPlainButtonStyle: ButtonStyle { + @Environment(\.utlraSecondaryViewBackground) var utlraSecondaryViewBackground + + @State private var isHovered = false + + func makeBody(configuration: Configuration) -> some View { + HStack { + configuration.label + .padding(.horizontal, 16) + .padding(.vertical, 8) + } + .background { + if isHovered { + ZStack { + RoundedRectangle(cornerRadius: 10) + .fill(utlraSecondaryViewBackground) + .overlay { + RoundedRectangle(cornerRadius: 10) + .fill( + LinearGradient( + colors: [ + .white.opacity(0.2), + .clear, + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + .opacity(0.5) + } + .shadow( + color: .white.opacity(0.3), + radius: 2, + x: -1, + y: -1 + ) + .shadow( + color: .black.opacity(0.3), + radius: 2, + x: 1, + y: 1 + ) + .shadow( + color: .white.opacity(0.15), + radius: 8, + x: 0, + y: 0 + ) + + RoundedRectangle(cornerRadius: 10) + .stroke( + LinearGradient( + colors: [ + .white.opacity(0.6), + .clear, + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ), + lineWidth: 0.5 + ) + } + .shadow( + color: .white.opacity(0.2), + radius: 4, + x: -1, + y: -1 + ) + .shadow( + color: .black.opacity(0.2), + radius: 4, + x: 1, + y: 1 + ) + .shadow( + color: .white.opacity(0.1), + radius: 8, + x: 0, + y: 0 + ) + } + } + .opacity(configuration.isPressed ? 0.8 : 1.0) + .scaleEffect(configuration.isPressed ? 0.98 : 1.0) + .animation( + .spring(duration: 0.2, bounce: 0.3), value: configuration.isPressed + ) + .animation(.easeOut(duration: 0.2), value: isHovered) + .onHover { hovering in + isHovered = hovering + } + } +} + +#Preview { + VStack { + Button("Hello") {} + .buttonStyle(.ultraIcon) + + Button { + + } label: { + Image(systemName: "paperclip") + }.buttonStyle(.ultraIcon) + + } + .frame(width: 200, height: 200) + .background(Color.gray) + +} diff --git a/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraSidebarButtonStyle.swift b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraSidebarButtonStyle.swift new file mode 100644 index 0000000..495619d --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraSidebarButtonStyle.swift @@ -0,0 +1,144 @@ +// +// UltraButtonStyle.swift +// UltraUI +// +// Created by John Mai on 2025/2/23. +// + +import SwiftUI + +extension ButtonStyle where Self == UltraSidebarButtonStyle { + static var ultraSidebar: some ButtonStyle { + UltraSidebarButtonStyle() + } +} + +public struct UltraSidebarButtonStyle: ButtonStyle { + @Environment(\.utlraSecondaryViewBackground) var utlraSecondaryViewBackground + + @State private var isHovered = false + private let cornerRadius: CGFloat + private let isSelected: Bool + private let disableHoverEffect: Bool + + var highlighted: Bool { + isHovered || isSelected + } + + init( + isSelected: Bool = true, + disableScaleEffect: Bool = false, + cornerRadius: CGFloat = 10 + ) { + self.isSelected = isSelected + self.disableHoverEffect = disableScaleEffect + self.cornerRadius = cornerRadius + } + + public init(_ isSelected: Bool) { + self.init( + isSelected: isSelected, + disableScaleEffect: true + ) + } + + public func makeBody(configuration: Configuration) -> some View { + configuration.label + .foregroundStyle(.white) + .background { + if highlighted { + RoundedRectangle(cornerRadius: cornerRadius) + .fill(utlraSecondaryViewBackground) + .overlay { + RoundedRectangle(cornerRadius: cornerRadius) + .fill( + LinearGradient( + colors: [ + .white.opacity(isHovered ? 0.1 : 0.05), + .clear, + + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + } + .shadow() + .shadow( + color: .white.opacity(isHovered ? 0.3 : 0.2), radius: 2, + x: -1, y: -1 + ) + .shadow( + color: .black.opacity(isHovered ? 0.3 : 0.2), radius: 2, + x: 1, y: 1 + ) + .shadow( + color: .white.opacity(isHovered ? 0.15 : 0.1), + radius: 8, x: 0, y: 0) + + RoundedRectangle(cornerRadius: cornerRadius) + .stroke( + LinearGradient( + colors: [ + .white.opacity(isHovered ? 0.6 : 0.5), + .clear, + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ), + lineWidth: 0.5 + ) + } + } + .opacity(configuration.isPressed ? 0.8 : 1.0) + .scaleEffect(configuration.isPressed ? 0.98 : 1.0) + .scaleEffect(isHovered && !disableHoverEffect ? 1.02 : 1.0) + .shadow( + color: .black.opacity( + configuration.isPressed ? 0.1 : isHovered ? 0.25 : 0.2 + ), + radius: configuration.isPressed ? 2 : isHovered ? 5 : 4, + x: 0, + y: configuration.isPressed ? 1 : isHovered ? 3 : 2 + ) + .animation( + .spring( + duration: 0.2, + bounce: 0.3 + ), + value: configuration.isPressed + ) + .animation( + .spring( + duration: 0.2, + bounce: 0.3 + ), + value: isHovered + ) + .onHover { hovering in + isHovered = hovering + } + .shadow() + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listItemTint(.clear) + .listRowInsets( + EdgeInsets( + top: 2, + leading: 0, + bottom: 2, + trailing: 0 + ) + ) + } +} + +#Preview { + VStack { + Button("Hello") {} + .buttonStyle(.ultraSidebar) + } + .frame(width: 200, height: 200) + .background(Color.gray) + +} diff --git a/Packages/UltraUI/Sources/UltraUI/Styles/HorizontalLabeledContentStyle.swift b/Packages/UltraUI/Sources/UltraUI/Styles/HorizontalLabeledContentStyle.swift new file mode 100644 index 0000000..b0eaa39 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Styles/HorizontalLabeledContentStyle.swift @@ -0,0 +1,24 @@ +// +// HorizontalLabeledContentStyle.swift +// UltraUI +// +// Created by John Mai on 2025/2/28. +// + +import SwiftUI + +public struct HorizontalLabeledContentStyle: LabeledContentStyle { + public func makeBody(configuration: Configuration) -> some View { + HStack { + configuration.label + .fontWeight(.medium) + Spacer() + configuration.content + .foregroundColor(.secondary) + } + } +} + +extension LabeledContentStyle where Self == HorizontalLabeledContentStyle { + public static var horizontal: HorizontalLabeledContentStyle { .init() } +} diff --git a/Packages/UltraUI/Sources/UltraUI/TextareaView.swift b/Packages/UltraUI/Sources/UltraUI/TextareaView.swift new file mode 100644 index 0000000..1e7a236 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/TextareaView.swift @@ -0,0 +1,164 @@ +// +// TextView.swift +// UltraUI +// +// Created by John Mai on 2025/2/23. +// + +import Foundation +import STTextView +import SwiftUI + +public struct TextareaView: NSViewRepresentable { + @Environment(\.isEnabled) private var isEnabled + @Environment(\.lineSpacing) private var lineSpacing + @Environment(\.utlraSubtitle) private var utlraSubtitle + + @Binding private var text: AttributedString + let placeholder: String + let font: NSFont + private var plugins: [any STPlugin] + + public init( + text: Binding, + placeholder: String = "66", + plugins: [any STPlugin] = [], + font: NSFont = .preferredFont(forTextStyle: .title3) + ) { + self._text = text + self.placeholder = placeholder + self.plugins = plugins + self.font = font + } + + public func makeNSView(context: Context) -> NSScrollView { + let scrollView = STTextView.scrollableTextView() + let textView = scrollView.documentView as! STTextView + + let placeholderView = NSTextField(labelWithString: placeholder) + placeholderView.font = font + placeholderView.textColor = NSColor(utlraSubtitle) + placeholderView.isHidden = !text.characters.isEmpty + placeholderView.translatesAutoresizingMaskIntoConstraints = false + placeholderView.lineBreakMode = .byWordWrapping + placeholderView.setContentCompressionResistancePriority( + .defaultLow, + for: .horizontal + ) + + placeholderView.refusesFirstResponder = true + placeholderView.isEditable = false + placeholderView.isSelectable = false + + textView.addSubview(placeholderView, positioned: .below, relativeTo: nil) + + NSLayoutConstraint.activate( + [ + placeholderView.leadingAnchor.constraint( + equalTo: textView.leadingAnchor, + constant: 4 + ), + placeholderView.topAnchor.constraint( + equalTo: textView.topAnchor, + constant: 0 + ), + placeholderView.trailingAnchor.constraint( + lessThanOrEqualTo: scrollView.contentView.trailingAnchor, + constant: -4 + ), + ] + ) + + context.coordinator.placeholderView = placeholderView + + textView.textDelegate = context.coordinator + textView.highlightSelectedLine = false + textView.isHorizontallyResizable = true + textView.showsLineNumbers = false + textView.textSelection = NSRange() + textView.textColor = .white + textView.markedTextAttributes = [.underlineColor: NSColor.clear] + textView.changeDocumentBackgroundColor(NSColor.clear) + + context.coordinator.isUpdating = true + textView.attributedText = NSAttributedString(text) + context.coordinator.isUpdating = false + + for plugin in plugins { + textView.addPlugin(plugin) + } + + return scrollView + } + + public func updateNSView(_ scrollView: NSScrollView, context: Context) { + context.coordinator.parent = self + + let textView = scrollView.documentView as! STTextView + + do { + context.coordinator.isUpdating = true + if context.coordinator.isDidChangeText == false { + textView.attributedText = NSAttributedString(text) + } + context.coordinator.isUpdating = false + context.coordinator.isDidChangeText = false + } + + let isEmpty = text.characters.isEmpty + context.coordinator.placeholderView?.isHidden = !isEmpty + context.coordinator.placeholderView?.stringValue = placeholder + context.coordinator.placeholderView?.font = font + + if textView.isEditable != isEnabled { + textView.isEditable = isEnabled + } + + if textView.isSelectable != isEnabled { + textView.isSelectable = isEnabled + } + + if textView.font != font { + textView.font = font + } + + textView.needsLayout = true + textView.needsDisplay = true + } + + public func makeCoordinator() -> TextCoordinator { + TextCoordinator(parent: self) + } + + @MainActor + public class TextCoordinator: @preconcurrency STTextViewDelegate { + var parent: TextareaView + var isUpdating: Bool = false + var isDidChangeText: Bool = false + weak var placeholderView: NSTextField? + + init(parent: TextareaView) { + self.parent = parent + } + + @MainActor + public func textViewDidChangeText(_ notification: Notification) { + guard let textView = notification.object as? STTextView else { + return + } + + let isEmpty = self.parent.text.characters.isEmpty + placeholderView?.isHidden = !isEmpty + + if !isUpdating { + let newTextValue = AttributedString( + textView.attributedText ?? NSAttributedString()) + + Task { @MainActor in + self.isDidChangeText = true + self.parent.text = newTextValue + } + } + } + } +} diff --git a/Packages/UltraUI/Sources/UltraUI/UltraNavigationSplitView.swift b/Packages/UltraUI/Sources/UltraUI/UltraNavigationSplitView.swift new file mode 100644 index 0000000..d7b73fe --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraNavigationSplitView.swift @@ -0,0 +1,306 @@ +// +// UltraNavigationSplitView.swift +// UltraUI +// +// Created by John Mai on 2025/2/22. +// + +import SwiftUI + +public enum UltraToolbarPlacement: CaseIterable { + case leading, trailing, principal +} + +public struct UltraToolbarItem: Identifiable, Equatable { + public var id: String = "" + let placement: UltraToolbarPlacement + let content: AnyView + + public init( + placement: UltraToolbarPlacement, + @ViewBuilder content: () -> Content + ) { + self.placement = placement + let view = content() + self.content = AnyView(view) + } + + private init( + id: String, + placement: UltraToolbarPlacement, + content: AnyView + ) { + self.id = id + self.placement = placement + self.content = content + } + + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.id == rhs.id + } + + func id(_ id: String) -> UltraToolbarItem { + UltraToolbarItem( + id: id, + placement: placement, + content: content + ) + } +} + +@resultBuilder +public enum UltraToolbarContentBuilder { + public static func buildBlock(_ components: UltraToolbarItem...) + -> [UltraToolbarItem] + { + components + } + + public static func buildOptional(_ component: [UltraToolbarItem]?) + -> [UltraToolbarItem] + { + component ?? [] + } + + public static func buildEither(first component: [UltraToolbarItem]) + -> [UltraToolbarItem] + { + component + } + + public static func buildEither(second component: [UltraToolbarItem]) + -> [UltraToolbarItem] + { + component + } + + public static func buildArray(_ components: [[UltraToolbarItem]]) + -> [UltraToolbarItem] + { + components.flatMap { $0 } + } + + public static func buildExpression(_ expression: UltraToolbarItem) + -> [UltraToolbarItem] + { + [expression] + } + + public static func buildPartialBlock(first: [UltraToolbarItem]) + -> [UltraToolbarItem] + { + first + } + + public static func buildPartialBlock( + accumulated: [UltraToolbarItem], next: [UltraToolbarItem] + ) -> [UltraToolbarItem] { + accumulated + next + } +} + +struct UltraToolbarModifier: ViewModifier { + let items: [UltraToolbarItem] + + @Environment(\.ultraNavigationState) var state + + func body(content: Content) -> some View { + content + .onAppear { state?.toolbarItems = items } + .onChange(of: items) { state?.toolbarItems = $1 } + .onDisappear { + if state?.toolbarItems == items { + state?.toolbarItems = [] + } + } + } +} + +struct UltraNavigationTitleViewModifier: ViewModifier { + let title: String + + @Environment(\.ultraNavigationState) var state + + func body(content: Content) -> some View { + content + .onAppear { state?.title = title } + .onChange(of: title) { state?.title = $1 } + .onDisappear { + if state?.title == title { + state?.title = "" + } + } + } +} + +extension View { + public func ultraToolbar( + @UltraToolbarContentBuilder content: () -> [UltraToolbarItem] + ) -> some View { + self.modifier(UltraToolbarModifier(items: content())) + } + + public func ultraNavigationTitle(_ title: String) -> some View { + modifier(UltraNavigationTitleViewModifier(title: title)) + } +} + +struct UltraNavigationStateKey: EnvironmentKey { + static let defaultValue: UltraNavigationState? = nil +} + +extension EnvironmentValues { + @Entry var ultraNavigationState: UltraNavigationState? +} + +@MainActor +@Observable +final class UltraNavigationState { + var title: String = "" + var toolbarItems: [UltraToolbarItem] = [] +} + +public struct UltraNavigationSplitView: View { + @State private var initialSidebarWidth: CGFloat + @State private var lastNonZeroWidth: CGFloat + @State private var isSidebarVisible = true + @State private var state = UltraNavigationState() + @State private var isDragging = false + @State private var isHovering = false + + let sidebar: () -> Sidebar + let detail: () -> Detail + + let minSidebarWidth: CGFloat + let maxSidebarWidth: CGFloat + + let showDivider: Bool + + public init( + initialSidebarWidth: CGFloat = 250, + minSidebarWidth: CGFloat = 200, + maxSidebarWidth: CGFloat = 400, + showDivider: Bool = true, + @ViewBuilder sidebar: @escaping () -> Sidebar, + @ViewBuilder detail: @escaping () -> Detail + ) { + _initialSidebarWidth = State(initialValue: initialSidebarWidth) + _lastNonZeroWidth = State(initialValue: initialSidebarWidth) + self.minSidebarWidth = minSidebarWidth + self.maxSidebarWidth = maxSidebarWidth + self.sidebar = sidebar + self.detail = detail + self.showDivider = showDivider + } + + public var body: some View { + HStack(spacing: .zero) { + if isSidebarVisible { + ZStack(alignment: .trailing) { + sidebar() + .frame(width: initialSidebarWidth) + .padding(.top, 32) + .background(UltraSidebarBackgroundView()) + + Rectangle() + .fill(Color.gray.opacity(isHovering || isDragging ? 0.3 : 0)) + .frame(width: 4) + .frame(maxHeight: .infinity) + .contentShape(Rectangle()) + .gesture( + DragGesture() + .onChanged { value in + isDragging = true + let newWidth = initialSidebarWidth + value.translation.width + if newWidth >= minSidebarWidth && newWidth <= maxSidebarWidth { + initialSidebarWidth = newWidth + lastNonZeroWidth = newWidth + } + } + .onEnded { _ in + isDragging = false + } + ) + .onHover { hovering in + isHovering = hovering + if hovering { + NSCursor.resizeLeftRight.push() + } else { + NSCursor.arrow.pop() + } + } + .animation(.spring, value: isHovering) + .animation(.spring, value: isDragging) + } + .transition(.move(edge: .leading)) + } + + VStack(spacing: .zero) { + if showDivider { + Divider() + .foregroundStyle(.secondary.opacity(0.2)) + } + detail() + .frame(maxWidth: .infinity, maxHeight: .infinity) + + } + .safeAreaInset(edge: .top, alignment: .center, spacing: 0) { + header().frame(height: 52) + } + } + .environment(\.ultraNavigationState, state) + } + + @ViewBuilder + func header() -> some View { + VStack(spacing: 0) { + Spacer() + + HStack { + if !isSidebarVisible { + Spacer() + .frame(width: 80) + } + + Button { + toggleSidebar() + } label: { + Image(systemName: "sidebar.leading") + .font(.title3) + } + .buttonStyle(.plain) + + ForEach(state.toolbarItems.filter { $0.placement == .leading }) { item in + item.content + } + + Spacer() + Text(LocalizedStringKey(state.title)) + .font(.headline) + + Spacer() + + ForEach(state.toolbarItems.filter { $0.placement == .trailing }) { item in + item.content + } + } + .padding(.horizontal, 10) + .padding(.trailing, 5) + Spacer() + } + .frame(height: 50) + .foregroundColor(.white) + } + + func toggleSidebar() { + withAnimation { + if isSidebarVisible { + lastNonZeroWidth = lastNonZeroWidth + lastNonZeroWidth = 0 + } else { + lastNonZeroWidth = lastNonZeroWidth + } + isSidebarVisible.toggle() + } + } +} diff --git a/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsPanel.swift b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsPanel.swift new file mode 100644 index 0000000..76ad4c5 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsPanel.swift @@ -0,0 +1,18 @@ +// +// UltraOptionsPanel.swift +// UltraUI +// +// Created by John Mai on 2025/2/26. +// + +import AppKit + +class UltraOptionsPanel: NSPanel { + override var canBecomeKey: Bool { + return true + } + + override var canBecomeMain: Bool { + return false + } +} diff --git a/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsWindowController.swift b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsWindowController.swift new file mode 100644 index 0000000..4379438 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsWindowController.swift @@ -0,0 +1,144 @@ +// +// UltraOptionsWindowController.swift +// UltraUI +// +// Created by John Mai on 2025/2/26. +// + +import AppKit +import SwiftUI + +final class UltraOptionsWindowController: NSWindowController, Sendable { + private var globalEventMonitor: Any? + private var localEventMonitor: Any? + + let padding: CGFloat = 4 + + convenience init(_ content: NSView) { + let window = UltraOptionsPanel( + contentRect: NSRect(x: 0, y: 0, width: 0, height: 0), + styleMask: [.borderless, .resizable, .nonactivatingPanel], + backing: .buffered, + defer: true + ) + + window.contentView = content + + window.level = .floating + window.isFloatingPanel = true + window.hidesOnDeactivate = false + window.collectionBehavior = .canJoinAllSpaces + + window.backgroundColor = NSColor.clear + window.hasShadow = true + window.isOpaque = false + + self.init(window: window) + } + + override func showWindow(_ sender: Any?) { + super.showWindow(sender) + window?.makeKeyAndOrderFront(nil) + startEventMonitoring() + } + + func showWindow() { + self.sizeWindowToFitContent() + self.showWindow(nil) + } + + func sizeWindowToFitContent() { + guard let window = self.window else { return } + let contentSize = window.contentView?.fittingSize ?? .zero + let windowSize = window.frame.size + let newSize = NSSize( + width: max(contentSize.width, windowSize.width), + height: max(contentSize.height, windowSize.height) + ) + window.setFrame( + NSRect(origin: window.frame.origin, size: newSize), + display: true + ) + } + + func setWindowPosition(_ rect: CGRect) { + guard let window = self.window, + let sourceWindow = NSApplication.shared.windows.first + else { return } + let targetX = + sourceWindow.frame.origin.x + rect.origin.x + - (window.frame.size.width - rect.size.width) / 2 + let targetY = + sourceWindow.frame.origin.y + sourceWindow.frame.height + - rect.origin.y - window.frame.size.height - rect.size.height + - padding + window.setFrameOrigin(NSPoint(x: targetX, y: targetY)) + } + + func closeWindow() { + stopEventMonitoring() + self.window?.close() + } + + private func startEventMonitoring() { + + stopEventMonitoring() + + globalEventMonitor = NSEvent.addGlobalMonitorForEvents(matching: [ + .leftMouseDown, .rightMouseDown, + ]) { [weak self] event in + guard let self = self else { return } + self.handleMouseEvent(event) + } + + localEventMonitor = NSEvent.addLocalMonitorForEvents(matching: [ + .leftMouseDown, .rightMouseDown, + ]) { [weak self] event in + guard let self = self else { return event } + + self.handleMouseEvent(event) + + return event + } + } + + private func handleMouseEvent(_ event: NSEvent) { + guard let window = self.window else { return } + + let clickLocation = event.locationInWindow + + var clickLocationInScreen: NSPoint + + if let eventWindow = event.window { + + let windowLocation = eventWindow.convertPoint( + toScreen: clickLocation) + clickLocationInScreen = windowLocation + } else { + + clickLocationInScreen = NSPoint( + x: clickLocation.x + (event.window?.frame.origin.x ?? 0), + y: clickLocation.y + (event.window?.frame.origin.y ?? 0) + ) + } + + if !NSPointInRect(clickLocationInScreen, window.frame) { + + DispatchQueue.main.async { + self.closeWindow() + } + } + } + + func stopEventMonitoring() { + if let globalEventMonitor = globalEventMonitor { + NSEvent.removeMonitor(globalEventMonitor) + self.globalEventMonitor = nil + } + + if let localEventMonitor = localEventMonitor { + NSEvent.removeMonitor(localEventMonitor) + self.localEventMonitor = nil + } + } +} diff --git a/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraPicker.swift b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraPicker.swift new file mode 100644 index 0000000..3501cbe --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraPicker.swift @@ -0,0 +1,83 @@ +// +// UltraPicker.swift +// UltraUI +// +// Created by John Mai on 2025/2/26. +// + +import SwiftUI + +public struct UltraPicker: View { + + @Environment(\.utlraViewBackground) var utlraViewBackground + + let options: [SelectionValue] + + @Binding var selection: SelectionValue? + + @State private var rect: CGRect = .zero + + @State private var controller: UltraOptionsWindowController? + + public init(options: [SelectionValue], selection: Binding) { + self.options = options + self._selection = selection + } + + public var body: some View { + Button { + showOptionsPanel() + } label: { + HStack { + Text(selection == nil ? "Select a model" : "\(selection!)") + .lineLimit(1) + Image(systemName: "chevron.down") + } + } + .onGeometryChange(for: CGRect.self) { + $0.frame(in: .global) + } action: { + rect = $0 + } + .buttonStyle(.ultraPlain) + } + + @ViewBuilder + private func optionsPanel() -> some View { + VStack { + ForEach(options, id: \.self) { option in + Button { + selection = option + controller?.close() + } label: { + Text("\(option)") + + .padding(.horizontal, 16) + .padding(.vertical, 8) + .frame(maxWidth: .infinity, alignment: .leading) + + } + .buttonStyle( + UltraSidebarButtonStyle(selection == option) + ) + } + } + .padding(8) + .background(utlraViewBackground) + .cornerRadius(10) + } + + private func showOptionsPanel() { + if controller == nil { + controller = UltraOptionsWindowController( + NSHostingView( + rootView: optionsPanel() + ) + ) + } + + controller?.showWindow() + controller?.setWindowPosition(rect) + + } +} diff --git a/Packages/UltraUI/Sources/UltraUI/UltraSection.swift b/Packages/UltraUI/Sources/UltraUI/UltraSection.swift new file mode 100644 index 0000000..1dd3b60 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraSection.swift @@ -0,0 +1,161 @@ +// +// UltraSection.swift +// UltraUI +// +// Created by John Mai on 2025/2/28. +// + +import SwiftUI + +public struct UltraSection { + private var header: Header + private var content: Content + private var footer: Footer + + private var backgroundColor: Color = .clear + private var cornerRadius: CGFloat = 0 + private var borderColor: Color = .clear + private var borderWidth: CGFloat = 0 + private var padding: EdgeInsets = EdgeInsets() + private var isCollapsible: Bool = false + private var _isExpanded: Binding? + + @Environment(\.utlraViewBackground) var utlraViewBackground + + private init(header: Header, content: Content, footer: Footer) { + self.header = header + self.content = content + self.footer = footer + } +} + +extension UltraSection: View where Header: View, Content: View, Footer: View { + public var body: some View { + VStack(alignment: .leading, spacing: 0) { + header.padding(.vertical, 12) + if _isExpanded?.wrappedValue ?? true { + VStack { + Divided { + content + } + } + .padding() + .background(utlraViewBackground) + .cornerRadius(10) + + } + + footer + } + .padding(padding) + .background(backgroundColor) + .cornerRadius(cornerRadius) + .overlay( + RoundedRectangle(cornerRadius: cornerRadius) + .stroke(borderColor, lineWidth: borderWidth) + ) + } + + public func backgroundColor(_ color: Color) -> UltraSection { + var copy = self + copy.backgroundColor = color + return copy + } + + public func cornerRadius(_ radius: CGFloat) -> UltraSection { + var copy = self + copy.cornerRadius = radius + return copy + } + + public func border(color: Color, width: CGFloat) -> UltraSection { + var copy = self + copy.borderColor = color + copy.borderWidth = width + return copy + } + + public func padding(_ insets: EdgeInsets) -> UltraSection { + var copy = self + copy.padding = insets + return copy + } + + public func padding(_ amount: CGFloat) -> UltraSection { + let insets = EdgeInsets(top: amount, leading: amount, bottom: amount, trailing: amount) + return padding(insets) + } + + public func collapsible(_ collapsible: Bool) -> UltraSection { + var copy = self + copy.isCollapsible = collapsible + return copy + } +} + +extension UltraSection where Header: View, Content: View, Footer: View { + public init( + @ViewBuilder content: () -> Content, @ViewBuilder header: () -> Header, + @ViewBuilder footer: () -> Footer + ) { + self.init(header: header(), content: content(), footer: footer()) + } +} + +extension UltraSection where Header == EmptyView, Content: View, Footer: View { + public init(@ViewBuilder content: () -> Content, @ViewBuilder footer: () -> Footer) { + self.init(header: EmptyView(), content: content(), footer: footer()) + } +} + +extension UltraSection where Header: View, Content: View, Footer == EmptyView { + public init(@ViewBuilder content: () -> Content, @ViewBuilder header: () -> Header) { + self.init(header: header(), content: content(), footer: EmptyView()) + } +} + +@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) +extension UltraSection where Header == EmptyView, Content: View, Footer == EmptyView { + public init(@ViewBuilder content: () -> Content) { + self.init(header: EmptyView(), content: content(), footer: EmptyView()) + } +} + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +extension UltraSection where Header == Text, Content: View, Footer == EmptyView { + public init(_ titleKey: LocalizedStringKey, @ViewBuilder content: () -> Content) { + self.init(header: Text(titleKey), content: content(), footer: EmptyView()) + } + + public init(_ title: S, @ViewBuilder content: () -> Content) where S: StringProtocol { + self.init(header: Text(title), content: content(), footer: EmptyView()) + } +} + +@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *) +extension UltraSection where Header: View, Content: View, Footer == EmptyView { + /// 创建一个带有展开状态控制的section + public init( + isExpanded: Binding, @ViewBuilder content: () -> Content, + @ViewBuilder header: () -> Header + ) { + self.init(header: header(), content: content(), footer: EmptyView()) + self._isExpanded = isExpanded + } +} + +extension UltraSection where Header == Text, Content: View, Footer == EmptyView { + public init( + _ titleKey: LocalizedStringKey, isExpanded: Binding, + @ViewBuilder content: () -> Content + ) { + self.init(header: Text(titleKey), content: content(), footer: EmptyView()) + self._isExpanded = isExpanded + } + + public init(_ title: S, isExpanded: Binding, @ViewBuilder content: () -> Content) + where S: StringProtocol { + self.init(header: Text(title), content: content(), footer: EmptyView()) + self._isExpanded = isExpanded + } +} diff --git a/Packages/UltraUI/Sources/UltraUI/UltraSecureTextField.swift b/Packages/UltraUI/Sources/UltraUI/UltraSecureTextField.swift new file mode 100644 index 0000000..2b816f0 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraSecureTextField.swift @@ -0,0 +1,88 @@ +// +// UltraSecureTextField.swift +// UltraUI +// +// Created by John Mai on 2025/3/1. +// + +import SwiftUI + +public struct UltraSecureTextField: NSViewRepresentable { + @Binding var text: String + + let placeholder: String + + @Environment(\.utlraPlaceholder) var utlraPlaceholder + @Environment(\.utlraText) var utlraText + + public init( + text: Binding, + placeholder: String + ) { + self._text = text + self.placeholder = placeholder + } + + public func makeNSView(context: Context) -> NSTextField { + let textField = NSSecureTextField() + textField.delegate = context.coordinator + textField.placeholderString = placeholder + + let attributes: [NSAttributedString.Key: Any] = [ + .foregroundColor: NSColor(utlraPlaceholder), + .font: NSFont.preferredFont(forTextStyle: .body), + ] + textField.placeholderAttributedString = NSAttributedString( + string: placeholder, + attributes: attributes + ) + + textField.font = NSFont.preferredFont(forTextStyle: .body) + textField.textColor = NSColor(utlraText) + textField.drawsBackground = false + textField.backgroundColor = .clear + textField.isBordered = false + textField.isBezeled = false + textField.focusRingType = .none + textField.isEditable = true + textField.isSelectable = true + + return textField + } + + public func updateNSView(_ nsView: NSTextField, context: Context) { + nsView.stringValue = text + } + + public func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + public class Coordinator: NSObject, NSTextFieldDelegate { + var parent: UltraSecureTextField + + init(_ parent: UltraSecureTextField) { + self.parent = parent + } + + public func controlTextDidChange(_ notification: Notification) { + if let textField = notification.object as? NSTextField { + parent.text = textField.stringValue + } + } + } +} + +#Preview { + + @Previewable @State var password: String = "" + + VStack { + UltraSecureTextField( + text: $password, + placeholder: "Enter your password" + ) + } + .padding() + .background(Color.black.opacity(0.5)) +} diff --git a/Packages/UltraUI/Sources/UltraUI/UltraSidebarBackgroundView.swift b/Packages/UltraUI/Sources/UltraUI/UltraSidebarBackgroundView.swift new file mode 100644 index 0000000..25be07c --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraSidebarBackgroundView.swift @@ -0,0 +1,51 @@ +// +// UltraBackgroundView.swift +// UltraUI +// +// Created by John Mai on 2025/2/23. +// + +import SwiftUI + +struct UltraSidebarBackgroundView: View { + @Environment(\.utlraViewBackground) var utlraViewBackground + + var body: some View { + ZStack { + Rectangle() + .fill(utlraViewBackground) + .overlay { + Rectangle() + .fill( + LinearGradient( + colors: [ + .black.opacity(0.2), + .clear, + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + } + .shadow( + color: .black.opacity(0.1), + radius: 2, + x: -1, + y: -1 + ) + .shadow( + color: .white.opacity(0.1), + radius: 2, + x: 1, + y: 1 + ) + .shadow( + color: .black.opacity(0.05), + radius: 8, + x: 0, + y: 0 + ) + } + + } +} diff --git a/Packages/UltraUI/Sources/UltraUI/UltraTextField.swift b/Packages/UltraUI/Sources/UltraUI/UltraTextField.swift new file mode 100644 index 0000000..7a3521d --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraTextField.swift @@ -0,0 +1,88 @@ +// +// UltraTextField.swift +// UltraUI +// +// Created by John Mai on 2025/3/1. +// + +import SwiftUI + +public struct UltraTextField: NSViewRepresentable { + @Binding var text: String + + let placeholder: String + + @Environment(\.utlraPlaceholder) var utlraPlaceholder + @Environment(\.utlraText) var utlraText + + public init( + text: Binding, + placeholder: String + ) { + self._text = text + self.placeholder = placeholder + } + + public func makeNSView(context: Context) -> NSTextField { + let textField = NSTextField() + textField.delegate = context.coordinator + textField.placeholderString = placeholder + + let attributes: [NSAttributedString.Key: Any] = [ + .foregroundColor: NSColor(utlraPlaceholder), + .font: NSFont.preferredFont(forTextStyle: .body), + ] + textField.placeholderAttributedString = NSAttributedString( + string: placeholder, + attributes: attributes + ) + + textField.font = NSFont.preferredFont(forTextStyle: .body) + textField.textColor = NSColor(utlraText) + textField.drawsBackground = false + textField.backgroundColor = .clear + textField.isBordered = false + textField.isBezeled = false + textField.focusRingType = .none + textField.isEditable = true + textField.isSelectable = true + + return textField + } + + public func updateNSView(_ nsView: NSTextField, context: Context) { + nsView.stringValue = text + } + + public func makeCoordinator() -> Coordinator { + Coordinator(self) + } + + public class Coordinator: NSObject, NSTextFieldDelegate { + var parent: UltraTextField + + init(_ parent: UltraTextField) { + self.parent = parent + } + + public func controlTextDidChange(_ notification: Notification) { + if let textField = notification.object as? NSTextField { + parent.text = textField.stringValue + } + } + } +} + +#Preview { + + @Previewable @State var name: String = "" + + VStack { + UltraTextField( + text: $name, + placeholder: "Enter your name" + ) + } + .padding() + .background(Color.black.opacity(0.5)) +} diff --git a/Packages/UltraUI/Sources/UltraUI/UltraWindowBackgroundView.swift b/Packages/UltraUI/Sources/UltraUI/UltraWindowBackgroundView.swift new file mode 100644 index 0000000..284badd --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraWindowBackgroundView.swift @@ -0,0 +1,68 @@ +// +// UltraWindowBackgroundView.swift +// UltraUI +// +// Created by John Mai on 2025/2/23. +// + +import SwiftUI + +public struct UltraWindowBackgroundView: View { + + @Environment(\.utlraRadius) var utlraRadius + @Environment(\.utlraWindowBackgroundColor) var utlraWindowBackgroundColor + + public init() {} + + public var body: some View { + ZStack { + RoundedRectangle(cornerRadius: utlraRadius) + .fill(utlraWindowBackgroundColor) + .overlay { + RoundedRectangle(cornerRadius: utlraRadius) + .fill( + LinearGradient( + colors: [ + .white.opacity(0.24), + .clear, + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ) + ) + } + .shadow( + color: .white.opacity(0.1), + radius: 2, + x: -1, + y: -1 + ) + .shadow( + color: .black.opacity(0.1), + radius: 2, + x: 1, + y: 1 + ) + .shadow( + color: .white.opacity(0.05), + radius: 8, + x: 0, + y: 0 + ) + + RoundedRectangle(cornerRadius: utlraRadius) + .stroke( + LinearGradient( + colors: [ + .white.opacity(0.5), + .clear, + ], + startPoint: .topLeading, + endPoint: .bottomTrailing + ), + lineWidth: 0.5 + ) + } + + } +} diff --git a/Packages/UltraUI/Tests/UltraUITests/UltraUITests.swift b/Packages/UltraUI/Tests/UltraUITests/UltraUITests.swift new file mode 100644 index 0000000..228f656 --- /dev/null +++ b/Packages/UltraUI/Tests/UltraUITests/UltraUITests.swift @@ -0,0 +1,7 @@ +import Testing + +@testable import UltraUI + +@Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. +} From 20bfe7aa7376f6a3d9b5919925627c83fe5590de Mon Sep 17 00:00:00 2001 From: John Mai Date: Sat, 8 Mar 2025 13:38:48 +0800 Subject: [PATCH 2/3] refactor: streamline window behavior and event monitoring in UltraOptionsPanel and UltraOptionsWindowController --- .../UltraPicker/UltraOptionsPanel.swift | 9 +- .../UltraOptionsWindowController.swift | 91 ++++++++++++++----- .../UltraUI/UltraPicker/UltraPicker.swift | 37 ++------ 3 files changed, 77 insertions(+), 60 deletions(-) diff --git a/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsPanel.swift b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsPanel.swift index 76ad4c5..d2b41cb 100644 --- a/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsPanel.swift +++ b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsPanel.swift @@ -8,11 +8,6 @@ import AppKit class UltraOptionsPanel: NSPanel { - override var canBecomeKey: Bool { - return true - } - - override var canBecomeMain: Bool { - return false - } + override var canBecomeKey: Bool { true } + override var canBecomeMain: Bool { false } } diff --git a/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsWindowController.swift b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsWindowController.swift index 4379438..14c5399 100644 --- a/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsWindowController.swift +++ b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsWindowController.swift @@ -28,14 +28,19 @@ final class UltraOptionsWindowController: NSWindowController, Sendable { window.isFloatingPanel = true window.hidesOnDeactivate = false window.collectionBehavior = .canJoinAllSpaces - - window.backgroundColor = NSColor.clear window.hasShadow = true window.isOpaque = false + window.backgroundColor = .clear self.init(window: window) } + deinit { + Task { [self] in + await stopEventMonitoring() + } + } + override func showWindow(_ sender: Any?) { super.showWindow(sender) window?.makeKeyAndOrderFront(nil) @@ -65,14 +70,58 @@ final class UltraOptionsWindowController: NSWindowController, Sendable { guard let window = self.window, let sourceWindow = NSApplication.shared.windows.first else { return } - let targetX = - sourceWindow.frame.origin.x + rect.origin.x - - (window.frame.size.width - rect.size.width) / 2 - let targetY = - sourceWindow.frame.origin.y + sourceWindow.frame.height - - rect.origin.y - window.frame.size.height - rect.size.height - - padding + + let rectInScreen = NSRect( + x: sourceWindow.frame.origin.x + rect.origin.x, + y: sourceWindow.frame.origin.y + sourceWindow.frame.height - rect.origin.y + - rect.size.height, + width: rect.size.width, + height: rect.size.height + ) + + guard + let currentScreen = NSScreen.screens.first(where: { + NSIntersectsRect(rectInScreen, $0.frame) + }) ?? NSScreen.main + else { + window.setFrameOrigin( + NSPoint( + x: rectInScreen.midX - window.frame.width / 2, + y: rectInScreen.origin.y - window.frame.size.height - padding)) + window.setWindowBackgroundBlurRadius() + return + } + + let safeMargin: CGFloat = 10.0 + + var targetX = rectInScreen.midX - window.frame.width / 2 + let targetYBelow = rectInScreen.origin.y - window.frame.size.height - padding + let targetYAbove = rectInScreen.origin.y + rectInScreen.size.height + padding + + let screenLeftX = currentScreen.visibleFrame.origin.x + let screenRightX = screenLeftX + currentScreen.visibleFrame.width + + if targetX < (screenLeftX + safeMargin) { + targetX = screenLeftX + safeMargin + } + + let rightEdgePosition = targetX + window.frame.width + if rightEdgePosition > (screenRightX - safeMargin) { + targetX = screenRightX - window.frame.width - safeMargin + } + + var targetY: CGFloat + + let screenBottomY = currentScreen.visibleFrame.origin.y + + if targetYBelow < (screenBottomY + safeMargin) { + targetY = targetYAbove + } else { + targetY = targetYBelow + } + window.setFrameOrigin(NSPoint(x: targetX, y: targetY)) + window.setWindowBackgroundBlurRadius() } func closeWindow() { @@ -81,23 +130,20 @@ final class UltraOptionsWindowController: NSWindowController, Sendable { } private func startEventMonitoring() { - stopEventMonitoring() - globalEventMonitor = NSEvent.addGlobalMonitorForEvents(matching: [ - .leftMouseDown, .rightMouseDown, - ]) { [weak self] event in + let mouseEvents: NSEvent.EventTypeMask = [.leftMouseDown, .rightMouseDown] + + globalEventMonitor = NSEvent.addGlobalMonitorForEvents(matching: mouseEvents) { + [weak self] event in guard let self = self else { return } self.handleMouseEvent(event) } - localEventMonitor = NSEvent.addLocalMonitorForEvents(matching: [ - .leftMouseDown, .rightMouseDown, - ]) { [weak self] event in + localEventMonitor = NSEvent.addLocalMonitorForEvents(matching: mouseEvents) { + [weak self] event in guard let self = self else { return event } - self.handleMouseEvent(event) - return event } } @@ -123,20 +169,19 @@ final class UltraOptionsWindowController: NSWindowController, Sendable { } if !NSPointInRect(clickLocationInScreen, window.frame) { - - DispatchQueue.main.async { - self.closeWindow() + DispatchQueue.main.async { [weak self] in + self?.closeWindow() } } } func stopEventMonitoring() { - if let globalEventMonitor = globalEventMonitor { + if let globalEventMonitor { NSEvent.removeMonitor(globalEventMonitor) self.globalEventMonitor = nil } - if let localEventMonitor = localEventMonitor { + if let localEventMonitor { NSEvent.removeMonitor(localEventMonitor) self.localEventMonitor = nil } diff --git a/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraPicker.swift b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraPicker.swift index 3501cbe..0982c56 100644 --- a/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraPicker.swift +++ b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraPicker.swift @@ -8,9 +8,6 @@ import SwiftUI public struct UltraPicker: View { - - @Environment(\.utlraViewBackground) var utlraViewBackground - let options: [SelectionValue] @Binding var selection: SelectionValue? @@ -31,6 +28,7 @@ public struct UltraPicker: View { HStack { Text(selection == nil ? "Select a model" : "\(selection!)") .lineLimit(1) + .truncationMode(.head) Image(systemName: "chevron.down") } } @@ -42,42 +40,21 @@ public struct UltraPicker: View { .buttonStyle(.ultraPlain) } - @ViewBuilder - private func optionsPanel() -> some View { - VStack { - ForEach(options, id: \.self) { option in - Button { - selection = option - controller?.close() - } label: { - Text("\(option)") - - .padding(.horizontal, 16) - .padding(.vertical, 8) - .frame(maxWidth: .infinity, alignment: .leading) - - } - .buttonStyle( - UltraSidebarButtonStyle(selection == option) - ) - } - } - .padding(8) - .background(utlraViewBackground) - .cornerRadius(10) - } - private func showOptionsPanel() { if controller == nil { controller = UltraOptionsWindowController( NSHostingView( - rootView: optionsPanel() + rootView: UltraOptions( + selection: $selection, + options: options, + onSelection: { _ in + controller?.closeWindow() + }) ) ) } controller?.showWindow() controller?.setWindowPosition(rect) - } } From 6613dfb5b626fae2b0670621a826f4d4d97b8591 Mon Sep 17 00:00:00 2001 From: John Mai Date: Wed, 12 Mar 2025 21:21:28 +0800 Subject: [PATCH 3/3] =?UTF-8?q?refactor=E2=80=A6=E2=80=A6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ChatMLX.xcodeproj/project.pbxproj | 24 +- .../xcshareddata/swiftpm/Package.resolved | 123 +++++++++- .../xcschemes/xcschememanagement.plist | 27 +- ChatMLX/ChatMLX.entitlements | 12 +- ChatMLX/ChatMLXApp.swift | 27 +- Packages/Common/Sources/Common/AppStore.swift | 15 -- .../Extensions/Binding+Extensions.swift | 14 -- .../Services/HuggingfaceHubService.swift | 54 ---- Packages/Conversation/Package.swift | 10 +- .../Sources/Conversation/BubbleShape.swift | 79 ++++++ .../Sources/Conversation/ChatView.swift | 164 ------------- .../Conversation/ConversationDetailView.swift | 118 +++++++++ .../ConversationSidebarView.swift | 58 ++++- .../Conversation/ConversationStore.swift | 231 +++++++++++++++++- .../Conversation/ConversationView.swift | 66 +++-- .../Sources/Conversation/MessageBubble.swift | 70 ++++++ .../Conversation/PromptEditorView.swift | 53 ++-- Packages/{Common => Database}/.gitignore | 0 Packages/{Models => Database}/Package.swift | 18 +- .../Sources/Database/AppDatabase.swift | 67 +++++ .../Database/DatabaseConfiguration.swift | 24 ++ .../Sources/Database/DatabaseMigrations.swift | 68 ++++++ .../Sources/Database/DatabaseMockData.swift | 56 +++++ .../Sources/Database/Models/Asset.swift | 24 ++ .../Database/Models/Conversation.swift | 60 +++++ .../Sources/Database/Models/Message.swift | 54 ++++ .../Database/Models/MessageAsset.swift | 14 ++ .../Sources/Database/Repository.swift | 22 ++ .../Tests/DatabaseTests/DatabaseTests.swift | 21 ++ Packages/{Models => Intelligence}/.gitignore | 0 Packages/Intelligence/Package.swift | 38 +++ .../Entities/ChatCompletionOptions.swift | 26 ++ .../Entities/ChatCompletionStreamChunk.swift | 12 + .../Intelligence/Entities/Message.swift | 18 ++ .../Sources/Intelligence/Entities/Model.swift | 32 +++ .../Intelligence/Entities/ModelType.swift | 22 ++ .../Intelligence/Entities/Provider.swift | 19 ++ .../Sources/Intelligence/Entities/Role.swift | 13 + .../Sources/Intelligence/Intelligence.swift | 2 + .../Intelligence/LLM/MLXProvider.swift | 156 ++++++++++++ .../Intelligence/LLM/OpenAIProvider.swift | 47 ++++ .../Protocols/ProviderProtocol.swift | 16 ++ .../Intelligence/ProviderFactory.swift | 19 ++ .../IntelligenceTests.swift} | 3 +- .../IntelligenceTests/MLXProviderTests.swift | 25 ++ .../Models/Sources/Models/Conversation.swift | 52 ---- Packages/Models/Sources/Models/Message.swift | 27 -- Packages/Models/Sources/Models/Model.swift | 43 ---- Packages/Models/Sources/Models/Role.swift | 13 - .../Tests/ModelsTests/LanguageTests.swift | 21 -- Packages/Settings/Package.swift | 6 +- .../Settings/Sources/Settings/AboutView.swift | 2 +- .../Settings/DefaultConversationView.swift | 2 +- .../DownloadManager/DownloadManagerView.swift | 53 ++++ .../DownloadManager/DownloadTaskView.swift | 67 +++++ .../Settings/DownloadManagerView.swift | 14 -- .../Settings/ExperimentalFeaturesView.swift | 2 +- .../Sources/Settings/GeneralView.swift | 5 +- .../Sources/Settings/HuggingFaceView.swift | 4 +- .../MCPServers/AddMCPServerView.swift | 75 ++++++ .../Settings/MCPServers/MCPServersView.swift | 90 +++++++ .../Settings/MCPServers/ServerItemView.swift | 102 ++++++++ .../Sources/Settings/MCPServersView.swift | 14 -- .../Sources/Settings/MLXCommunityView.swift | 2 +- .../Settings/Models/DownloadTask.swift | 31 +++ .../Settings}/Models/SettingsTab.swift | 0 .../Sources/Settings/ModelsView.swift | 49 +++- .../Sources/Settings/ProvidersView.swift | 69 +++++- .../Sources/Settings/SearchView.swift | 2 +- .../Settings/SettingsSidebarView.swift | 4 +- .../Sources/Settings/SettingsStore.swift | 31 ++- .../Sources/Settings/SettingsView.swift | 11 +- Packages/UltraUI/Package.swift | 3 + .../EnvironmentValues+Extensions.swift | 3 +- .../Extensions/NSWindow+Extensions.swift | 13 +- .../UltraUI/Extensions/View+Extensions.swift | 2 +- .../UltraWindowStyleViewModifier.swift | 1 + .../ButtonStyles/UltraButtonStyle.swift | 2 + .../ButtonStyles/UltraPlainButtonStyle.swift | 6 +- .../UltraUI/UltraNavigationSplitView.swift | 36 ++- .../UltraUI/UltraPicker/UltraOptions.swift | 40 +++ .../UltraOptionsWindowController.swift | 4 +- .../Sources/UltraUI/UltraSection.swift | 7 +- .../UltraUI/UltraSidebarBackgroundView.swift | 2 +- Packages/Utilities/.gitignore | 8 + Packages/{Common => Utilities}/Package.swift | 18 +- .../Sources/Utilities/AppStore.swift | 31 +++ .../Extensions/Binding+Extensions.swift | 45 ++++ .../Extensions/Date+Extensions.swift | 2 +- .../Extensions/Defaults+Extensions.swift | 10 +- .../Sources}/Utilities/MarkdownMetadata.swift | 2 +- .../Models/HuggingFaceEndpoint.swift | 0 .../Sources/Utilities}/Models/Language.swift | 2 +- .../Services/HuggingfaceHubService.swift | 92 +++++++ .../Sources/Utilities/Utilities.swift | 2 + .../UtilitiesTests/UtilitiesTests.swift} | 3 +- README-zh_CN.md | 2 +- README.md | 2 +- 98 files changed, 2520 insertions(+), 630 deletions(-) delete mode 100644 Packages/Common/Sources/Common/AppStore.swift delete mode 100644 Packages/Common/Sources/Common/Extensions/Binding+Extensions.swift delete mode 100644 Packages/Common/Sources/Common/Services/HuggingfaceHubService.swift create mode 100644 Packages/Conversation/Sources/Conversation/BubbleShape.swift delete mode 100644 Packages/Conversation/Sources/Conversation/ChatView.swift create mode 100644 Packages/Conversation/Sources/Conversation/ConversationDetailView.swift create mode 100644 Packages/Conversation/Sources/Conversation/MessageBubble.swift rename Packages/{Common => Database}/.gitignore (100%) rename Packages/{Models => Database}/Package.swift (62%) create mode 100644 Packages/Database/Sources/Database/AppDatabase.swift create mode 100644 Packages/Database/Sources/Database/DatabaseConfiguration.swift create mode 100644 Packages/Database/Sources/Database/DatabaseMigrations.swift create mode 100644 Packages/Database/Sources/Database/DatabaseMockData.swift create mode 100644 Packages/Database/Sources/Database/Models/Asset.swift create mode 100644 Packages/Database/Sources/Database/Models/Conversation.swift create mode 100644 Packages/Database/Sources/Database/Models/Message.swift create mode 100644 Packages/Database/Sources/Database/Models/MessageAsset.swift create mode 100644 Packages/Database/Sources/Database/Repository.swift create mode 100644 Packages/Database/Tests/DatabaseTests/DatabaseTests.swift rename Packages/{Models => Intelligence}/.gitignore (100%) create mode 100644 Packages/Intelligence/Package.swift create mode 100644 Packages/Intelligence/Sources/Intelligence/Entities/ChatCompletionOptions.swift create mode 100644 Packages/Intelligence/Sources/Intelligence/Entities/ChatCompletionStreamChunk.swift create mode 100644 Packages/Intelligence/Sources/Intelligence/Entities/Message.swift create mode 100644 Packages/Intelligence/Sources/Intelligence/Entities/Model.swift create mode 100644 Packages/Intelligence/Sources/Intelligence/Entities/ModelType.swift create mode 100644 Packages/Intelligence/Sources/Intelligence/Entities/Provider.swift create mode 100644 Packages/Intelligence/Sources/Intelligence/Entities/Role.swift create mode 100644 Packages/Intelligence/Sources/Intelligence/Intelligence.swift create mode 100644 Packages/Intelligence/Sources/Intelligence/LLM/MLXProvider.swift create mode 100644 Packages/Intelligence/Sources/Intelligence/LLM/OpenAIProvider.swift create mode 100644 Packages/Intelligence/Sources/Intelligence/Protocols/ProviderProtocol.swift create mode 100644 Packages/Intelligence/Sources/Intelligence/ProviderFactory.swift rename Packages/{Models/Tests/ModelsTests/ModelsTests.swift => Intelligence/Tests/IntelligenceTests/IntelligenceTests.swift} (82%) create mode 100644 Packages/Intelligence/Tests/IntelligenceTests/MLXProviderTests.swift delete mode 100644 Packages/Models/Sources/Models/Conversation.swift delete mode 100644 Packages/Models/Sources/Models/Message.swift delete mode 100644 Packages/Models/Sources/Models/Model.swift delete mode 100644 Packages/Models/Sources/Models/Role.swift delete mode 100644 Packages/Models/Tests/ModelsTests/LanguageTests.swift create mode 100644 Packages/Settings/Sources/Settings/DownloadManager/DownloadManagerView.swift create mode 100644 Packages/Settings/Sources/Settings/DownloadManager/DownloadTaskView.swift delete mode 100644 Packages/Settings/Sources/Settings/DownloadManagerView.swift create mode 100644 Packages/Settings/Sources/Settings/MCPServers/AddMCPServerView.swift create mode 100644 Packages/Settings/Sources/Settings/MCPServers/MCPServersView.swift create mode 100644 Packages/Settings/Sources/Settings/MCPServers/ServerItemView.swift delete mode 100644 Packages/Settings/Sources/Settings/MCPServersView.swift create mode 100644 Packages/Settings/Sources/Settings/Models/DownloadTask.swift rename Packages/{Models/Sources => Settings/Sources/Settings}/Models/SettingsTab.swift (100%) create mode 100644 Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptions.swift create mode 100644 Packages/Utilities/.gitignore rename Packages/{Common => Utilities}/Package.swift (73%) create mode 100644 Packages/Utilities/Sources/Utilities/AppStore.swift create mode 100644 Packages/Utilities/Sources/Utilities/Extensions/Binding+Extensions.swift rename Packages/{Common/Sources/Common => Utilities/Sources/Utilities}/Extensions/Date+Extensions.swift (95%) rename Packages/{Common/Sources/Common => Utilities/Sources/Utilities}/Extensions/Defaults+Extensions.swift (65%) rename Packages/{Common/Sources/Common => Utilities/Sources}/Utilities/MarkdownMetadata.swift (99%) rename Packages/{Models/Sources => Utilities/Sources/Utilities}/Models/HuggingFaceEndpoint.swift (100%) rename Packages/{Models/Sources => Utilities/Sources/Utilities}/Models/Language.swift (99%) create mode 100644 Packages/Utilities/Sources/Utilities/Services/HuggingfaceHubService.swift create mode 100644 Packages/Utilities/Sources/Utilities/Utilities.swift rename Packages/{Common/Tests/CommonTests/CommonTests.swift => Utilities/Tests/UtilitiesTests/UtilitiesTests.swift} (84%) diff --git a/ChatMLX.xcodeproj/project.pbxproj b/ChatMLX.xcodeproj/project.pbxproj index 3ecd5b0..b2b2269 100644 --- a/ChatMLX.xcodeproj/project.pbxproj +++ b/ChatMLX.xcodeproj/project.pbxproj @@ -9,17 +9,16 @@ /* Begin PBXBuildFile section */ 523068232D71E6540069F093 /* Conversation in Frameworks */ = {isa = PBXBuildFile; productRef = 523068222D71E6540069F093 /* Conversation */; }; 523068982D71F0FC0069F093 /* Settings in Frameworks */ = {isa = PBXBuildFile; productRef = 523068972D71F0FC0069F093 /* Settings */; }; - 52996F0C2D6F7787003AD246 /* HuggingfaceHub in Frameworks */ = {isa = PBXBuildFile; productRef = 52996F0B2D6F7787003AD246 /* HuggingfaceHub */; }; - 52996F0F2D6F77A2003AD246 /* HuggingfaceHub in Frameworks */ = {isa = PBXBuildFile; productRef = 52996F0E2D6F77A2003AD246 /* HuggingfaceHub */; }; /* End PBXBuildFile section */ /* Begin PBXFileReference section */ + 5216AFDE2D7C5E1900556FD6 /* Database */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Database; path = Packages/Database; sourceTree = ""; }; 523067CF2D71DEE70069F093 /* UltraUI */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = UltraUI; path = Packages/UltraUI; sourceTree = ""; }; - 523067D42D71E5040069F093 /* Common */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Common; path = Packages/Common; sourceTree = SOURCE_ROOT; }; 523068212D71E6390069F093 /* Conversation */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Conversation; path = Packages/Conversation; sourceTree = ""; }; - 523068242D71E68D0069F093 /* Models */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Models; path = Packages/Models; sourceTree = SOURCE_ROOT; }; 523068672D71F09C0069F093 /* Settings */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Settings; path = Packages/Settings; sourceTree = SOURCE_ROOT; }; 526675422C85EDCB001EF113 /* ChatMLX.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = ChatMLX.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 526DAC492D7DC21D00CD9270 /* Intelligence */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Intelligence; path = Packages/Intelligence; sourceTree = ""; }; + 527749362D807A3A00D420E4 /* Utilities */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Utilities; path = Packages/Utilities; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -31,8 +30,6 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 52996F0F2D6F77A2003AD246 /* HuggingfaceHub in Frameworks */, - 52996F0C2D6F7787003AD246 /* HuggingfaceHub in Frameworks */, 523068982D71F0FC0069F093 /* Settings in Frameworks */, 523068232D71E6540069F093 /* Conversation in Frameworks */, ); @@ -51,9 +48,10 @@ 526675392C85EDCB001EF113 = { isa = PBXGroup; children = ( - 523067D42D71E5040069F093 /* Common */, + 527749362D807A3A00D420E4 /* Utilities */, + 526DAC492D7DC21D00CD9270 /* Intelligence */, + 5216AFDE2D7C5E1900556FD6 /* Database */, 523068212D71E6390069F093 /* Conversation */, - 523068242D71E68D0069F093 /* Models */, 523068672D71F09C0069F093 /* Settings */, 523067CF2D71DEE70069F093 /* UltraUI */, 52996ED72D6F7437003AD246 /* ChatMLX */, @@ -90,8 +88,6 @@ ); name = ChatMLX; packageProductDependencies = ( - 52996F0B2D6F7787003AD246 /* HuggingfaceHub */, - 52996F0E2D6F77A2003AD246 /* HuggingfaceHub */, 523068222D71E6540069F093 /* Conversation */, 523068972D71F0FC0069F093 /* Settings */, ); @@ -375,14 +371,6 @@ isa = XCSwiftPackageProductDependency; productName = Settings; }; - 52996F0B2D6F7787003AD246 /* HuggingfaceHub */ = { - isa = XCSwiftPackageProductDependency; - productName = HuggingfaceHub; - }; - 52996F0E2D6F77A2003AD246 /* HuggingfaceHub */ = { - isa = XCSwiftPackageProductDependency; - productName = HuggingfaceHub; - }; /* End XCSwiftPackageProductDependency section */ }; rootObject = 5266753A2C85EDCB001EF113 /* Project object */; diff --git a/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index ce61799..90d9823 100644 --- a/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "cddab7a5a48825d0398510e03669e6f2406bf3dc2c0b1594e3e084d897c0120b", + "originHash" : "29f3c5c70c2ac2ac0b01d925b1acaf68aba9d44303267147c908eeb2aebb5082", "pins" : [ { "identity" : "anycodable", @@ -10,6 +10,15 @@ "version" : "0.6.7" } }, + { + "identity" : "compactslider", + "kind" : "remoteSourceControl", + "location" : "https://github.com/buh/CompactSlider", + "state" : { + "revision" : "dcd792d9fb99d70a68782ab02ff23775cc6461a1", + "version" : "2.0.7" + } + }, { "identity" : "coretextswift", "kind" : "remoteSourceControl", @@ -28,6 +37,69 @@ "version" : "9.0.2" } }, + { + "identity" : "grdb.swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/groue/GRDB.swift.git", + "state" : { + "revision" : "6eba24d16952452a8a54f6a639491f3c8215527f", + "version" : "7.3.0" + } + }, + { + "identity" : "gzipswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/1024jp/GzipSwift", + "state" : { + "revision" : "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", + "version" : "6.0.1" + } + }, + { + "identity" : "jinja", + "kind" : "remoteSourceControl", + "location" : "https://github.com/johnmai-dev/Jinja", + "state" : { + "revision" : "bbddb92fc51ae420b87300298370fd1dfc308f73", + "version" : "1.1.1" + } + }, + { + "identity" : "mlx-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ml-explore/mlx-swift", + "state" : { + "revision" : "b990c58153af70eb0914bca7dd74401d341fa9ae", + "version" : "0.21.3" + } + }, + { + "identity" : "mlx-swift-examples", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ml-explore/mlx-swift-examples/", + "state" : { + "branch" : "main", + "revision" : "11cab4cbe2ba45aaf12dff9572807110580c0ff0" + } + }, + { + "identity" : "networkimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gonzalezreal/NetworkImage", + "state" : { + "revision" : "2849f5323265386e200484b0d0f896e73c3411b9", + "version" : "6.0.1" + } + }, + { + "identity" : "openai", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MacPaw/OpenAI.git", + "state" : { + "branch" : "0.3.6", + "revision" : "e59e4923acc924db51d415e99d0a76c7d54037e0" + } + }, { "identity" : "semaphore", "kind" : "remoteSourceControl", @@ -60,8 +132,44 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/apple/swift-argument-parser", "state" : { - "revision" : "41982a3656a71c768319979febd796c6fd111d5c", - "version" : "1.5.0" + "revision" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-cmark", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-cmark", + "state" : { + "revision" : "3ccff77b2dc5b96b77db3da0d68d28068593fa53", + "version" : "0.5.0" + } + }, + { + "identity" : "swift-collections", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-collections.git", + "state" : { + "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", + "version" : "1.1.4" + } + }, + { + "identity" : "swift-markdown-ui", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gonzalezreal/swift-markdown-ui", + "state" : { + "revision" : "5f613358148239d0292c0cef674a3c2314737f9e", + "version" : "2.4.1" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" } }, { @@ -73,6 +181,15 @@ "version" : "600.0.1" } }, + { + "identity" : "swift-transformers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/huggingface/swift-transformers", + "state" : { + "revision" : "55710ddfb1ae804b4b7ce973be75cf2e41272185", + "version" : "0.1.17" + } + }, { "identity" : "swiftui-introspect", "kind" : "remoteSourceControl", diff --git a/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist b/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist index 9046445..8ccc679 100644 --- a/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,16 +4,41 @@ SchemeUserState + AnyCodable (Playground) 1.xcscheme + + orderHint + 10 + AnyCodable (Playground).xcscheme orderHint - 9 + 17 + + Associations (Playground).xcscheme + + orderHint + 13 ChatMLX.xcscheme_^#shared#^_ orderHint 0 + MyPlayground (Playground).xcscheme + + orderHint + 14 + + Tour (Playground).xcscheme + + orderHint + 15 + + TransactionObserver (Playground).xcscheme + + orderHint + 16 + SuppressBuildableAutocreation diff --git a/ChatMLX/ChatMLX.entitlements b/ChatMLX/ChatMLX.entitlements index 81f6dc3..c7bcdcd 100644 --- a/ChatMLX/ChatMLX.entitlements +++ b/ChatMLX/ChatMLX.entitlements @@ -2,11 +2,15 @@ + com.apple.security.temporary-exception.files.home-relative-path.read-write + + /.cache/huggingface/ + + com.apple.security.app-sandbox + + com.apple.security.files.user-selected.read-write + com.apple.security.network.client - com.apple.security.app-sandbox - - com.apple.security.files.user-selected.read-only - diff --git a/ChatMLX/ChatMLXApp.swift b/ChatMLX/ChatMLXApp.swift index ab1d486..b9cd0e4 100644 --- a/ChatMLX/ChatMLXApp.swift +++ b/ChatMLX/ChatMLXApp.swift @@ -6,28 +6,35 @@ // import Conversation +import Utilities +import Database import Defaults import Settings import SwiftUI @main struct ChatMLXApp: App { + @State private var appStore: AppStore = .init() @State private var conversationStore: ConversationStore = .init() + @State private var settingsStore: SettingsStore = .init() @Default(.language) var language var body: some Scene { - Group { - WindowGroup { - ConversationView() - .environment(conversationStore) - } - - Settings { - SettingsView() + WindowGroup { + ConversationView() + .environment(appStore) + .environment(conversationStore) + .environment(\.locale, .init(identifier: language.id)) + .environment(\.appDatabase, .shared) + } - } + Settings { + SettingsView() + .environment(appStore) + .environment(settingsStore) + .environment(\.locale, .init(identifier: language.id)) + .environment(\.appDatabase, .shared) } - .environment(\.locale, .init(identifier: language.rawValue)) } } diff --git a/Packages/Common/Sources/Common/AppStore.swift b/Packages/Common/Sources/Common/AppStore.swift deleted file mode 100644 index df4af86..0000000 --- a/Packages/Common/Sources/Common/AppStore.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// AppStore.swift -// Common -// -// Created by John Mai on 2025/2/27. -// - -import Foundation -import Models - -@MainActor -@Observable -final class AppStore { - var models: [Model] = [] -} diff --git a/Packages/Common/Sources/Common/Extensions/Binding+Extensions.swift b/Packages/Common/Sources/Common/Extensions/Binding+Extensions.swift deleted file mode 100644 index a303fc8..0000000 --- a/Packages/Common/Sources/Common/Extensions/Binding+Extensions.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// Binding+Extensions.swift -// Common -// -// Created by John Mai on 2025/3/1. -// - -import SwiftUI - -extension Binding { - public func toUnwrapped(defaultValue: T) -> Binding where Value == T? { - Binding(get: { self.wrappedValue ?? defaultValue }, set: { self.wrappedValue = $0 }) - } -} diff --git a/Packages/Common/Sources/Common/Services/HuggingfaceHubService.swift b/Packages/Common/Sources/Common/Services/HuggingfaceHubService.swift deleted file mode 100644 index cdcf77e..0000000 --- a/Packages/Common/Sources/Common/Services/HuggingfaceHubService.swift +++ /dev/null @@ -1,54 +0,0 @@ -// -// HuggingfaceHubService.swift -// Common -// -// Created by John Mai on 2025/2/19. -// -import HuggingfaceHub -import Models - -struct HuggingfaceHubService { - func scanMLXModels() throws -> [Model] { - let hfCacheInfo = try CacheManager().scanCacheDir() - - return hfCacheInfo.repos.filter { repo in - repo.repoId.hasPrefix("mlx-community/") || repo.repoId.contains("-MLX") - || isMLX(repo: repo) - }.map { repo in - - let file = try? HuggingfaceHub.Utility.tryToLoadFromCache( - repoId: repo.repoId, filename: "") - - return Model( - provider: .mlx, - name: repo.repoId, - model: .local(file ?? repo.repoPath) - ) - } - } - - private func isMLX(repo: CachedRepoInfo) -> Bool { - if repo.repoId.hasPrefix("mlx-community/") { - return true - } - - let file = try? HuggingfaceHub.Utility.tryToLoadFromCache( - repoId: repo.repoId, filename: "README.md") - - guard let file else { - return false - } - - let markdown = try? String(contentsOf: file) - - guard let markdown else { - return false - } - - let metadata = MarkdownMetadata(from: markdown) - - let tags = metadata.array(for: "tags") - - return tags.contains("mlx") - } -} diff --git a/Packages/Conversation/Package.swift b/Packages/Conversation/Package.swift index 96ab6b3..8be2020 100644 --- a/Packages/Conversation/Package.swift +++ b/Packages/Conversation/Package.swift @@ -13,9 +13,12 @@ let package = Package( targets: ["Conversation"]) ], dependencies: [ + .package(name: "Utilities", path: "../Utilities"), + .package(name: "Database", path: "../Database"), .package(name: "UltraUI", path: "../UltraUI"), - .package(name: "Models", path: "../Models"), + .package(name: "Intelligence", path: "../Intelligence"), .package(url: "https://github.com/markiv/SwiftUI-Shimmer", from: "1.5.1"), + .package(url: "https://github.com/gonzalezreal/swift-markdown-ui", from: "2.0.2"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -23,8 +26,11 @@ let package = Package( .target( name: "Conversation", dependencies: [ - "Models", + "Intelligence", "UltraUI", + "Utilities", + "Database", + .product(name: "MarkdownUI", package: "swift-markdown-ui"), .product(name: "Shimmer", package: "SwiftUI-Shimmer"), ] ), diff --git a/Packages/Conversation/Sources/Conversation/BubbleShape.swift b/Packages/Conversation/Sources/Conversation/BubbleShape.swift new file mode 100644 index 0000000..8b6d346 --- /dev/null +++ b/Packages/Conversation/Sources/Conversation/BubbleShape.swift @@ -0,0 +1,79 @@ +// +// BubbleShape.swift +// Conversation +// +// Created by John Mai on 2025/3/2. +// + +import SwiftUI + +struct BubbleShape: Shape { + let isUser: Bool + let cornerRadius: CGFloat + + func path(in rect: CGRect) -> Path { + var path = Path() + + if isUser { + path.move(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius)) + + path.addArc( + center: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + cornerRadius), + radius: cornerRadius, + startAngle: Angle(degrees: 180), + endAngle: Angle(degrees: 270), + clockwise: false) + + path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY)) + + path.addArc( + center: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY + cornerRadius), + radius: cornerRadius, + startAngle: Angle(degrees: 270), + endAngle: Angle(degrees: 0), + clockwise: false) + + path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) + path.addLine(to: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY)) + path.addArc( + center: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY - cornerRadius), + radius: cornerRadius, + startAngle: Angle(degrees: 90), + endAngle: Angle(degrees: 180), + clockwise: false) + + } else { + path.move(to: CGPoint(x: rect.minX, y: rect.maxY)) + path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius)) + path.addArc( + center: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + cornerRadius), + radius: cornerRadius, + startAngle: Angle(degrees: 180), + endAngle: Angle(degrees: 270), + clockwise: false) + + path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY)) + + path.addArc( + center: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY + cornerRadius), + radius: cornerRadius, + startAngle: Angle(degrees: 270), + endAngle: Angle(degrees: 0), + clockwise: false) + + path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius)) + + path.addArc( + center: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - cornerRadius), + radius: cornerRadius, + startAngle: Angle(degrees: 0), + endAngle: Angle(degrees: 90), + clockwise: false) + + path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) + } + + path.closeSubpath() + return path + } +} diff --git a/Packages/Conversation/Sources/Conversation/ChatView.swift b/Packages/Conversation/Sources/Conversation/ChatView.swift deleted file mode 100644 index 3e0beb8..0000000 --- a/Packages/Conversation/Sources/Conversation/ChatView.swift +++ /dev/null @@ -1,164 +0,0 @@ -// -// Message.swift -// Conversation -// -// Created by John Mai on 2025/2/23. -// - -import SwiftUI - -// 消息模型 -struct Message2: Identifiable { - let id = UUID() - let content: String - let isFromMe: Bool - let timestamp: Date -} - -struct BubbleShape: Shape { - let isFromMe: Bool - let cornerRadius: CGFloat - - func path(in rect: CGRect) -> Path { - var path = Path() - - if isFromMe { - path.move(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius)) - - path.addArc( - center: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + cornerRadius), - radius: cornerRadius, - startAngle: Angle(degrees: 180), - endAngle: Angle(degrees: 270), - clockwise: false) - - path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY)) - - path.addArc( - center: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY + cornerRadius), - radius: cornerRadius, - startAngle: Angle(degrees: 270), - endAngle: Angle(degrees: 0), - clockwise: false) - - path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY)) - path.addLine(to: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY)) - path.addArc( - center: CGPoint(x: rect.minX + cornerRadius, y: rect.maxY - cornerRadius), - radius: cornerRadius, - startAngle: Angle(degrees: 90), - endAngle: Angle(degrees: 180), - clockwise: false) - - } else { - path.move(to: CGPoint(x: rect.minX, y: rect.maxY)) - path.addLine(to: CGPoint(x: rect.minX, y: rect.minY + cornerRadius)) - path.addArc( - center: CGPoint(x: rect.minX + cornerRadius, y: rect.minY + cornerRadius), - radius: cornerRadius, - startAngle: Angle(degrees: 180), - endAngle: Angle(degrees: 270), - clockwise: false) - - path.addLine(to: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY)) - - path.addArc( - center: CGPoint(x: rect.maxX - cornerRadius, y: rect.minY + cornerRadius), - radius: cornerRadius, - startAngle: Angle(degrees: 270), - endAngle: Angle(degrees: 0), - clockwise: false) - - path.addLine(to: CGPoint(x: rect.maxX, y: rect.maxY - cornerRadius)) - - path.addArc( - center: CGPoint(x: rect.maxX - cornerRadius, y: rect.maxY - cornerRadius), - radius: cornerRadius, - startAngle: Angle(degrees: 0), - endAngle: Angle(degrees: 90), - clockwise: false) - - path.addLine(to: CGPoint(x: rect.minX, y: rect.maxY)) - } - - path.closeSubpath() - return path - } -} - -struct MessageBubble: View { - @Environment(\.utlraViewBackground) var utlraViewBackground - @Environment(\.utlraSecondaryViewBackground) var utlraSecondaryViewBackground - - let message: Message2 - - var body: some View { - HStack { - if message.isFromMe { - Spacer() - } - - Text(message.content) - .padding(.horizontal, 12) - .padding(.vertical, 8) - .background( - BubbleShape(isFromMe: message.isFromMe, cornerRadius: 10) - .fill( - message.isFromMe - ? utlraViewBackground : utlraSecondaryViewBackground) - ) - .foregroundColor(message.isFromMe ? .white.opacity(0.8) : .white) - - if !message.isFromMe { - Spacer() - } - } - .frame(maxWidth: 765) - .padding(.horizontal, 8) - .padding(.vertical, 4) - } -} - -struct ChatView: View { - @State private var messages: [Message2] = [ - Message2(content: "Hello!", isFromMe: false, timestamp: Date()), - Message2(content: "What have you been up to lately?", isFromMe: true, timestamp: Date()), - Message2( - content: "I'm learning SwiftUI and implementing a chat interface.", isFromMe: false, - timestamp: Date()), - Message2(content: "Looks good!", isFromMe: true, timestamp: Date()), - Message2( - content: "Which one do you think is better, SwiftUI or UIKit?", isFromMe: false, - timestamp: Date()), - Message2( - content: "I think SwiftUI is more concise and easier to use, but UIKit is more mature.", - isFromMe: true, timestamp: Date()), - Message2(content: "Indeed!", isFromMe: false, timestamp: Date()), - Message2( - content: "Are you using SwiftUI for any projects?", isFromMe: true, timestamp: Date()), - Message2( - content: "Not right now, but I'm considering making a personal blog.", isFromMe: false, - timestamp: Date()), - Message2(content: "Sounds good!", isFromMe: true, timestamp: Date()), - Message2( - content: "What framework are you planning to use?", isFromMe: false, timestamp: Date()), - Message2(content: "I'm considering using Vapor.", isFromMe: true, timestamp: Date()), - Message2(content: "Vapor is pretty good!", isFromMe: false, timestamp: Date()), - ] - - var body: some View { - ScrollView { - LazyVStack { - ForEach(messages) { message in - MessageBubble(message: message) - } - } - } - .padding(.horizontal) - } -} - -// 预览 -#Preview { - ChatView() -} diff --git a/Packages/Conversation/Sources/Conversation/ConversationDetailView.swift b/Packages/Conversation/Sources/Conversation/ConversationDetailView.swift new file mode 100644 index 0000000..a2ec6c7 --- /dev/null +++ b/Packages/Conversation/Sources/Conversation/ConversationDetailView.swift @@ -0,0 +1,118 @@ +// +// ConversationDetailView.swift +// Conversation +// +// Created by John Mai on 2025/2/23. +// + +import Combine +import Database +import GRDB +import SwiftUI +import SwiftUIIntrospect + +struct ConversationDetailView: View { + + @Environment(ConversationStore.self) private var conversationStore + + @State private var scrollToBottom: Bool = true + @State private var scrollViewProxy: ScrollViewProxy? = nil + @State private var scrollViewProxy2: NSScrollView? + + var body: some View { + ScrollViewReader { proxy in + List { + Color.clear.frame(width: 0, height: 0).id("top") + .listRowSeparator(.hidden) + ForEach(conversationStore.currentMessages) { message in + MessageBubble(message: message) + .listRowSeparator(.hidden) + } + Color.clear.frame(width: 0, height: 0).id("bottom") + .listRowSeparator(.hidden) + } + .frame(maxWidth: 800) + .listStyle(.plain) + .scrollContentBackground(.hidden) + .onAppear { + scrollViewProxy = proxy + if scrollToBottom { + withAnimation { + proxy.scrollTo("bottom", anchor: .bottom) + } + } + } + .onChange(of: conversationStore.currentMessages.last?.id) { _, _ in + if scrollToBottom { + withAnimation { + proxy.scrollTo("bottom", anchor: .bottom) + } + } + } +// .onChange(of: conversationStore.currentMessages.count) { newCount, previousCount in +// if let scrollView = scrollViewProxy2, previousCount > 0, newCount > previousCount { +// let oldContentHeight = scrollView.documentView?.frame.height ?? 0 +// +// DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { +// let newContentHeight = scrollView.documentView?.frame.height ?? 0 +// let diff = newContentHeight - oldContentHeight +// +// let newY = scrollView.contentView.bounds.origin.y + diff +// scrollView.contentView.scroll( +// to: CGPoint(x: scrollView.contentView.bounds.origin.x, y: newY)) +// } +// } +// } +// .introspect(.scrollView, on: .macOS(.v15, .v14)) { scrollView in +// let contentView = scrollView.contentView +// +// NotificationCenter.default.addObserver( +// forName: NSView.boundsDidChangeNotification, +// object: contentView, +// queue: .main +// ) { _ in +// MainActor.assumeIsolated { +// +// let visibleRect = contentView.bounds +// let contentRect = scrollView.documentView?.frame ?? .zero +// +// print("visibleRect: \(visibleRect)") +// print("contentRect: \(contentRect)") +// +// if visibleRect.minY < 10 && contentRect.height > visibleRect.height { +// guard conversationStore.hasReachedTop == false else { return } +// +// guard conversationStore.isLoadMessages == false else { return } +// let oldContentHeight = contentRect.height +// print("oldContentHeight: \(contentRect.height)") +// +// Task { [oldContentHeight] in +// try? await conversationStore.loadMessagesIfNeeded() +// +// DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { +// let newContentHeight = contentRect.height +// print("newContentHeight: \(contentRect.height)") +// print("newContentHeight: \(newContentHeight)") +// let diff = newContentHeight - oldContentHeight +// print("diff: \(diff)") +// +// let newY = scrollView.contentView.bounds.origin.y + diff +// scrollView.contentView.scroll( +// to: CGPoint( +// x: scrollView.contentView.bounds.origin.x, y: 280)) +// } +// +// } +// } +// +// let isNearBottom = (contentRect.height - visibleRect.maxY) < 50 +// if isNearBottom != scrollToBottom { +// +// scrollToBottom = isNearBottom +// } +// } +// } +// } + } + } +} diff --git a/Packages/Conversation/Sources/Conversation/ConversationSidebarView.swift b/Packages/Conversation/Sources/Conversation/ConversationSidebarView.swift index 2143e89..cae7784 100644 --- a/Packages/Conversation/Sources/Conversation/ConversationSidebarView.swift +++ b/Packages/Conversation/Sources/Conversation/ConversationSidebarView.swift @@ -5,15 +5,20 @@ // Created by John Mai on 2025/2/21. // -import Models +import Database import Shimmer import SwiftUI +import SwiftUIIntrospect import UltraUI struct ConversationSidebarView: View { - @Binding var conversations: [Conversation] + var conversations: [Conversation] @Binding var selectedConversation: Conversation? + @Environment(ConversationStore.self) var conversationStore + + @State var proxy: ScrollViewProxy? + var body: some View { VStack(spacing: 16) { HStack { @@ -29,31 +34,60 @@ struct ConversationSidebarView: View { .repeatForever(autoreverses: false)) } .shadow() - - List { - ForEach(conversations) { conversation in - item(conversation: conversation) + ScrollViewReader { proxy in + ScrollView { + Color.clear + .frame(width: 0, height: 0) + .id("top") + LazyVStack(spacing: 4) { + ForEach(conversations, id: \.id) { conversation in + item(conversation: conversation) + } + } + .padding(.horizontal, 6) } + .onChange(of: conversations) { oldValue, newValue in + if oldValue.count < newValue.count { + withAnimation { + proxy.scrollTo("top", anchor: .top) + } + } + } + .scrollContentBackground(.hidden) + } + } + .task { + do { + try await self.conversationStore.loadConversations() + } catch { + print("Failed to load conversations: \(error)") } - .listStyle(.plain) - .scrollContentBackground(.hidden) + } } @ViewBuilder func item(conversation: Conversation) -> some View { Button(action: { - selectedConversation = conversation + Task{ + do { + try await conversationStore.switchToConversation(conversation) + } catch { + print("Failed to switch to conversation: \(error)") + } + } + }) { VStack(alignment: .leading, spacing: 8) { HStack { - Text(conversation.title) + Text(conversation.titleUnwrapped) .font(.headline) .lineLimit(1) + .help(conversation.titleUnwrapped) Spacer() - Text(conversation.updatedTime.formatted()) + Text(conversation.updatedAt.shortFormatted()) .font(.caption) .foregroundStyle(.secondary) @@ -70,7 +104,7 @@ struct ConversationSidebarView: View { .padding(16) } .buttonStyle( - UltraSidebarButtonStyle(conversation == selectedConversation) + UltraSidebarButtonStyle(conversation.id == selectedConversation?.id) ) } diff --git a/Packages/Conversation/Sources/Conversation/ConversationStore.swift b/Packages/Conversation/Sources/Conversation/ConversationStore.swift index af2b683..1f5d942 100644 --- a/Packages/Conversation/Sources/Conversation/ConversationStore.swift +++ b/Packages/Conversation/Sources/Conversation/ConversationStore.swift @@ -5,21 +5,234 @@ // Created by John Mai on 2025/2/22. // +import Database import Foundation -import Models +import GRDB +import Intelligence +import SwiftUI @MainActor @Observable public final class ConversationStore { - public var conversations: [Conversation] - public var selectedConversation: Conversation? + public var conversations: [Conversation] = [] - public init( - conversations: [Conversation] = [], - selectedConversation: Conversation? = nil - ) { - self.conversations = conversations - self.selectedConversation = selectedConversation + public var currentMessages: [Message] = [] + + public var selectedConversation: Conversation? = nil + + public var prompt: AttributedString = "" + + public var model: Model? + + public var isLoadMessages: Bool = false + public var hasReachedTop: Bool = false + + private var database: AppDatabase = .shared + + public init() {} + + func loadConversations() async throws { + let conversations = try await database.reader.read { db in + try Conversation + .order(Column("updatedAt")) + .fetchAll(db) + } + withAnimation { + self.conversations = conversations + } + } + + private func fetchMessages() async throws -> [Message] { + let messages = try await database.reader.read { + [selectedConversation] db in + try Message + .filter(Column("conversationId") == selectedConversation?.id.uuidString) + .order(Column("createdAt")) +// .order(Column("createdAt").desc) +// .limit(10) + .fetchAll(db) + } + + return Array(messages.reversed()) + } + +// func loadMessagesIfNeeded() async throws { +// guard !hasReachedTop, !isLoadMessages else { +// return +// } +// +// guard let selectedConversation = selectedConversation else { +// return +// } +// +// isLoadMessages = true +// +// defer { +// isLoadMessages = false +// } +// +// if currentMessages.isEmpty { +// let messages = try await fetchMessages() +// withAnimation { +// self.currentMessages = messages +// } +// return +// } +// +// let pageSize = 10 +// +// let earliestDate = currentMessages.first?.createdAt +// +// let olderMessages = try await database.reader.read { [selectedConversation] db in +// try Message +// .filter(Column("conversationId") == selectedConversation.id.uuidString) +// .filter(earliestDate == nil || Column("createdAt") < earliestDate!) +// .order(Column("createdAt").desc) +// .limit(pageSize) +// .fetchAll(db) +// .sorted(by: { $0.createdAt < $1.createdAt }) // 确保按时间正序排列 +// } +// +// if olderMessages.isEmpty { +// hasReachedTop = true +// return +// } +// +// withAnimation { +// self.currentMessages.insert(contentsOf: olderMessages, at: 0) +// } +// } + + func createConversation() async throws { + let conversation = Conversation() + withAnimation { + self.selectedConversation = conversation + self.currentMessages = [] + self.conversations.insert(conversation, at: 0) + hasReachedTop = false // 重置标志 + } + + Task { + try await database.insert(conversation) + } + } + + func switchToConversation(_ conversation: Conversation) async throws { + withAnimation { + currentMessages = [] + selectedConversation = selectedConversation != conversation ? conversation : nil + hasReachedTop = false + } + + if selectedConversation != nil { + let messages = try await fetchMessages() + + withAnimation { + self.currentMessages = messages + } + } + } + + func send() async throws { + guard !(selectedConversation?.isInferring ?? false), let model, !prompt.characters.isEmpty + else { + return + } + + let prompt: String = String(self.prompt.characters) + + self.prompt = "" + + var conversation = selectedConversation ?? Conversation() + conversation.model = self.model + conversation.isInferring = true + + withAnimation { + selectedConversation = conversation + } + + Task { + try await self.database.update(conversation) + } + + defer { + conversation.isInferring = false + selectedConversation = conversation + Task { + try await self.database.update(conversation) + } + } + + let userMessage = Message( + role: .user, + content: prompt, + model: conversation.model!, + conversationId: conversation.id + ) + currentMessages.append(userMessage) + + Task { + try await self.database.insert(userMessage) + } + + let provider = ProviderFactory.createProvider(provider: model.provider) + + var messages: [ChatCompletionMessage] = [] + + for message in self.currentMessages { + messages.append( + ChatCompletionMessage( + role: message.role, + content: message.content + ) + ) + } + + let stream = await provider.streamingChatCompletion( + model: model, + messages: messages, + options: .none + ) + + var assistantMessage = Message( + role: .assistant, + content: "", + model: conversation.model!, + conversationId: conversation.id + ) + currentMessages.append(assistantMessage) + + Task { + try await self.database.insert(assistantMessage) + } + + var lastUpdate = Date() + + for try await chunk in stream { + guard let content = chunk.content else { + continue + } + + if lastUpdate.timeIntervalSinceNow < -0.2 || chunk.finishReason != nil { + assistantMessage.content = content + + if let index = currentMessages.firstIndex(where: { $0.id == assistantMessage.id }) { + currentMessages[index] = assistantMessage + } + + if selectedConversation?.id != assistantMessage.conversationId { + Task { + try await self.database.update(assistantMessage) + } + } + + lastUpdate = Date() + } + } + + Task { + try await self.database.update(assistantMessage) + } } } diff --git a/Packages/Conversation/Sources/Conversation/ConversationView.swift b/Packages/Conversation/Sources/Conversation/ConversationView.swift index 5df2026..1387b16 100644 --- a/Packages/Conversation/Sources/Conversation/ConversationView.swift +++ b/Packages/Conversation/Sources/Conversation/ConversationView.swift @@ -1,12 +1,14 @@ -import Models // // ConversationView.swift // Conversation // // Created by John Mai on 2025/2/21. // + +import Database import SwiftUI import UltraUI +import Utilities public struct ConversationView: View { @State private var prompt = AttributedString("") @@ -18,54 +20,50 @@ public struct ConversationView: View { public var body: some View { @Bindable var conversationStore = conversationStore - UltraNavigationSplitView { + UltraNavigationSplitView(showDivider: conversationStore.selectedConversation != nil) { ConversationSidebarView( - conversations: $conversationStore.conversations, + conversations: conversationStore.conversations, selectedConversation: $conversationStore.selectedConversation ) } detail: { VStack(spacing: .zero) { - if let conversations = conversationStore.selectedConversation { - ChatView() + + if let conversation = conversationStore.selectedConversation { + ConversationDetailView() .frame(maxHeight: .infinity) - .ultraNavigationTitle(conversations.title) + .ultraNavigationTitle(conversation.titleUnwrapped) } else { GreetingView() + .ultraNavigationTitle("") } - PromptEditorView(prompt: $prompt) { - - } trailingToolbar: { + PromptEditorView().frame(maxWidth: 765) + } + .ultraToolbar { + UltraToolbarItem(placement: .leading) { + Button { + Task { + do { + try await conversationStore.createConversation() + } catch { + print("Failed to create conversation: \(error)") + } + } + } label: { + Image(systemName: "plus") + } + .buttonStyle(.ultraIcon) + + SettingsLink { + Image(systemName: "gear") + } + .buttonStyle(.ultraIcon) } - .frame(maxWidth: 765) + } } .frame(minWidth: 580, minHeight: 360) - .task { - conversationStore.conversations = [ - .init( - title: "Exploring SwiftUI and Vapor in Chat App Development", - description: - "This conversation delves into the nuances of using SwiftUI for building a chat interface, comparing it with UIKit, and considering Vapor for backend development. The dialogue showcases the developer's journey, from learning SwiftUI to planning a personal blog project, while highlighting the strengths of these frameworks.", - model: .init( - provider: .openAI, - name: "gpt-4o", - model: .id("gpt-4o") - ) - ), - .init( - title: "Summary of the legal advisory dialogue", - description: - "This conversation is about legal counseling and covers questions, answers and related advice on legal issues. As specific conversation content was not provided, the above titles and descriptions are generic templates that are applicable to most legal counseling scenarios. For a more tailored title and description, please provide the specific conversation content to customize a version that more accurately reflects the topic and focus of the conversation.", - model: .init( - provider: .openAI, - name: "gpt-4o", - model: .id("gpt-4o") - ) - ), - ] - } .ultraWindowStyle() } } diff --git a/Packages/Conversation/Sources/Conversation/MessageBubble.swift b/Packages/Conversation/Sources/Conversation/MessageBubble.swift new file mode 100644 index 0000000..82350aa --- /dev/null +++ b/Packages/Conversation/Sources/Conversation/MessageBubble.swift @@ -0,0 +1,70 @@ +// +// MessageBubble.swift +// Conversation +// +// Created by John Mai on 2025/3/2. +// + +import Utilities +import Database +import MarkdownUI +import SwiftUI + +struct MessageBubble: View { + @Environment(\.ultraViewBackground) var utlraViewBackground + @Environment(\.utlraSecondaryViewBackground) var utlraSecondaryViewBackground + + @State private var isHovering = false + + let message: Message + + var body: some View { + + HStack { + if message.role == .user { + Spacer() + } + + VStack(alignment: message.role == .user ? .trailing : .leading, spacing: 2) { + Group { + if message.role == .user { + Text(message.content) + } else { + Markdown(message.content) + } + } + .padding(.horizontal, 12) + .padding(.vertical, 8) + .background( + BubbleShape(isUser: message.role == .user, cornerRadius: 10) + .fill( + message.role == .user + ? utlraViewBackground : utlraSecondaryViewBackground) + ) + .foregroundColor(message.role == .user ? .white.opacity(0.8) : .white) + + Text(message.createdAt.shortFormatted()) + .font(.caption2) + .foregroundColor(.gray) + .padding(.horizontal, 4) + .opacity(isHovering ? 1 : 0) + .animation(.easeIn(duration: 0.15), value: isHovering) + .frame(height: 16) + } + .onHover { hovering in + isHovering = hovering + } + + if message.role != .user { + Spacer() + } + } + + + + .padding(.horizontal, 8) + .padding(.vertical, 4) + .shadow() + + } +} diff --git a/Packages/Conversation/Sources/Conversation/PromptEditorView.swift b/Packages/Conversation/Sources/Conversation/PromptEditorView.swift index 414988d..f9cacf3 100644 --- a/Packages/Conversation/Sources/Conversation/PromptEditorView.swift +++ b/Packages/Conversation/Sources/Conversation/PromptEditorView.swift @@ -5,26 +5,23 @@ // Created by John Mai on 2025/2/23. // -import Models +import Utilities +import Database +import Intelligence import STTextView import SwiftUI import UltraUI -struct PromptEditorView: View { - @Environment(\.utlraViewBackground) var utlraViewBackground - - @Binding var prompt: AttributedString - - @ViewBuilder var leadingToolbar: () -> LeadingToolbar - @ViewBuilder var trailingToolbar: () -> TrailingToolbar +struct PromptEditorView: View { + @Environment(\.ultraViewBackground) var utlraViewBackground + @Environment(AppStore.self) var store + @Environment(ConversationStore.self) var conversationStore @State private var height: CGFloat = 36 @State private var selection: NSRange? = nil @State private var models: [Model] = [] - @State var model: Model? = nil - @Environment(\.utlraRadius) var ultraRadius let font: NSFont = .preferredFont(forTextStyle: .title3) @@ -32,9 +29,11 @@ struct PromptEditorView: View { let maxHeight: CGFloat = 200 var body: some View { + @Bindable var conversationStore = conversationStore + VStack(spacing: .zero) { TextareaView( - text: $prompt, + text: $conversationStore.prompt, placeholder: "What do you want to know?", plugins: [ TextViewPlugin( @@ -55,26 +54,32 @@ struct PromptEditorView: View { Image(systemName: "paperclip") }.buttonStyle(.ultraIcon) - // 网络搜索 Button { } label: { Image(systemName: "network") }.buttonStyle(.ultraIcon) - leadingToolbar() Spacer() - trailingToolbar() + UltraPicker( - options: models, - selection: $model + options: store.activeModels, + selection: $conversationStore.model ) Button("Send", systemImage: "paperplane.fill") { - print("Send") - }.buttonStyle(.ultra) - } + Task { + do { + try await conversationStore.send() + } catch { + print(error) + } + } + } + .disabled(conversationStore.prompt.characters.isEmpty || conversationStore.model == nil) + .buttonStyle(.ultra) + } } .padding() .background(utlraViewBackground) @@ -82,11 +87,11 @@ struct PromptEditorView: View { .shadow() .padding() .task { - // do { - // models = try HuggingfaceHubService().scanMLXModels() - // } catch { - // print(error) - // } + do { + try store.loadModels() + } catch { + print(error) + } } } diff --git a/Packages/Common/.gitignore b/Packages/Database/.gitignore similarity index 100% rename from Packages/Common/.gitignore rename to Packages/Database/.gitignore diff --git a/Packages/Models/Package.swift b/Packages/Database/Package.swift similarity index 62% rename from Packages/Models/Package.swift rename to Packages/Database/Package.swift index 9a505e1..ba29c6a 100644 --- a/Packages/Models/Package.swift +++ b/Packages/Database/Package.swift @@ -4,29 +4,31 @@ import PackageDescription let package = Package( - name: "Models", + name: "Database", platforms: [.macOS(.v14)], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library( - name: "Models", - targets: ["Models"]) + name: "Database", + targets: ["Database"]), ], dependencies: [ - .package(url: "https://github.com/sindresorhus/Defaults", from: "9.0.2") + .package(name: "Intelligence", path: "../Intelligence"), + .package(url: "https://github.com/groue/GRDB.swift.git", from: "7.3.0"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( - name: "Models", + name: "Database", dependencies: [ - "Defaults" + "Intelligence", + .product(name: "GRDB", package: "GRDB.swift") ] ), .testTarget( - name: "ModelsTests", - dependencies: ["Models"] + name: "DatabaseTests", + dependencies: ["Database"] ), ] ) diff --git a/Packages/Database/Sources/Database/AppDatabase.swift b/Packages/Database/Sources/Database/AppDatabase.swift new file mode 100644 index 0000000..4735328 --- /dev/null +++ b/Packages/Database/Sources/Database/AppDatabase.swift @@ -0,0 +1,67 @@ +// +// AppDatabase.swift +// Database +// +// Created by John Mai on 2025/3/9. +// + +import Foundation +import GRDB +import SwiftUI + +public final class AppDatabase: Sendable { + + public static let shared = makeShared() + + let dbWriter: any DatabaseWriter + + public var reader: any DatabaseReader { + dbWriter + } + + init(_ dbWriter: any GRDB.DatabaseWriter) throws { + self.dbWriter = dbWriter + } + + public static func makeShared(inMemory: Bool = false) -> Self { + do { + let database = try createDatabase( + configuration: createConfiguration(), + inMemory: inMemory + ) + + try configureMigrations().migrate(database) + + return try Self(database) + } catch { + fatalError("Failed to create database: \(error)") + } + } + + private static func createDatabase( + configuration: Configuration, + inMemory: Bool = false + ) throws -> any DatabaseWriter { + if ProcessInfo.processInfo.environment["XCODE_RUNNING_FOR_PREVIEWS"] == nil && !inMemory { + let path = URL.documentsDirectory.appending(component: "db.sqlite").path() + + #if DEBUG + print("Database Path: \(path)") + #endif + + return try DatabasePool(path: path, configuration: configuration) + } else { + return try DatabaseQueue(configuration: configuration) + } + } +} + +extension EnvironmentValues { + @Entry public var appDatabase: AppDatabase = .makeShared(inMemory: true) +} + +extension View { + public func appDatabase(_ appDatabase: AppDatabase) -> some View { + self.environment(\.appDatabase, appDatabase) + } +} diff --git a/Packages/Database/Sources/Database/DatabaseConfiguration.swift b/Packages/Database/Sources/Database/DatabaseConfiguration.swift new file mode 100644 index 0000000..d6bdae7 --- /dev/null +++ b/Packages/Database/Sources/Database/DatabaseConfiguration.swift @@ -0,0 +1,24 @@ +// +// DatabaseConfiguration.swift +// Database +// +// Created by John Mai on 2025/3/9. +// + +import Foundation +import GRDB + +extension AppDatabase { + static func createConfiguration() -> Configuration { + var configuration = Configuration() + configuration.foreignKeysEnabled = true + +// #if DEBUG +// configuration.prepareDatabase { db in +// db.trace(options: .profile) { print($0.expandedDescription) } +// } +// #endif + + return configuration + } +} diff --git a/Packages/Database/Sources/Database/DatabaseMigrations.swift b/Packages/Database/Sources/Database/DatabaseMigrations.swift new file mode 100644 index 0000000..cf8e8ba --- /dev/null +++ b/Packages/Database/Sources/Database/DatabaseMigrations.swift @@ -0,0 +1,68 @@ +// +// DatabaseMigrations.swift +// Database +// +// Created by John Mai on 2025/3/9. +// + +import Foundation +import GRDB + +extension AppDatabase { + static func configureMigrations() -> DatabaseMigrator { + var migrator = DatabaseMigrator() + + #if DEBUG + migrator.eraseDatabaseOnSchemaChange = true + #endif + + migrator.registerMigration("v1") { db in + try db.create(table: "conversation") { table in + table.column("id", .text).primaryKey() + table.column("title", .text) + table.column("description", .text) + table.column("model", .jsonb) + table.column("isInferring", .boolean).notNull().defaults(to: false) + table.column("createdAt", .datetime) + table.column("updatedAt", .datetime) + } + + try db.create(table: "message") { table in + table.column("id", .text).primaryKey() + table.column("role", .text).notNull() + table.column("content", .text).notNull() + table.column("reasoning", .text) + table.column("model", .jsonb) + table.belongsTo("conversation", onDelete: .cascade).notNull() + table.column("createdAt", .datetime).notNull() + table.column("updatedAt", .datetime).notNull() + } + + try db.create(table: "asset") { table in + table.column("id", .text).primaryKey() + table.column("type", .text).notNull() + table.column("name", .text).notNull() + table.column("size", .integer).notNull() + table.column("url", .text).notNull() + table.column("hash", .text).notNull() + table.column("metadata", .jsonb) + table.column("createdAt", .datetime).notNull() + table.column("updatedAt", .datetime).notNull() + } + + try db.create(table: "messageAsset") { table in + table.belongsTo("message", onDelete: .cascade).notNull() + table.belongsTo("asset", onDelete: .setNull) + } + + } + + #if DEBUG + migrator.registerMigration("Add mock data") { db in + try db.createMockData() + } + #endif + + return migrator + } +} diff --git a/Packages/Database/Sources/Database/DatabaseMockData.swift b/Packages/Database/Sources/Database/DatabaseMockData.swift new file mode 100644 index 0000000..5eb9bb1 --- /dev/null +++ b/Packages/Database/Sources/Database/DatabaseMockData.swift @@ -0,0 +1,56 @@ +// +// DatabaseMigrations.swift +// Database +// +// Created by John Mai on 2025/3/9. +// + +import Foundation +import GRDB +import Intelligence + +extension Database { + func createMockData() throws { + let model = Model( + provider: .mlx, + name: "mlx-community/Qwen2.5-VL-7B-Instruct-8bit", + model: .id("Qwen2.5-VL-7B-Instruct-8bit") + ) + + let conversation = Conversation(title: "What is your name", model: model) + try conversation.insert(self) + + try Message( + role: .user, + content: "What is your name?", + model: model, + conversationId: conversation.id + ).insert(self) + + try Message( + role: .assistant, + content: + "Are you asking about my name? You can call me ChatGPT! 😊Or are you asking for name suggestions for something specific?", + model: model, + conversationId: conversation.id + ).insert(self) + + let conversation2 = Conversation(title: "What can you help with?") + try conversation2.insert(self) + + try Message( + role: .user, + content: "What can you help with?", + model: model, + conversationId: conversation2.id + ).insert(self) + + try Message( + role: .assistant, + content: + "I can help with a variety of topics! I can provide information, answer questions, or just chat with you. What would you like to do?", + model: model, + conversationId: conversation2.id + ).insert(self) + } +} diff --git a/Packages/Database/Sources/Database/Models/Asset.swift b/Packages/Database/Sources/Database/Models/Asset.swift new file mode 100644 index 0000000..14cf544 --- /dev/null +++ b/Packages/Database/Sources/Database/Models/Asset.swift @@ -0,0 +1,24 @@ +// +// Asset.swift +// Database +// +// Created by John Mai on 2025/3/8. +// + +import Foundation +import GRDB + +struct Asset: TableRecord { + var id: UUID + var type: String + var name: String + var size: Int + var url: URL + var hash: String + var metadata: [String: String] + var createdAt: Date + var updatedAt: Date + + static let messageAssets = hasMany(MessageAsset.self) + static let messages = hasMany(Message.self, through: messageAssets, using: MessageAsset.message) +} diff --git a/Packages/Database/Sources/Database/Models/Conversation.swift b/Packages/Database/Sources/Database/Models/Conversation.swift new file mode 100644 index 0000000..b4f43c8 --- /dev/null +++ b/Packages/Database/Sources/Database/Models/Conversation.swift @@ -0,0 +1,60 @@ +// +// Conversation.swift +// Database +// +// Created by John Mai on 2025/3/8. +// + +import Foundation +import GRDB +import Intelligence + +public struct Conversation: Codable, Equatable, Hashable, FetchableRecord, Sendable, + PersistableRecord +{ + public static func databaseUUIDEncodingStrategy( + for column: String + ) -> DatabaseUUIDEncodingStrategy { + .uppercaseString + } + + public var id: UUID + public var title: String? + public var description: String? + public var model: Model? + public var isInferring: Bool + public var createdAt: Date + public var updatedAt: Date + + public var titleUnwrapped: String { + title ?? String(localized: "Untitled") + } + + enum CodingKeys: String, CodingKey { + case id, title, description, model, isInferring, createdAt, updatedAt + } + + public init( + id: UUID = UUID(), + title: String? = nil, + model: Model? = nil, + isInferring: Bool = false, + createdAt: Date = .init(), + updatedAt: Date = .init() + ) { + self.id = id + self.title = title + self.model = model + self.isInferring = isInferring + self.createdAt = createdAt + self.updatedAt = updatedAt + } + + public static func == (lhs: Self, rhs: Self) -> Bool { + lhs.id == rhs.id + } +} + +extension Conversation: TableRecord { + static let messages = hasMany(Message.self) +} diff --git a/Packages/Database/Sources/Database/Models/Message.swift b/Packages/Database/Sources/Database/Models/Message.swift new file mode 100644 index 0000000..29e4687 --- /dev/null +++ b/Packages/Database/Sources/Database/Models/Message.swift @@ -0,0 +1,54 @@ +// +// Message.swift +// Database +// +// Created by John Mai on 2025/3/8. +// + +import Foundation +import GRDB +import Intelligence + +public struct Message: Codable, FetchableRecord, Identifiable, Hashable, Equatable, Sendable, + PersistableRecord +{ + public static func databaseUUIDEncodingStrategy( + for column: String + ) -> DatabaseUUIDEncodingStrategy { + .uppercaseString + } + + public var id: UUID + public var role: Role + public var content: String + public var reasoning: String? + public var model: Model + public var conversationId: UUID + public var createdAt: Date + public var updatedAt: Date + + public init( + id: UUID = UUID(), + role: Role, + content: String, + model: Model, + conversationId: UUID, + reasoning: String? = nil, + createdAt: Date = .init(), + updatedAt: Date = .init() + ) { + self.id = id + self.role = role + self.content = content + self.model = model + self.conversationId = conversationId + self.reasoning = reasoning + self.createdAt = createdAt + self.updatedAt = updatedAt + } +} + +extension Message: TableRecord { + static let messageAssets = hasMany(MessageAsset.self) + static let assets = hasMany(Asset.self, through: messageAssets, using: MessageAsset.asset) +} diff --git a/Packages/Database/Sources/Database/Models/MessageAsset.swift b/Packages/Database/Sources/Database/Models/MessageAsset.swift new file mode 100644 index 0000000..9d5e90d --- /dev/null +++ b/Packages/Database/Sources/Database/Models/MessageAsset.swift @@ -0,0 +1,14 @@ +// +// MessageAsset.swift +// Database +// +// Created by John Mai on 2025/3/8. +// + +import Foundation +import GRDB + +struct MessageAsset:TableRecord { + static let message = belongsTo(Message.self) + static let asset = belongsTo(Asset.self) +} diff --git a/Packages/Database/Sources/Database/Repository.swift b/Packages/Database/Sources/Database/Repository.swift new file mode 100644 index 0000000..ac89f2b --- /dev/null +++ b/Packages/Database/Sources/Database/Repository.swift @@ -0,0 +1,22 @@ +// +// Repository.swift +// Database +// +// Created by John Mai on 2025/3/11. +// + +import GRDB + +extension AppDatabase { + public func insert(_ record: T) async throws { + try await dbWriter.write { db in + try record.insert(db) + } + } + + public func update(_ record: T) async throws { + try await dbWriter.write { db in + try record.update(db) + } + } +} diff --git a/Packages/Database/Tests/DatabaseTests/DatabaseTests.swift b/Packages/Database/Tests/DatabaseTests/DatabaseTests.swift new file mode 100644 index 0000000..5f3002b --- /dev/null +++ b/Packages/Database/Tests/DatabaseTests/DatabaseTests.swift @@ -0,0 +1,21 @@ +import Testing +@testable import Database +import GRDB + +@Test func example() async throws { +// let dbQueue = DatabaseQueue.appDatabase(inMemory: true) +// +// try await dbQueue.write { db in +// var conversation = Conversation(title: "test") +// try conversation.insert(db) +// +// var message = Message(role: "user", content: "66", conversationId: conversation.id!) +// try message.insert(db) +// } +// +// try await dbQueue.read { db in +// let conversations = try Conversation.including(all: Conversation.messages).asRequest(of: ConversationWithMessage.self).fetchAll(db) +// +// print(conversations) +// } +} diff --git a/Packages/Models/.gitignore b/Packages/Intelligence/.gitignore similarity index 100% rename from Packages/Models/.gitignore rename to Packages/Intelligence/.gitignore diff --git a/Packages/Intelligence/Package.swift b/Packages/Intelligence/Package.swift new file mode 100644 index 0000000..9429411 --- /dev/null +++ b/Packages/Intelligence/Package.swift @@ -0,0 +1,38 @@ +// swift-tools-version: 6.0 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "Intelligence", + platforms: [.macOS(.v14)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "Intelligence", + targets: ["Intelligence"]), + ], + dependencies: [ + .package(name: "HuggingfaceHub", path: "../../../../maiqingqiang/HuggingfaceHub"), + .package(url: "https://github.com/ml-explore/mlx-swift-examples/", branch: "main"), + .package(url: "https://github.com/MacPaw/OpenAI.git", branch: "0.3.6") + ], + targets: [ + // Targets are the basic building blocks of a package, defining a module or a test suite. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "Intelligence", + dependencies: [ + "HuggingfaceHub", + "OpenAI", + .product(name: "MLXLMCommon", package: "mlx-swift-examples"), + .product(name: "MLXLLM", package: "mlx-swift-examples"), + .product(name: "MLXVLM", package: "mlx-swift-examples"), + ] + ), + .testTarget( + name: "IntelligenceTests", + dependencies: ["Intelligence"] + ), + ] +) diff --git a/Packages/Intelligence/Sources/Intelligence/Entities/ChatCompletionOptions.swift b/Packages/Intelligence/Sources/Intelligence/Entities/ChatCompletionOptions.swift new file mode 100644 index 0000000..1abface --- /dev/null +++ b/Packages/Intelligence/Sources/Intelligence/Entities/ChatCompletionOptions.swift @@ -0,0 +1,26 @@ +// +// ChatCompletionOptions.swift +// Intelligence +// +// Created by John Mai on 2025/3/9. +// + + +public struct ChatCompletionOptions: Sendable { + let temperature: Float? + let maxTokens: Int? + let topP: Float? + let stop: [String]? + + init( + temperature: Float? = nil, + maxTokens: Int? = nil, + topP: Float? = nil, + stop: [String]? = nil + ) { + self.temperature = temperature + self.maxTokens = maxTokens + self.topP = topP + self.stop = stop + } +} diff --git a/Packages/Intelligence/Sources/Intelligence/Entities/ChatCompletionStreamChunk.swift b/Packages/Intelligence/Sources/Intelligence/Entities/ChatCompletionStreamChunk.swift new file mode 100644 index 0000000..4cc5031 --- /dev/null +++ b/Packages/Intelligence/Sources/Intelligence/Entities/ChatCompletionStreamChunk.swift @@ -0,0 +1,12 @@ +// +// ChatCompletionStreamChunk.swift +// Intelligence +// +// Created by John Mai on 2025/3/9. +// + + +public struct ChatCompletionStreamChunk: Sendable { + public let content: String? + public let finishReason: String? +} diff --git a/Packages/Intelligence/Sources/Intelligence/Entities/Message.swift b/Packages/Intelligence/Sources/Intelligence/Entities/Message.swift new file mode 100644 index 0000000..9e506b7 --- /dev/null +++ b/Packages/Intelligence/Sources/Intelligence/Entities/Message.swift @@ -0,0 +1,18 @@ +// +// ChatCompletionMessage.swift +// Intelligence +// +// Created by John Mai on 2025/3/9. +// + +public struct ChatCompletionMessage: Sendable { + public var role: Role + public var content: String + public var reasoning: String? + + public init(role: Role, content: String, reasoning: String? = nil) { + self.role = role + self.content = content + self.reasoning = reasoning + } +} diff --git a/Packages/Intelligence/Sources/Intelligence/Entities/Model.swift b/Packages/Intelligence/Sources/Intelligence/Entities/Model.swift new file mode 100644 index 0000000..77652b6 --- /dev/null +++ b/Packages/Intelligence/Sources/Intelligence/Entities/Model.swift @@ -0,0 +1,32 @@ +// +// Model.swift +// Intelligence +// +// Created by John Mai on 2025/3/8. +// + +import Foundation + +public struct Model: Hashable, Codable, Sendable { + public let provider: Provider + public let name: String + public let model: ModelType + + public init(provider: Provider, name: String, model: ModelType) { + self.provider = provider + self.name = name + self.model = model + } +} + +extension Model: CustomStringConvertible { + public var description: String { + return name + } +} + +extension Model: Identifiable { + public var id: String { + return "\(provider)-\(name)" + } +} diff --git a/Packages/Intelligence/Sources/Intelligence/Entities/ModelType.swift b/Packages/Intelligence/Sources/Intelligence/Entities/ModelType.swift new file mode 100644 index 0000000..21afb5c --- /dev/null +++ b/Packages/Intelligence/Sources/Intelligence/Entities/ModelType.swift @@ -0,0 +1,22 @@ +// +// ModelType.swift +// Intelligence +// +// Created by John Mai on 2025/3/8. +// + +import Foundation + +public enum ModelType: Equatable, Hashable, CustomStringConvertible, Codable, Sendable { + case local(URL) + case id(String) + + public var description: String { + switch self { + case .local(let url): + return url.lastPathComponent + case .id(let id): + return id + } + } +} diff --git a/Packages/Intelligence/Sources/Intelligence/Entities/Provider.swift b/Packages/Intelligence/Sources/Intelligence/Entities/Provider.swift new file mode 100644 index 0000000..5f24914 --- /dev/null +++ b/Packages/Intelligence/Sources/Intelligence/Entities/Provider.swift @@ -0,0 +1,19 @@ +// +// Provider.swift +// Intelligence +// +// Created by John Mai on 2025/3/8. +// + +import Foundation + +public enum Provider: String, Codable, Sendable { + case mlx = "MLX" + case openAI = "OpenAI" +} + +extension Provider: CustomStringConvertible { + public var description: String { + return rawValue + } +} diff --git a/Packages/Intelligence/Sources/Intelligence/Entities/Role.swift b/Packages/Intelligence/Sources/Intelligence/Entities/Role.swift new file mode 100644 index 0000000..7cdef76 --- /dev/null +++ b/Packages/Intelligence/Sources/Intelligence/Entities/Role.swift @@ -0,0 +1,13 @@ +// +// Role.swift +// Intelligence +// +// Created by John Mai on 2025/3/9. +// + +public enum Role: String, Codable, Sendable { + case system + case assistant + case tool + case user +} diff --git a/Packages/Intelligence/Sources/Intelligence/Intelligence.swift b/Packages/Intelligence/Sources/Intelligence/Intelligence.swift new file mode 100644 index 0000000..08b22b8 --- /dev/null +++ b/Packages/Intelligence/Sources/Intelligence/Intelligence.swift @@ -0,0 +1,2 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book diff --git a/Packages/Intelligence/Sources/Intelligence/LLM/MLXProvider.swift b/Packages/Intelligence/Sources/Intelligence/LLM/MLXProvider.swift new file mode 100644 index 0000000..0f52ee1 --- /dev/null +++ b/Packages/Intelligence/Sources/Intelligence/LLM/MLXProvider.swift @@ -0,0 +1,156 @@ +// +// MLXProvider.swift +// Intelligence +// +// Created by John Mai on 2025/3/1. +// + +import Foundation +import MLX +import MLXLLM +import MLXLMCommon +import MLXVLM + +actor MLXProvider: ProviderProtocol { + let cacheDir = FileManager.default.homeDirectoryForCurrentUser + .appendingPathComponent( + ".cache", + isDirectory: true + ) + .appendingPathComponent( + "huggingface", + isDirectory: true + ) + .appendingPathComponent( + "hub", + isDirectory: true + ) + + var running = false + + enum LoadState { + case idle + case loaded(ModelContainer) + } + + var loadState = LoadState.idle + + private let supportedVLM = [ + "paligemma", + "qwen2_vl", + "idefics3", + ] + + private let supportedLLM = [ + "mistral", + "llama", + "phi", + "phi3", + "phimoe", + "gemma", + "gemma2", + "qwen2", + "starcoder2", + "cohere", + "openelm", + "internlm2", + ] + + func load(model: Model) async throws -> ModelContainer { + switch loadState { + case .idle: + GPU.set(cacheLimit: 20 * 1024 * 1024) + + guard case .local(let modelDirectory) = model.model else { + fatalError("Model is not local") + } + + let modelConfiguration = ModelConfiguration(directory: modelDirectory) + + let baseConfig = try JSONDecoder().decode( + BaseConfiguration.self, + from: Data( + contentsOf: modelDirectory.appending( + component: "config.json" + ) + ) + ) + + if supportedVLM.contains(baseConfig.modelType) { + let modelContainer = try await VLMModelFactory.shared.loadContainer( + configuration: modelConfiguration + ) + + loadState = .loaded(modelContainer) + return modelContainer + } + + if supportedLLM.contains(baseConfig.modelType) { + let modelContainer = try await LLMModelFactory.shared.loadContainer( + configuration: modelConfiguration + ) + + loadState = .loaded(modelContainer) + return modelContainer + } + + fatalError("Revision file not found") + case .loaded(let modelContainer): + return modelContainer + } + } + + func streamingChatCompletion( + model: Model, + messages: [ChatCompletionMessage], + options: ChatCompletionOptions? + ) async -> AsyncThrowingStream { + return AsyncThrowingStream { continuation in + Task { + do { + let modelContainer = try await load(model: model) + + let result = try await modelContainer.perform { context in + + var _messages:[Message] = [] + for message in messages { + _messages.append(["role": message.role.rawValue, "content": message.content]) + } + + print("messages -> \(_messages)") + + let input = try await context.processor.prepare( + input: .init(messages: _messages, tools: nil)) + return try MLXLMCommon.generate( + input: input, + parameters: GenerateParameters(temperature: 0.6), + context: context + ) { tokens in + let text = context.tokenizer.decode(tokens: tokens) + + let chunk = ChatCompletionStreamChunk(content: text, finishReason: nil) + + continuation.yield(chunk) + + if tokens.count >= 8000 { + return .stop + } else { + return .more + } + } + } + + let chunk = ChatCompletionStreamChunk(content: result.output, finishReason: nil) + + continuation.yield(chunk) + continuation.finish() + } catch { + continuation.finish(throwing: error) + } + } + } + } + + func cancel() async { + } +} diff --git a/Packages/Intelligence/Sources/Intelligence/LLM/OpenAIProvider.swift b/Packages/Intelligence/Sources/Intelligence/LLM/OpenAIProvider.swift new file mode 100644 index 0000000..0737888 --- /dev/null +++ b/Packages/Intelligence/Sources/Intelligence/LLM/OpenAIProvider.swift @@ -0,0 +1,47 @@ +// +// OpenAIProvider.swift +// Intelligence +// +// Created by John Mai on 2025/3/2. +// + +import Foundation +import OpenAI + +final class OpenAIProvider: ProviderProtocol { + func streamingChatCompletion( + model: Model, + messages: [ChatCompletionMessage], + options: ChatCompletionOptions? + ) async -> AsyncThrowingStream { + return AsyncThrowingStream { continuation in + Task { + do { + let openAI = OpenAI(apiToken: "YOUR_TOKEN_HERE") + + for try await result in openAI.chatsStream( + query: .init( + messages: [ + .user(.init(content: .string("你好"))) + ], model: .gpt4_o_mini)) + { + result.choices.forEach { choice in + continuation.yield( + ChatCompletionStreamChunk( + content: choice.delta.content, finishReason: nil)) + } + } + + continuation.finish() + } catch { + continuation.finish(throwing: error) + } + } + } + } + + func cancel() async { + // Not implemented + } + +} diff --git a/Packages/Intelligence/Sources/Intelligence/Protocols/ProviderProtocol.swift b/Packages/Intelligence/Sources/Intelligence/Protocols/ProviderProtocol.swift new file mode 100644 index 0000000..5975c44 --- /dev/null +++ b/Packages/Intelligence/Sources/Intelligence/Protocols/ProviderProtocol.swift @@ -0,0 +1,16 @@ +// +// Provider.swift +// Intelligence +// +// Created by John Mai on 2025/3/2. +// + +public protocol ProviderProtocol: Sendable { + func streamingChatCompletion( + model: Model, + messages: [ChatCompletionMessage], + options: ChatCompletionOptions? + ) async -> AsyncThrowingStream + + func cancel() async +} diff --git a/Packages/Intelligence/Sources/Intelligence/ProviderFactory.swift b/Packages/Intelligence/Sources/Intelligence/ProviderFactory.swift new file mode 100644 index 0000000..3d3d156 --- /dev/null +++ b/Packages/Intelligence/Sources/Intelligence/ProviderFactory.swift @@ -0,0 +1,19 @@ +// +// ProviderFactory.swift +// Intelligence +// +// Created by John Mai on 2025/3/2. +// + +import Foundation + +public final class ProviderFactory { + public static func createProvider(provider: Provider) -> ProviderProtocol { + switch provider { + case .openAI: + fatalError("OpenAI is not supported yet") + case .mlx: + return MLXProvider() + } + } +} diff --git a/Packages/Models/Tests/ModelsTests/ModelsTests.swift b/Packages/Intelligence/Tests/IntelligenceTests/IntelligenceTests.swift similarity index 82% rename from Packages/Models/Tests/ModelsTests/ModelsTests.swift rename to Packages/Intelligence/Tests/IntelligenceTests/IntelligenceTests.swift index 089210c..aefc7b7 100644 --- a/Packages/Models/Tests/ModelsTests/ModelsTests.swift +++ b/Packages/Intelligence/Tests/IntelligenceTests/IntelligenceTests.swift @@ -1,6 +1,5 @@ import Testing - -@testable import Models +@testable import Intelligence @Test func example() async throws { // Write your test here and use APIs like `#expect(...)` to check expected conditions. diff --git a/Packages/Intelligence/Tests/IntelligenceTests/MLXProviderTests.swift b/Packages/Intelligence/Tests/IntelligenceTests/MLXProviderTests.swift new file mode 100644 index 0000000..9204109 --- /dev/null +++ b/Packages/Intelligence/Tests/IntelligenceTests/MLXProviderTests.swift @@ -0,0 +1,25 @@ +// +// LLMTests.swift +// Intelligence +// +// Created by John Mai on 2025/3/1. +// + +import Testing + +@testable import Intelligence + +@Test func mlx() async throws { +// let service = ProviderFactory.createProvider(provider: .mlx) +// +// let stream = await service.streamingChatCompletion( +//// model: "Qwen/Qwen2-0.5B-Instruct-MLX", +// model: "mlx-community/Qwen2-VL-2B-Instruct-4bit", +// messages: [], +// options: ChatCompletionOptions() +// ) +// +// for try await chunk in stream { +// print("chunk -> \(chunk))") +// } +} diff --git a/Packages/Models/Sources/Models/Conversation.swift b/Packages/Models/Sources/Models/Conversation.swift deleted file mode 100644 index a1d5411..0000000 --- a/Packages/Models/Sources/Models/Conversation.swift +++ /dev/null @@ -1,52 +0,0 @@ -// -// Conversation.swift -// Models -// -// Created by John Mai on 2025/2/22. -// - -import Foundation - -public struct Conversation: Identifiable { - public var id = UUID() - - public var title: String - - public var description: String? - - public var model: Model - - public var messages: [Message] = [] - - public var current_node: Message.ID? - - public var createdTime: Date - - public var updatedTime: Date - - public init( - id: UUID = UUID(), - title: String, - description: String? = nil, - model: Model, - messages: [Message] = [], - current_node: Message.ID? = nil, - createdTime: Date = Date(), - updatedTime: Date = Date() - ) { - self.id = id - self.title = title - self.description = description - self.model = model - self.messages = messages - self.current_node = current_node - self.createdTime = createdTime - self.updatedTime = updatedTime - } -} - -extension Conversation: Equatable, Hashable { - public static func == (lhs: Conversation, rhs: Conversation) -> Bool { - return lhs.id == rhs.id - } -} diff --git a/Packages/Models/Sources/Models/Message.swift b/Packages/Models/Sources/Models/Message.swift deleted file mode 100644 index 2b7fe85..0000000 --- a/Packages/Models/Sources/Models/Message.swift +++ /dev/null @@ -1,27 +0,0 @@ -// -// Message.swift -// Models -// -// Created by John Mai on 2025/2/22. -// - -import Foundation - -public struct Message: Identifiable, Equatable, Hashable { - public static func == (lhs: Message, rhs: Message) -> Bool { - return lhs.id == rhs.id - } - - public var id = UUID() - public var role: String - public var content: String - public var reasoning: String - public var model: Model - public var provider: String - public var error: String - public var tools: [String] - public var parent: Message.ID? - public var children: [Message.ID] - public var createdTime: Date - public var updatedTime: Date -} diff --git a/Packages/Models/Sources/Models/Model.swift b/Packages/Models/Sources/Models/Model.swift deleted file mode 100644 index 76e43d4..0000000 --- a/Packages/Models/Sources/Models/Model.swift +++ /dev/null @@ -1,43 +0,0 @@ -// -// Model.swift -// Models -// -// Created by John Mai on 2025/2/22. -// - -import Foundation - -public enum Provider { - case mlx - case openAI -} - -public enum ModelType: Equatable, Hashable, CustomStringConvertible { - case local(URL) - case id(String) - - public var description: String { - switch self { - case .local(let url): - return url.lastPathComponent - case .id(let id): - return id - } - } -} - -public struct Model: Hashable, CustomStringConvertible { - public let provider: Provider - public let name: String - public let model: ModelType - - public init(provider: Provider, name: String, model: ModelType) { - self.provider = provider - self.name = name - self.model = model - } - - public var description: String { - return name - } -} diff --git a/Packages/Models/Sources/Models/Role.swift b/Packages/Models/Sources/Models/Role.swift deleted file mode 100644 index e4af818..0000000 --- a/Packages/Models/Sources/Models/Role.swift +++ /dev/null @@ -1,13 +0,0 @@ -// -// Role.swift -// Models -// -// Created by John Mai on 2025/2/22. -// - -enum Role { - case system - case user - case assistant - case tool -} diff --git a/Packages/Models/Tests/ModelsTests/LanguageTests.swift b/Packages/Models/Tests/ModelsTests/LanguageTests.swift deleted file mode 100644 index 684c7ca..0000000 --- a/Packages/Models/Tests/ModelsTests/LanguageTests.swift +++ /dev/null @@ -1,21 +0,0 @@ -// -// LanguageTests.swift -// Common -// -// Created by John Mai on 2025/2/28. -// - -import Testing - -@testable import Models - -@Test func availableLanguages() async throws { - debugPrint(Language.allCases) - print("Available languages count: \(Language.allCases.count)") - - for language in Language.allCases { - print("Language: \(language)") - } - - #expect(Language.allCases.count == 40) -} diff --git a/Packages/Settings/Package.swift b/Packages/Settings/Package.swift index 769ffbc..4fc0f06 100644 --- a/Packages/Settings/Package.swift +++ b/Packages/Settings/Package.swift @@ -13,9 +13,8 @@ let package = Package( targets: ["Settings"]) ], dependencies: [ - .package(name: "Common", path: "../Common"), + .package(name: "Utilities", path: "../Utilities"), .package(name: "UltraUI", path: "../UltraUI"), - .package(name: "Models", path: "../Models"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -23,9 +22,8 @@ let package = Package( .target( name: "Settings", dependencies: [ - "Common", + "Utilities", "UltraUI", - "Models", ] ), .testTarget( diff --git a/Packages/Settings/Sources/Settings/AboutView.swift b/Packages/Settings/Sources/Settings/AboutView.swift index eb78ce8..170c797 100644 --- a/Packages/Settings/Sources/Settings/AboutView.swift +++ b/Packages/Settings/Sources/Settings/AboutView.swift @@ -1,6 +1,6 @@ // // AboutView.swift -// Sources +// Settings // // Created by John Mai on 2025/2/27. // diff --git a/Packages/Settings/Sources/Settings/DefaultConversationView.swift b/Packages/Settings/Sources/Settings/DefaultConversationView.swift index 85dc74c..91c1f6a 100644 --- a/Packages/Settings/Sources/Settings/DefaultConversationView.swift +++ b/Packages/Settings/Sources/Settings/DefaultConversationView.swift @@ -1,6 +1,6 @@ // // DefaultConversationView.swift -// Sources +// Settings // // Created by John Mai on 2025/2/27. // diff --git a/Packages/Settings/Sources/Settings/DownloadManager/DownloadManagerView.swift b/Packages/Settings/Sources/Settings/DownloadManager/DownloadManagerView.swift new file mode 100644 index 0000000..f57a3ef --- /dev/null +++ b/Packages/Settings/Sources/Settings/DownloadManager/DownloadManagerView.swift @@ -0,0 +1,53 @@ +// +// DownloadManagerView.swift +// Settings +// +// Created by John Mai on 2025/2/27. +// + +import SwiftUI +import UltraUI + +struct DownloadManagerView: View { + + @Environment(SettingsStore.self) var settingsStore + + var body: some View { + List { + ForEach(settingsStore.downloadTasks) { task in + DownloadTaskView(task: task) + } + } + .ultraToolbar { + UltraToolbarItem(placement: .trailing) { + Button { + Task { + do { + try await settingsStore.addDownloadTask() + } catch { + print(error) + } + } + } label: { + Label("Add Task", systemImage: "plus") + } + + Button { + Task { + await settingsStore.downloadTasks[0].task.pause() + } + } label: { + Label("Add Task2", systemImage: "plus") + } + + Button { + Task { + try await settingsStore.downloadTasks[0].task.download() + } + } label: { + Label("Add Task3", systemImage: "plus") + } + } + } + } +} diff --git a/Packages/Settings/Sources/Settings/DownloadManager/DownloadTaskView.swift b/Packages/Settings/Sources/Settings/DownloadManager/DownloadTaskView.swift new file mode 100644 index 0000000..5ef083a --- /dev/null +++ b/Packages/Settings/Sources/Settings/DownloadManager/DownloadTaskView.swift @@ -0,0 +1,67 @@ +// +// DownloadTaskView.swift +// Settings +// +// Created by John Mai on 2025/3/5. +// + +import Utilities +import SwiftUI + +struct DownloadTaskView: View { + + let task: DownloadTask + + var body: some View { + HStack { + VStack { + HStack { + Text(task.repoId) + .font(.headline) + .lineLimit(1) + .truncationMode(.head) + .help(task.repoId) + + Spacer() + + Text("\(task.progress * 100, specifier: "%.2f")%") + .font(.subheadline) + .frame(width: 50, alignment: .trailing) + } + + HStack { + Spacer() + + Text("\(task.downloaded) / \(task.total)") + .font(.caption) + .foregroundStyle(.white.opacity(0.7)) + } + + ProgressView(value: task.progress) + .progressViewStyle(LinearProgressViewStyle()) + .frame(height: 4) + } + + Spacer() + + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) + .font(.title2) + } + } +} + +#Preview { + VStack { + DownloadTaskView( + task: .init( + repoId: "mlx-community/Qwen2.5-0.5B-Instruct-4bit", + task: .init(repoId: "") + ) + ) + .labeledContentStyle(.horizontal) + .foregroundStyle(.white) + } + .padding() + .background(Color.black.opacity(0.5)) +} diff --git a/Packages/Settings/Sources/Settings/DownloadManagerView.swift b/Packages/Settings/Sources/Settings/DownloadManagerView.swift deleted file mode 100644 index e728987..0000000 --- a/Packages/Settings/Sources/Settings/DownloadManagerView.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// DownloadManagerView.swift -// Sources -// -// Created by John Mai on 2025/2/27. -// - -import SwiftUI - -struct DownloadManagerView: View { - var body: some View { - Text("Download Manager") - } -} diff --git a/Packages/Settings/Sources/Settings/ExperimentalFeaturesView.swift b/Packages/Settings/Sources/Settings/ExperimentalFeaturesView.swift index 64c80b0..ef46d16 100644 --- a/Packages/Settings/Sources/Settings/ExperimentalFeaturesView.swift +++ b/Packages/Settings/Sources/Settings/ExperimentalFeaturesView.swift @@ -1,6 +1,6 @@ // // ExperimentalFeaturesView.swift -// Sources +// Settings // // Created by John Mai on 2025/2/27. // diff --git a/Packages/Settings/Sources/Settings/GeneralView.swift b/Packages/Settings/Sources/Settings/GeneralView.swift index 8f1560a..d63c7ed 100644 --- a/Packages/Settings/Sources/Settings/GeneralView.swift +++ b/Packages/Settings/Sources/Settings/GeneralView.swift @@ -1,13 +1,12 @@ // // GeneralView.swift -// Sources +// Settings // // Created by John Mai on 2025/2/27. // -import Common +import Utilities import Defaults -import Models import SwiftUI import UltraUI diff --git a/Packages/Settings/Sources/Settings/HuggingFaceView.swift b/Packages/Settings/Sources/Settings/HuggingFaceView.swift index 6d28bcc..b404e1f 100644 --- a/Packages/Settings/Sources/Settings/HuggingFaceView.swift +++ b/Packages/Settings/Sources/Settings/HuggingFaceView.swift @@ -1,12 +1,12 @@ // // HuggingFaceView.swift -// Sources +// Settings // // Created by John Mai on 2025/2/27. // +import Utilities import Defaults -import Models import SwiftUI import UltraUI diff --git a/Packages/Settings/Sources/Settings/MCPServers/AddMCPServerView.swift b/Packages/Settings/Sources/Settings/MCPServers/AddMCPServerView.swift new file mode 100644 index 0000000..843da1c --- /dev/null +++ b/Packages/Settings/Sources/Settings/MCPServers/AddMCPServerView.swift @@ -0,0 +1,75 @@ +// +// AddMCPServerView.swift +// Settings +// +// Created by John Mai on 2025/3/5. +// + +import SwiftUI + +struct AddMCPServerView: View { + @Binding var isPresented: Bool + var onSave: (MCPServer) -> Void + + @State private var serverName = "" + @State private var selectedType: MCPServerType = .stdio + @State private var command = "" + @State private var serverLink = "" + @State private var toolInput = "" + @State private var tools: [String] = [] + + var body: some View { + NavigationView { + Form { + Section(header: Text("Server Information")) { + TextField("Server Name", text: $serverName) + + Picker("Server Type", selection: $selectedType) { + Text("stdio").tag(MCPServerType.stdio) + Text("sse").tag(MCPServerType.sse) + } + .pickerStyle(SegmentedPickerStyle()) + } + + // 根据选择的类型显示不同的输入项 + Section(header: Text(selectedType == .stdio ? "Command" : "Server Link")) { + if selectedType == .stdio { + TextField("Command", text: $command) + } else { + TextField("Server Link", text: $serverLink) + } + } + + // 工具输入 + Section(header: Text("Tools")) { + HStack { + TextField("Add Tool", text: $toolInput) + + Button(action: { + if !toolInput.isEmpty { + tools.append(toolInput) + toolInput = "" + } + }) { + Image(systemName: "plus.circle.fill") + } + } + + ForEach(tools, id: \.self) { tool in + HStack { + Text(tool) + Spacer() + Button(action: { + tools.removeAll { $0 == tool } + }) { + Image(systemName: "xmark.circle.fill") + .foregroundColor(.red) + } + } + } + } + } + .navigationTitle("Add New MCP Server") + } + } +} diff --git a/Packages/Settings/Sources/Settings/MCPServers/MCPServersView.swift b/Packages/Settings/Sources/Settings/MCPServers/MCPServersView.swift new file mode 100644 index 0000000..5f9d33b --- /dev/null +++ b/Packages/Settings/Sources/Settings/MCPServers/MCPServersView.swift @@ -0,0 +1,90 @@ +// +// MCPServersView.swift +// Settings +// +// Created by John Mai on 2025/2/27. +// + +import SwiftUI +import UltraUI + +enum MCPServerType: String, CaseIterable, Identifiable { + case stdio + case sse + + var id: String { self.rawValue } +} + + +struct MCPServer: Identifiable { + var id = UUID() + var name: String + var type: MCPServerType + var isOnline: Bool = true + var tools: [String] + var command: String? + var serverLink: String? + + static func createStdio(name: String, command: String, tools: [String]) -> MCPServer { + MCPServer(name: name, type: .stdio, tools: tools, command: command) + } + + static func createSSE(name: String, serverLink: String, tools: [String]) -> MCPServer { + MCPServer(name: name, type: .sse, tools: tools, serverLink: serverLink) + } +} + +struct MCPServersView: View { + @State private var servers: [MCPServer] = [ + MCPServer.createStdio( + name: "weather", + command: "node ~/mcp-quickstart/weather-server-typescript/build/index.js", + tools: ["get-alerts", "get-forecast", "get-mars-weather"] + ), + MCPServer.createSSE( + name: "fetch", + serverLink: "http://localhost:8765/sse", + tools: ["fetch"] + ) + ] + + @State private var showingAddServerSheet = false + + var body: some View { + List { + ForEach(servers) { server in + ServerItemView(server: server) + .listRowInsets(EdgeInsets(top: 4, leading: -4, bottom: 4, trailing: -4)) + .listRowSeparator(.hidden) + } + } + .listStyle(.plain) + .scrollContentBackground(.hidden) + .padding() + .ultraToolbar { + UltraToolbarItem(placement: .trailing) { + Button { + showingAddServerSheet = true + } label: { + Image(systemName: "plus") + } + .buttonStyle(.plain) + } + } + .sheet(isPresented: $showingAddServerSheet) { + AddMCPServerView( + isPresented: $showingAddServerSheet, + onSave: { newServer in + servers.append(newServer) + }) + } + } +} + +#Preview { + VStack { + MCPServersView() + .labeledContentStyle(.horizontal) + .foregroundStyle(.white) + }.background(Color.black.opacity(0.5)) +} diff --git a/Packages/Settings/Sources/Settings/MCPServers/ServerItemView.swift b/Packages/Settings/Sources/Settings/MCPServers/ServerItemView.swift new file mode 100644 index 0000000..2a40a7f --- /dev/null +++ b/Packages/Settings/Sources/Settings/MCPServers/ServerItemView.swift @@ -0,0 +1,102 @@ +// +// ServerItemView.swift +// Settings +// +// Created by John Mai on 2025/3/4. +// + +import SwiftUI + + +struct ServerItemView: View { + let server: MCPServer + + @Environment(\.ultraViewBackground) var utlraViewBackground + + var body: some View { + VStack(alignment: .leading, spacing: 12) { + HStack { + Circle() + .fill(server.isOnline ? Color.green : Color.red) + .frame(width: 10, height: 10) + + Text(server.name) + .font(.title2) + + Text(server.type.rawValue) + .font(.subheadline) + .foregroundStyle(.gray) + + Spacer() + + HStack { + Button(action: {}) { + Image(systemName: "pencil") + } + + Button(action: {}) { + Image(systemName: "arrow.clockwise") + } + + Button(action: {}) { + Image(systemName: "trash") + } + } + .buttonStyle(.ultraPlain) + } + + LabeledContent { + HStack(spacing: 8) { + ForEach(server.tools, id: \.self) { tool in + Text(tool) + .padding(.horizontal, 8) + .padding(.vertical, 2) + .background(Color.gray.opacity(0.2)) + .cornerRadius(4) + } + } + } label: { + HStack { + Image(systemName: "wrench") + Text("Tools:") + } + } + + if let command = server.command, server.type == .stdio { + LabeledContent { + Text(command) + } label: { + HStack { + Image(systemName: "terminal") + Text("Command:") + } + } + } else if let link = server.serverLink, server.type == .sse { + LabeledContent { + Text(link) + } label: { + HStack { + Image(systemName: "link") + Text("Server Link:") + } + } + } + } + .labeledContentStyle(.horizontal) + .padding() + .background(utlraViewBackground) + .shadow() + .cornerRadius(8) + } +} + +#Preview { + let server = MCPServer.createStdio( + name: "weather", + command: "node ~/mcp-quickstart/weather-server-typescript/build/index.js", + tools: ["get-alerts", "get-forecast", "get-mars-weather"] + ) + + ServerItemView(server: server) +} + diff --git a/Packages/Settings/Sources/Settings/MCPServersView.swift b/Packages/Settings/Sources/Settings/MCPServersView.swift deleted file mode 100644 index c44c340..0000000 --- a/Packages/Settings/Sources/Settings/MCPServersView.swift +++ /dev/null @@ -1,14 +0,0 @@ -// -// MCPServersView.swift -// Sources -// -// Created by John Mai on 2025/2/27. -// - -import SwiftUI - -struct MCPServersView: View { - var body: some View { - Text("MCP Servers") - } -} diff --git a/Packages/Settings/Sources/Settings/MLXCommunityView.swift b/Packages/Settings/Sources/Settings/MLXCommunityView.swift index 20da571..1f46d7d 100644 --- a/Packages/Settings/Sources/Settings/MLXCommunityView.swift +++ b/Packages/Settings/Sources/Settings/MLXCommunityView.swift @@ -1,6 +1,6 @@ // // MLXCommunityView.swift -// Sources +// Settings // // Created by John Mai on 2025/2/27. // diff --git a/Packages/Settings/Sources/Settings/Models/DownloadTask.swift b/Packages/Settings/Sources/Settings/Models/DownloadTask.swift new file mode 100644 index 0000000..91b84a2 --- /dev/null +++ b/Packages/Settings/Sources/Settings/Models/DownloadTask.swift @@ -0,0 +1,31 @@ +// +// DownloadTask.swift +// Utilities +// +// Created by John Mai on 2025/3/5. +// + +import Foundation +import HuggingfaceHub + +public struct DownloadTask:Sendable { + public let repoId: String + public let task: SnapshotDownloader + public var progress: Double = 0 + public var downloaded: Int64 = 0 + public var total: Int64 = 0 + public var isCompleted: Bool { + return progress >= 1.0 + } + + public init(repoId: String, task: SnapshotDownloader) { + self.repoId = repoId + self.task = task + } +} + +extension DownloadTask: Identifiable { + public var id: String { + repoId + } +} diff --git a/Packages/Models/Sources/Models/SettingsTab.swift b/Packages/Settings/Sources/Settings/Models/SettingsTab.swift similarity index 100% rename from Packages/Models/Sources/Models/SettingsTab.swift rename to Packages/Settings/Sources/Settings/Models/SettingsTab.swift diff --git a/Packages/Settings/Sources/Settings/ModelsView.swift b/Packages/Settings/Sources/Settings/ModelsView.swift index 8552c1d..3c6eac1 100644 --- a/Packages/Settings/Sources/Settings/ModelsView.swift +++ b/Packages/Settings/Sources/Settings/ModelsView.swift @@ -1,14 +1,59 @@ // // ModelsView.swift -// Sources +// Settings // // Created by John Mai on 2025/2/27. // +import Utilities +import Defaults import SwiftUI +import UltraUI struct ModelsView: View { + + @Environment(AppStore.self) var store + @Default(.disabledModels) var disabledModels + var body: some View { - Text("Models") + VStack { + ForEach(store.models.keys.sorted(by: { $0.rawValue < $1.rawValue }), id: \.self) { + key in + UltraSection { + ForEach(store.models[key] ?? [], id: \.self) { model in + LabeledContent(model.name) { + Toggle( + "", + isOn: Binding( + get: { + !disabledModels.contains(model.id) + }, + set: { value in + if value { + disabledModels.removeAll(where: { $0 == model.id }) + } else { + disabledModels.append(model.id) + } + }) + ).toggleStyle(.switch) + } + } + } header: { + Text("\(key)") + .fontWeight(.medium) + } + + } + + Spacer() + } + .padding() + .task { + do { + try store.loadModels() + } catch { + print(error) + } + } } } diff --git a/Packages/Settings/Sources/Settings/ProvidersView.swift b/Packages/Settings/Sources/Settings/ProvidersView.swift index 05bcac4..c09b5ca 100644 --- a/Packages/Settings/Sources/Settings/ProvidersView.swift +++ b/Packages/Settings/Sources/Settings/ProvidersView.swift @@ -1,14 +1,79 @@ // // ProvidersView.swift -// Sources +// Settings // // Created by John Mai on 2025/2/27. // +import CompactSlider +import Utilities +import Defaults import SwiftUI +import UltraUI struct ProvidersView: View { + + @State private var value = 0.5 + + @Default(.gpuCacheLimit) var gpuCacheLimit + @Default(.gpuMemoryLimit) var gpuMemoryLimit + + let defaultSize: CGFloat = 20 + + let physicalMemory = ProcessInfo.processInfo.physicalMemory / (1024 * 1024) + var body: some View { - Text("Providers") + VStack { + UltraSection { + LabeledContent { + CompactSlider( + value: $gpuCacheLimit.asFloat(), + in: 0 ... Float(physicalMemory), + step: 512 + ) + .frame(maxHeight: defaultSize) + Spacer() + Text("\(gpuCacheLimit) MB") + .font(.body) + .foregroundColor(.secondary) + } label: { + Text("GPU Cache Limit") + } + + LabeledContent { + CompactSlider( + value: $gpuMemoryLimit.asFloatOrNil(), + in: 0 ... Float(physicalMemory), + step: 512 + ) + .frame(maxHeight: defaultSize) + Spacer() + if let gpuMemoryLimit { + Text("\(gpuMemoryLimit) MB") + .font(.body) + } else { + Text("Unlimited") + .font(.body) + } + + } label: { + Text("GPU Memory Limit") + } + } header: { + Text("MLX") + .fontWeight(.medium) + } + + Spacer() + } + .padding() } } + +#Preview { + VStack { + ProvidersView() + .labeledContentStyle(.horizontal) + .foregroundStyle(.white) + }.background(Color.black.opacity(0.5)) +} diff --git a/Packages/Settings/Sources/Settings/SearchView.swift b/Packages/Settings/Sources/Settings/SearchView.swift index 992f5fb..b2a9aab 100644 --- a/Packages/Settings/Sources/Settings/SearchView.swift +++ b/Packages/Settings/Sources/Settings/SearchView.swift @@ -1,6 +1,6 @@ // // SearchView.swift -// Sources +// Settings // // Created by John Mai on 2025/2/27. // diff --git a/Packages/Settings/Sources/Settings/SettingsSidebarView.swift b/Packages/Settings/Sources/Settings/SettingsSidebarView.swift index 9b77cda..6b20c1d 100644 --- a/Packages/Settings/Sources/Settings/SettingsSidebarView.swift +++ b/Packages/Settings/Sources/Settings/SettingsSidebarView.swift @@ -1,11 +1,11 @@ // // SettingsSidebarView.swift -// Sources +// Settings // // Created by John Mai on 2025/2/27. // -import Models +import Utilities import SwiftUI import UltraUI diff --git a/Packages/Settings/Sources/Settings/SettingsStore.swift b/Packages/Settings/Sources/Settings/SettingsStore.swift index 86512fa..a50b423 100644 --- a/Packages/Settings/Sources/Settings/SettingsStore.swift +++ b/Packages/Settings/Sources/Settings/SettingsStore.swift @@ -1,14 +1,41 @@ // // SettingsStore.swift -// Sources +// Settings // // Created by John Mai on 2025/2/27. // +import Utilities import Foundation +import HuggingfaceHub @MainActor @Observable -final class SettingsStore { +public final class SettingsStore { + var downloadTasks: [DownloadTask] = [] + + public init() {} + + func addDownloadTask(repoId: String = "mlx-community/Qwen2.5-0.5B-Instruct-4bit") async throws { + let task = SnapshotDownloader( + repoId: repoId, + options: .init(onProgress: { progress in + Task { @MainActor in + if let index = self.downloadTasks.firstIndex(where: { $0.repoId == repoId }) { + self.downloadTasks[index].progress = progress.fractionCompleted + self.downloadTasks[index].downloaded = progress.completedUnitCount + + if self.downloadTasks[index].total != progress.totalUnitCount { + self.downloadTasks[index].total = progress.totalUnitCount + } + } + } + }) + ) + + downloadTasks.append(.init(repoId: repoId, task: task)) + + try await task.download() + } } diff --git a/Packages/Settings/Sources/Settings/SettingsView.swift b/Packages/Settings/Sources/Settings/SettingsView.swift index 0df5033..be9cfe3 100644 --- a/Packages/Settings/Sources/Settings/SettingsView.swift +++ b/Packages/Settings/Sources/Settings/SettingsView.swift @@ -1,11 +1,11 @@ // // SettingsView.swift -// Sources +// Settings // // Created by John Mai on 2025/2/27. // -import Models +import Utilities import SwiftUI import UltraUI @@ -41,7 +41,7 @@ public struct SettingsView: View { .labeledContentStyle(.horizontal) } .ultraWindowStyle() - .frame(width: 650, height: 480) + .frame(width: 720, height: 480) } @ViewBuilder @@ -72,3 +72,8 @@ public struct SettingsView: View { } } } + +#Preview { + SettingsView() + .background(Color.black.opacity(0.5)) +} diff --git a/Packages/UltraUI/Package.swift b/Packages/UltraUI/Package.swift index 641435c..b95b292 100644 --- a/Packages/UltraUI/Package.swift +++ b/Packages/UltraUI/Package.swift @@ -15,6 +15,8 @@ let package = Package( dependencies: [ .package(url: "https://github.com/krzyzanowskim/STTextView", from: "2.0.0"), .package(url: "https://github.com/siteline/swiftui-introspect", from: "1.0.0"), + .package(url: "https://github.com/buh/CompactSlider", from: "2.0.7"), + ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. @@ -23,6 +25,7 @@ let package = Package( name: "UltraUI", dependencies: [ "STTextView", + "CompactSlider", .product(name: "SwiftUIIntrospect", package: "swiftui-introspect"), ] ), diff --git a/Packages/UltraUI/Sources/UltraUI/Extensions/EnvironmentValues+Extensions.swift b/Packages/UltraUI/Sources/UltraUI/Extensions/EnvironmentValues+Extensions.swift index 7b41df7..f0efb13 100644 --- a/Packages/UltraUI/Sources/UltraUI/Extensions/EnvironmentValues+Extensions.swift +++ b/Packages/UltraUI/Sources/UltraUI/Extensions/EnvironmentValues+Extensions.swift @@ -13,7 +13,7 @@ extension EnvironmentValues { @Entry public var utlraWindowBlur: Int = 50 @Entry public var utlraWindowBackgroundColor: Color = .black.opacity(0.6) - @Entry public var utlraViewBackground: Color = .black.opacity(0.28) + @Entry public var ultraViewBackground: Color = .black.opacity(0.28) @Entry public var utlraSecondaryViewBackground: Color = .white.opacity(0.12) @Entry public var utlraTint: Color = .init(red: 21 / 255, green: 146 / 255, blue: 250 / 255) @@ -22,5 +22,4 @@ extension EnvironmentValues { @Entry public var utlraSubtitle: Color = .white.opacity(0.7) @Entry public var utlraText: Color = .white @Entry public var utlraPlaceholder: Color = .white.opacity(0.7) - } diff --git a/Packages/UltraUI/Sources/UltraUI/Extensions/NSWindow+Extensions.swift b/Packages/UltraUI/Sources/UltraUI/Extensions/NSWindow+Extensions.swift index d057878..3b8477f 100644 --- a/Packages/UltraUI/Sources/UltraUI/Extensions/NSWindow+Extensions.swift +++ b/Packages/UltraUI/Sources/UltraUI/Extensions/NSWindow+Extensions.swift @@ -10,15 +10,24 @@ import SwiftUI @_silgen_name("CGSDefaultConnectionForThread") func CGSDefaultConnectionForThread() -> CGSConnection? +//@_silgen_name("CGSSetWindowBackgroundBlurRadiusStyle") +//func CGSSetWindowBackgroundBlurRadiusStyle( +// _ connection: CGSConnection, _ windowNumber: CGWindowID, _ style: Int +//) -> OSStatus +// +//@_silgen_name("CGSSetWindowBackgroundBlurRadiusWithOpacityHint") +//func CGSSetWindowBackgroundBlurRadiusWithOpacityHint( +// _ connection: CGSConnection, _ windowNumber: CGWindowID, _ radius: Int, _ hint: Int +//) -> OSStatus + @_silgen_name("CGSSetWindowBackgroundBlurRadius") @discardableResult func CGSSetWindowBackgroundBlurRadius( _ connection: CGSConnection, _ windowNumber: CGWindowID, _ radius: Int -) -> CGError +) -> OSStatus typealias CGSConnection = UInt32 typealias CGWindowID = Int -typealias CGError = Int32 extension NSWindow { func setWindowBackgroundBlurRadius(_ radius: Int = 50) { diff --git a/Packages/UltraUI/Sources/UltraUI/Extensions/View+Extensions.swift b/Packages/UltraUI/Sources/UltraUI/Extensions/View+Extensions.swift index aec1e03..bc35ad9 100644 --- a/Packages/UltraUI/Sources/UltraUI/Extensions/View+Extensions.swift +++ b/Packages/UltraUI/Sources/UltraUI/Extensions/View+Extensions.swift @@ -10,7 +10,7 @@ import SwiftUIIntrospect extension View { public func shadow() -> some View { - self.shadow(radius: 6) + self.shadow(radius: 8) } public func ultraWindowStyle() -> some View { diff --git a/Packages/UltraUI/Sources/UltraUI/Modifiers/UltraWindowStyleViewModifier.swift b/Packages/UltraUI/Sources/UltraUI/Modifiers/UltraWindowStyleViewModifier.swift index defc5a1..56b9131 100644 --- a/Packages/UltraUI/Sources/UltraUI/Modifiers/UltraWindowStyleViewModifier.swift +++ b/Packages/UltraUI/Sources/UltraUI/Modifiers/UltraWindowStyleViewModifier.swift @@ -18,6 +18,7 @@ struct UltraWindowStyleViewModifier: ViewModifier { window.setWindowBackgroundBlurRadius(utlraWindowBlur) window.backgroundColor = NSColor(utlraWindowBackgroundColor) +// window.backgroundColor = .clear window.toolbarStyle = .unified window.titlebarAppearsTransparent = true diff --git a/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraButtonStyle.swift b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraButtonStyle.swift index 091cd7b..93a0e34 100644 --- a/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraButtonStyle.swift +++ b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraButtonStyle.swift @@ -16,6 +16,7 @@ extension ButtonStyle where Self == UltraButtonStyle { public struct UltraButtonStyle: ButtonStyle { @Environment(\.utlraSecondaryViewBackground) var utlraSecondaryViewBackground + @Environment(\.isEnabled) private var isEnabled @State private var isHovered = false private let cornerRadius: CGFloat @@ -45,6 +46,7 @@ public struct UltraButtonStyle: ButtonStyle { public func makeBody(configuration: Configuration) -> some View { configuration.label + .foregroundStyle(isEnabled ? .white : .gray) .padding(.horizontal, 16) .padding(.vertical, 8) .foregroundStyle(.white) diff --git a/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraPlainButtonStyle.swift b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraPlainButtonStyle.swift index ec503ed..1400303 100644 --- a/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraPlainButtonStyle.swift +++ b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraPlainButtonStyle.swift @@ -8,17 +8,17 @@ import SwiftUI extension ButtonStyle where Self == UltraPlainButtonStyle { - static var ultraPlain: some ButtonStyle { + public static var ultraPlain: some ButtonStyle { UltraPlainButtonStyle() } } -struct UltraPlainButtonStyle: ButtonStyle { +public struct UltraPlainButtonStyle: ButtonStyle { @Environment(\.utlraSecondaryViewBackground) var utlraSecondaryViewBackground @State private var isHovered = false - func makeBody(configuration: Configuration) -> some View { + public func makeBody(configuration: Configuration) -> some View { HStack { configuration.label .padding(.horizontal, 16) diff --git a/Packages/UltraUI/Sources/UltraUI/UltraNavigationSplitView.swift b/Packages/UltraUI/Sources/UltraUI/UltraNavigationSplitView.swift index d7b73fe..78d6979 100644 --- a/Packages/UltraUI/Sources/UltraUI/UltraNavigationSplitView.swift +++ b/Packages/UltraUI/Sources/UltraUI/UltraNavigationSplitView.swift @@ -198,8 +198,15 @@ public struct UltraNavigationSplitView: View { if isSidebarVisible { ZStack(alignment: .trailing) { sidebar() + .safeAreaInset(edge: .top, alignment: .trailing, spacing: 0) { + HStack { + leadingToolbarItems() + } + .frame(height: 52) + .padding(.horizontal) + } .frame(width: initialSidebarWidth) - .padding(.top, 32) + // .padding(.top, 32) .background(UltraSidebarBackgroundView()) Rectangle() @@ -251,6 +258,21 @@ public struct UltraNavigationSplitView: View { .environment(\.ultraNavigationState, state) } + @ViewBuilder + func leadingToolbarItems() -> some View { + ForEach(state.toolbarItems.filter { $0.placement == .leading }) { item in + item.content + } + + Button { + toggleSidebar() + } label: { + Image(systemName: "sidebar.leading") + .font(.title3) + } + .buttonStyle(.ultraIcon) + } + @ViewBuilder func header() -> some View { VStack(spacing: 0) { @@ -260,18 +282,8 @@ public struct UltraNavigationSplitView: View { if !isSidebarVisible { Spacer() .frame(width: 80) - } - Button { - toggleSidebar() - } label: { - Image(systemName: "sidebar.leading") - .font(.title3) - } - .buttonStyle(.plain) - - ForEach(state.toolbarItems.filter { $0.placement == .leading }) { item in - item.content + leadingToolbarItems() } Spacer() diff --git a/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptions.swift b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptions.swift new file mode 100644 index 0000000..cc2f779 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptions.swift @@ -0,0 +1,40 @@ +// +// UltraOptions.swift +// UltraUI +// +// Created by John Mai on 2025/3/8. +// + +import SwiftUI + +struct UltraOptions: View { + + @Binding var selection: SelectionValue? + let options: [SelectionValue] + let onSelection: (SelectionValue) -> Void + @Environment(\.ultraViewBackground) var utlraViewBackground + + var body: some View { + ScrollView { + ForEach(options, id: \.self) { option in + Button { + selection = option + onSelection(option) + } label: { + Text("\(option)") + .padding(.horizontal, 16) + .padding(.vertical, 8) + .frame(maxWidth: .infinity, alignment: .leading) + + } + .buttonStyle( + UltraSidebarButtonStyle(selection == option) + ) + } + } + .padding(4) + .background(utlraViewBackground) + .cornerRadius(10) + .frame(maxHeight: 200) + } +} diff --git a/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsWindowController.swift b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsWindowController.swift index 14c5399..fc78590 100644 --- a/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsWindowController.swift +++ b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsWindowController.swift @@ -28,10 +28,10 @@ final class UltraOptionsWindowController: NSWindowController, Sendable { window.isFloatingPanel = true window.hidesOnDeactivate = false window.collectionBehavior = .canJoinAllSpaces - window.hasShadow = true window.isOpaque = false window.backgroundColor = .clear - + window.hasShadow = true + self.init(window: window) } diff --git a/Packages/UltraUI/Sources/UltraUI/UltraSection.swift b/Packages/UltraUI/Sources/UltraUI/UltraSection.swift index 1dd3b60..c88a640 100644 --- a/Packages/UltraUI/Sources/UltraUI/UltraSection.swift +++ b/Packages/UltraUI/Sources/UltraUI/UltraSection.swift @@ -20,7 +20,7 @@ public struct UltraSection { private var isCollapsible: Bool = false private var _isExpanded: Binding? - @Environment(\.utlraViewBackground) var utlraViewBackground + @Environment(\.ultraViewBackground) var utlraViewBackground private init(header: Header, content: Content, footer: Footer) { self.header = header @@ -42,7 +42,6 @@ extension UltraSection: View where Header: View, Content: View, Footer: View { .padding() .background(utlraViewBackground) .cornerRadius(10) - } footer @@ -114,14 +113,12 @@ extension UltraSection where Header: View, Content: View, Footer == EmptyView { } } -@available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.0, *) extension UltraSection where Header == EmptyView, Content: View, Footer == EmptyView { public init(@ViewBuilder content: () -> Content) { self.init(header: EmptyView(), content: content(), footer: EmptyView()) } } -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) extension UltraSection where Header == Text, Content: View, Footer == EmptyView { public init(_ titleKey: LocalizedStringKey, @ViewBuilder content: () -> Content) { self.init(header: Text(titleKey), content: content(), footer: EmptyView()) @@ -132,9 +129,7 @@ extension UltraSection where Header == Text, Content: View, Footer == EmptyView } } -@available(iOS 17.0, macOS 14.0, tvOS 17.0, watchOS 10.0, visionOS 1.0, *) extension UltraSection where Header: View, Content: View, Footer == EmptyView { - /// 创建一个带有展开状态控制的section public init( isExpanded: Binding, @ViewBuilder content: () -> Content, @ViewBuilder header: () -> Header diff --git a/Packages/UltraUI/Sources/UltraUI/UltraSidebarBackgroundView.swift b/Packages/UltraUI/Sources/UltraUI/UltraSidebarBackgroundView.swift index 25be07c..2c29b60 100644 --- a/Packages/UltraUI/Sources/UltraUI/UltraSidebarBackgroundView.swift +++ b/Packages/UltraUI/Sources/UltraUI/UltraSidebarBackgroundView.swift @@ -8,7 +8,7 @@ import SwiftUI struct UltraSidebarBackgroundView: View { - @Environment(\.utlraViewBackground) var utlraViewBackground + @Environment(\.ultraViewBackground) var utlraViewBackground var body: some View { ZStack { diff --git a/Packages/Utilities/.gitignore b/Packages/Utilities/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Packages/Utilities/.gitignore @@ -0,0 +1,8 @@ +.DS_Store +/.build +/Packages +xcuserdata/ +DerivedData/ +.swiftpm/configuration/registries.json +.swiftpm/xcode/package.xcworkspace/contents.xcworkspacedata +.netrc diff --git a/Packages/Common/Package.swift b/Packages/Utilities/Package.swift similarity index 73% rename from Packages/Common/Package.swift rename to Packages/Utilities/Package.swift index 16292aa..214c260 100644 --- a/Packages/Common/Package.swift +++ b/Packages/Utilities/Package.swift @@ -4,33 +4,33 @@ import PackageDescription let package = Package( - name: "Common", + name: "Utilities", platforms: [.macOS(.v14)], products: [ // Products define the executables and libraries a package produces, making them visible to other packages. .library( - name: "Common", - targets: ["Common"]) + name: "Utilities", + targets: ["Utilities"]), ], dependencies: [ + .package(name: "Intelligence", path: "../Intelligence"), .package(name: "HuggingfaceHub", path: "../../../../maiqingqiang/HuggingfaceHub"), - .package(name: "Models", path: "../Models"), .package(url: "https://github.com/sindresorhus/Defaults", from: "9.0.2"), ], targets: [ // Targets are the basic building blocks of a package, defining a module or a test suite. // Targets can depend on other targets in this package and products from dependencies. .target( - name: "Common", + name: "Utilities", dependencies: [ - "Models", - "Defaults", + "Intelligence", "HuggingfaceHub", + "Defaults" ] ), .testTarget( - name: "CommonTests", - dependencies: ["Common"] + name: "UtilitiesTests", + dependencies: ["Utilities"] ), ] ) diff --git a/Packages/Utilities/Sources/Utilities/AppStore.swift b/Packages/Utilities/Sources/Utilities/AppStore.swift new file mode 100644 index 0000000..d9833ad --- /dev/null +++ b/Packages/Utilities/Sources/Utilities/AppStore.swift @@ -0,0 +1,31 @@ +// +// AppStore.swift +// Utilities +// +// Created by John Mai on 2025/2/27. +// + +import Defaults +import Foundation +import Intelligence + +@MainActor +@Observable +public final class AppStore { + public var models: [Provider: [Model]] = [:] + public var activeModels: [Model] = [] + + let huggingfaceHubService: HuggingfaceHubService = .init() + + public init() {} + + public func loadModels() throws { + models[.mlx] = try huggingfaceHubService.scanMLXModels() + + let disabledModels = Defaults[.disabledModels] + + activeModels = models.flatMap { $0.value }.filter { model in + !disabledModels.contains(model.id) + } + } +} diff --git a/Packages/Utilities/Sources/Utilities/Extensions/Binding+Extensions.swift b/Packages/Utilities/Sources/Utilities/Extensions/Binding+Extensions.swift new file mode 100644 index 0000000..5a6163c --- /dev/null +++ b/Packages/Utilities/Sources/Utilities/Extensions/Binding+Extensions.swift @@ -0,0 +1,45 @@ +// +// Binding+Extensions.swift +// Utilities +// +// Created by John Mai on 2025/3/1. +// + +import SwiftUI + +extension Binding { + public func toUnwrapped(defaultValue: T) -> Binding where Value == T? { + Binding(get: { self.wrappedValue ?? defaultValue }, set: { self.wrappedValue = $0 }) + } +} + +extension Binding where Value: Sendable { + public func asDouble() -> Binding where Value: BinaryInteger { + Binding( + get: { Double(self.wrappedValue) }, + set: { self.wrappedValue = Value($0) } + ) + } + + public func asFloat() -> Binding where Value: BinaryInteger { + Binding( + get: { Float(self.wrappedValue) }, + set: { self.wrappedValue = Value($0) } + ) + } + + + public func asFloatOrNil() -> Binding where Value == Optional { + return Binding( + get: { self.wrappedValue.map(Float.init) ?? 0 }, + set: { value in + if value == 0 { + self.wrappedValue = nil + } else { + self.wrappedValue = T(value) + } + } + ) + } + +} diff --git a/Packages/Common/Sources/Common/Extensions/Date+Extensions.swift b/Packages/Utilities/Sources/Utilities/Extensions/Date+Extensions.swift similarity index 95% rename from Packages/Common/Sources/Common/Extensions/Date+Extensions.swift rename to Packages/Utilities/Sources/Utilities/Extensions/Date+Extensions.swift index 2a203a9..242fd94 100644 --- a/Packages/Common/Sources/Common/Extensions/Date+Extensions.swift +++ b/Packages/Utilities/Sources/Utilities/Extensions/Date+Extensions.swift @@ -8,7 +8,7 @@ import Foundation extension Date { - func formatted() -> String { + public func shortFormatted() -> String { let now = Date() let calendar = Calendar.current diff --git a/Packages/Common/Sources/Common/Extensions/Defaults+Extensions.swift b/Packages/Utilities/Sources/Utilities/Extensions/Defaults+Extensions.swift similarity index 65% rename from Packages/Common/Sources/Common/Extensions/Defaults+Extensions.swift rename to Packages/Utilities/Sources/Utilities/Extensions/Defaults+Extensions.swift index 8b28d96..068ecba 100644 --- a/Packages/Common/Sources/Common/Extensions/Defaults+Extensions.swift +++ b/Packages/Utilities/Sources/Utilities/Extensions/Defaults+Extensions.swift @@ -1,13 +1,12 @@ // // Defaults+Extensions.swift -// Common +// Utilities // // Created by John Mai on 2025/3/1. // import Defaults import Foundation -import Models extension Defaults.Keys { // MARK: - General @@ -20,4 +19,11 @@ extension Defaults.Keys { public static let huggingFaceEndpoint = Key( "huggingFaceEndpoint", default: .hugfaceFace) public static let huggingFaceCachePath = Key("huggingFaceCachePath", default: nil) + + // MARK: - Models + public static let disabledModels = Key<[String]>("disabledModels", default: []) + + // MARK: - Providers + public static let gpuCacheLimit = Key("gpuCacheLimit", default: 1024) + public static let gpuMemoryLimit = Key("gpuMemoryLimit", default: nil) } diff --git a/Packages/Common/Sources/Common/Utilities/MarkdownMetadata.swift b/Packages/Utilities/Sources/Utilities/MarkdownMetadata.swift similarity index 99% rename from Packages/Common/Sources/Common/Utilities/MarkdownMetadata.swift rename to Packages/Utilities/Sources/Utilities/MarkdownMetadata.swift index 7da98c7..ebf74ff 100644 --- a/Packages/Common/Sources/Common/Utilities/MarkdownMetadata.swift +++ b/Packages/Utilities/Sources/Utilities/MarkdownMetadata.swift @@ -1,6 +1,6 @@ // // MarkdownMetadata.swift -// Common +// Utilities // // Created by John Mai on 2025/2/27. // diff --git a/Packages/Models/Sources/Models/HuggingFaceEndpoint.swift b/Packages/Utilities/Sources/Utilities/Models/HuggingFaceEndpoint.swift similarity index 100% rename from Packages/Models/Sources/Models/HuggingFaceEndpoint.swift rename to Packages/Utilities/Sources/Utilities/Models/HuggingFaceEndpoint.swift diff --git a/Packages/Models/Sources/Models/Language.swift b/Packages/Utilities/Sources/Utilities/Models/Language.swift similarity index 99% rename from Packages/Models/Sources/Models/Language.swift rename to Packages/Utilities/Sources/Utilities/Models/Language.swift index dee7524..7edc250 100644 --- a/Packages/Models/Sources/Models/Language.swift +++ b/Packages/Utilities/Sources/Utilities/Models/Language.swift @@ -1,6 +1,6 @@ // // Language.swift -// Common +// Utilities // // Created by John Mai on 2025/2/28. // diff --git a/Packages/Utilities/Sources/Utilities/Services/HuggingfaceHubService.swift b/Packages/Utilities/Sources/Utilities/Services/HuggingfaceHubService.swift new file mode 100644 index 0000000..8c53e52 --- /dev/null +++ b/Packages/Utilities/Sources/Utilities/Services/HuggingfaceHubService.swift @@ -0,0 +1,92 @@ +// +// HuggingfaceHubService.swift +// Utilities +// +// Created by John Mai on 2025/2/19. +// + +import Defaults +import Foundation +import HuggingfaceHub +import Intelligence + +struct HuggingfaceHubService { + func scanMLXModels() throws -> [Model] { + var cacheDir = Defaults[.huggingFaceCachePath] + + if cacheDir == nil { + let pw = getpwuid(getuid())! + let huggingfaceDir = URL( + fileURLWithFileSystemRepresentation: pw.pointee.pw_dir, + isDirectory: true, + relativeTo: nil + ) + .appendingPathComponent(".cache") + .appendingPathComponent("huggingface") + + cacheDir = huggingfaceDir + } + + cacheDir = cacheDir?.appendingPathComponent("hub") + + let hfCacheInfo = try CacheManager(cacheDir: cacheDir).scanCacheDir() + + return try hfCacheInfo.repos.filter { repo in + repo.repoId.hasPrefix("mlx-community/") || repo.repoId.contains("-MLX") + || isMLX(repo: repo) + }.map { repo in + + // let file = try? HuggingfaceHub.Utility.tryToLoadFromCache( + // repoId: repo.repoId, + // filename: "" + // ) + + return Model( + provider: .mlx, + name: repo.repoId, + model: try .local(getModelDirectory(repo.repoPath)) + ) + }.sorted { $0.name < $1.name } + } + + func getModelDirectory(_ repoPath: URL) throws -> URL { + let defaultRevision = "main" + let refsDir = repoPath.appendingPathComponent("refs") + let revisionFile = refsDir.appendingPathComponent(defaultRevision) + + guard FileManager.default.fileExists(atPath: revisionFile.path) else { + fatalError("Revision file not found") + } + + let actualRevision = try String(contentsOf: revisionFile) + let snapshotsDir = repoPath.appendingPathComponent("snapshots") + let modelDirectory = snapshotsDir.appendingPathComponent(actualRevision) + + return modelDirectory + } + + private func isMLX(repo: CachedRepoInfo) -> Bool { + if repo.repoId.hasPrefix("mlx-community/") { + return true + } + + let file = try? HuggingfaceHub.Utility.tryToLoadFromCache( + repoId: repo.repoId, filename: "README.md") + + guard let file else { + return false + } + + let markdown = try? String(contentsOf: file) + + guard let markdown else { + return false + } + + let metadata = MarkdownMetadata(from: markdown) + + let tags = metadata.array(for: "tags") + + return tags.contains("mlx") + } +} diff --git a/Packages/Utilities/Sources/Utilities/Utilities.swift b/Packages/Utilities/Sources/Utilities/Utilities.swift new file mode 100644 index 0000000..08b22b8 --- /dev/null +++ b/Packages/Utilities/Sources/Utilities/Utilities.swift @@ -0,0 +1,2 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book diff --git a/Packages/Common/Tests/CommonTests/CommonTests.swift b/Packages/Utilities/Tests/UtilitiesTests/UtilitiesTests.swift similarity index 84% rename from Packages/Common/Tests/CommonTests/CommonTests.swift rename to Packages/Utilities/Tests/UtilitiesTests/UtilitiesTests.swift index 579abbc..1dae078 100644 --- a/Packages/Common/Tests/CommonTests/CommonTests.swift +++ b/Packages/Utilities/Tests/UtilitiesTests/UtilitiesTests.swift @@ -1,6 +1,5 @@ import Testing - -@testable import Common +@testable import Utilities @Test func example() async throws { // Write your test here and use APIs like `#expect(...)` to check expected conditions. diff --git a/README-zh_CN.md b/README-zh_CN.md index e5138f5..72c63a4 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -32,7 +32,7 @@ ## 特性 🚀 -- **多语言**:支持应用商店中39种主流语言,包括英语、简体中文、繁体中文、日语和韩语。 +- **多语言**:支持应用商店中40种主流语言,包括英语、简体中文、繁体中文、日语和韩语。 - **多个模型**:提供多个模型,包括 Llama、OpenELM、Phi、Qwen、Starcoder 和 Cohere。 - **高性能**:基于 MLX 和 Apple silicon 的强大性能。 - **隐私与安全**:在本地运行 LLM,以确保用户隐私和安全。 diff --git a/README.md b/README.md index 551e8fe..6a45bbd 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ English | [简体中文](./README-zh_CN.md) ## Features 🚀 -- **Multilingual:** Supports all 39 major App Store languages, including English, Simplified Chinese, Traditional Chinese, Japanese, and Korean. +- **Multilingual:** Supports all 40 major App Store languages, including English, Simplified Chinese, Traditional Chinese, Japanese, and Korean. - **Multiple Models**: Provides multiple models, including Llama, OpenELM, Phi, Qwen, Starcoder, Cohere, Gemma. - **High Performance**: Based on the powerful performance of MLX and Apple silicon. - **Privacy and Security**: Run LLM locally to ensure user privacy and security.