diff --git a/ChatMLX.xcodeproj/project.pbxproj b/ChatMLX.xcodeproj/project.pbxproj index 2e9f4fa..b2b2269 100644 --- a/ChatMLX.xcodeproj/project.pbxproj +++ b/ChatMLX.xcodeproj/project.pbxproj @@ -3,426 +3,69 @@ 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 */; }; /* 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 = ""; }; + 523068212D71E6390069F093 /* Conversation */ = {isa = PBXFileReference; lastKnownFileType = wrapper; name = Conversation; path = Packages/Conversation; sourceTree = ""; }; + 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 = ""; }; + 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 */ + 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 */, + 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 */ = { - 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; - 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 */ = { + 523067D02D71DF3A0069F093 /* Frameworks */ = { isa = PBXGroup; children = ( - 526676342C85F903001EF113 /* LLMRunner.swift */, - 526676352C85F903001EF113 /* Logger.swift */, - 528D831B2CAD49E600163AAB /* PersistenceController.swift */, - 526676332C85F903001EF113 /* Huggingface */, ); - path = Utilities; + name = Frameworks; 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 */, + 527749362D807A3A00D420E4 /* Utilities */, + 526DAC492D7DC21D00CD9270 /* Intelligence */, + 5216AFDE2D7C5E1900556FD6 /* Database */, + 523068212D71E6390069F093 /* Conversation */, + 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 +83,13 @@ ); 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 */, + 523068222D71E6540069F093 /* Conversation */, + 523068972D71F0FC0069F093 /* Settings */, ); productName = ChatMLX; productReference = 526675422C85EDCB001EF113 /* ChatMLX.app */; @@ -499,16 +124,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 +139,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 +149,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 +283,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 +313,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 +362,16 @@ }; /* 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 */ = { - isa = XCSwiftPackageProductDependency; - productName = Transformers; - }; - 526675612C85EFB3001EF113 /* Alamofire */ = { - isa = XCSwiftPackageProductDependency; - package = 526675602C85EFB3001EF113 /* XCRemoteSwiftPackageReference "Alamofire" */; - productName = Alamofire; - }; - 526675642C85EFC7001EF113 /* AlertToast */ = { - isa = XCSwiftPackageProductDependency; - package = 526675632C85EFC7001EF113 /* XCRemoteSwiftPackageReference "AlertToast" */; - productName = AlertToast; - }; - 526675672C85EFDF001EF113 /* CompactSlider */ = { - isa = XCSwiftPackageProductDependency; - package = 526675662C85EFDF001EF113 /* XCRemoteSwiftPackageReference "CompactSlider" */; - productName = CompactSlider; - }; - 5266756A2C85F0E8001EF113 /* Defaults */ = { - isa = XCSwiftPackageProductDependency; - package = 526675692C85F0E8001EF113 /* XCRemoteSwiftPackageReference "Defaults" */; - productName = Defaults; - }; - 5266756D2C85F0FF001EF113 /* Luminare */ = { + 523068222D71E6540069F093 /* Conversation */ = { isa = XCSwiftPackageProductDependency; - package = 5266756C2C85F0FF001EF113 /* XCRemoteSwiftPackageReference "Luminare" */; - productName = Luminare; + productName = Conversation; }; - 526675732C85F1F9001EF113 /* Splash */ = { + 523068972D71F0FC0069F093 /* Settings */ = { 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 = Settings; }; /* 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..90d9823 100644 --- a/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,67 +1,67 @@ { - "originHash" : "91755e46d4857336740696612733433e7fa7ef978bc35290de8f756037756422", + "originHash" : "29f3c5c70c2ac2ac0b01d925b1acaf68aba9d44303267147c908eeb2aebb5082", "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" : "compactslider", "kind" : "remoteSourceControl", - "location" : "https://github.com/elai950/AlertToast.git", + "location" : "https://github.com/buh/CompactSlider", "state" : { - "revision" : "b39252eacd159904afd7d718bba0afabb87f2f2f", - "version" : "1.3.9" + "revision" : "dcd792d9fb99d70a68782ab02ff23775cc6461a1", + "version" : "2.0.7" } }, { - "identity" : "compactslider", + "identity" : "coretextswift", "kind" : "remoteSourceControl", - "location" : "https://github.com/buh/CompactSlider.git", + "location" : "https://github.com/krzyzanowskim/CoreTextSwift", "state" : { - "revision" : "abe4d1df6f0c85dcb133266cc07c2a5d08295726", - "version" : "1.1.6" + "revision" : "833177201d6421e6322296f39fce6ff6ae52618a", + "version" : "0.2.0" } }, { "identity" : "defaults", "kind" : "remoteSourceControl", - "location" : "https://github.com/sindresorhus/Defaults.git", + "location" : "https://github.com/sindresorhus/Defaults", "state" : { - "branch" : "main", - "revision" : "4c009d5c2496e7aa126e922c94dd3d6ae049efa2" + "revision" : "cc938ecf0bed848dc80a9726ac5455104e3f9dae", + "version" : "9.0.2" } }, { - "identity" : "gzipswift", + "identity" : "grdb.swift", "kind" : "remoteSourceControl", - "location" : "https://github.com/1024jp/GzipSwift", + "location" : "https://github.com/groue/GRDB.swift.git", "state" : { - "revision" : "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", - "version" : "6.0.1" + "revision" : "6eba24d16952452a8a54f6a639491f3c8215527f", + "version" : "7.3.0" } }, { - "identity" : "jinja", + "identity" : "gzipswift", "kind" : "remoteSourceControl", - "location" : "https://github.com/maiqingqiang/Jinja", + "location" : "https://github.com/1024jp/GzipSwift", "state" : { - "revision" : "6dbe4c449469fb586d0f7339f900f0dd4d78b167", - "version" : "1.0.6" + "revision" : "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", + "version" : "6.0.1" } }, { - "identity" : "luminare", + "identity" : "jinja", "kind" : "remoteSourceControl", - "location" : "https://github.com/MrKai77/Luminare.git", + "location" : "https://github.com/johnmai-dev/Jinja", "state" : { - "branch" : "main", - "revision" : "a9c1d600e972c5523c27eb7cd84637f4f05beaaa" + "revision" : "bbddb92fc51ae420b87300298370fd1dfc308f73", + "version" : "1.1.1" } }, { @@ -69,8 +69,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/ml-explore/mlx-swift", "state" : { - "revision" : "d649c62b77c487c25012910b0d02b30283d388ca", - "version" : "0.18.1" + "revision" : "b990c58153af70eb0914bca7dd74401d341fa9ae", + "version" : "0.21.3" } }, { @@ -79,7 +79,7 @@ "location" : "https://github.com/ml-explore/mlx-swift-examples/", "state" : { "branch" : "main", - "revision" : "a719a6d05f19e5c25be201842a5c3d471cbd0c38" + "revision" : "11cab4cbe2ba45aaf12dff9572807110580c0ff0" } }, { @@ -92,39 +92,75 @@ } }, { - "identity" : "splash", + "identity" : "openai", "kind" : "remoteSourceControl", - "location" : "https://github.com/johnsundell/splash.git", + "location" : "https://github.com/MacPaw/OpenAI.git", "state" : { - "revision" : "7f4df436eb78fe64fe2c32c58006e9949fa28ad8", - "version" : "0.16.0" + "branch" : "0.3.6", + "revision" : "e59e4923acc924db51d415e99d0a76c7d54037e0" + } + }, + { + "identity" : "semaphore", + "kind" : "remoteSourceControl", + "location" : "https://github.com/groue/Semaphore", + "state" : { + "revision" : "2543679282aa6f6c8ecf2138acd613ed20790bc2", + "version" : "0.1.0" + } + }, + { + "identity" : "sttextkitplus", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzyzanowskim/STTextKitPlus", + "state" : { + "revision" : "446d6d431b163840ac5cc844ca5cf4704886c30e", + "version" : "0.1.8" + } + }, + { + "identity" : "sttextview", + "kind" : "remoteSourceControl", + "location" : "https://github.com/krzyzanowskim/STTextView", + "state" : { + "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" : "0fbc8848e389af3bb55c182bc19ca9d5dc2f255b", + "version" : "1.4.0" + } + }, + { + "identity" : "swift-cmark", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-cmark", "state" : { - "revision" : "41982a3656a71c768319979febd796c6fd111d5c", - "version" : "1.5.0" + "revision" : "3ccff77b2dc5b96b77db3da0d68d28068593fa53", + "version" : "0.5.0" } }, { - "identity" : "swift-log", + "identity" : "swift-collections", "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-log", + "location" : "https://github.com/apple/swift-collections.git", "state" : { - "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", - "version" : "1.6.1" + "revision" : "671108c96644956dddcd89dd59c203dcdb36cec7", + "version" : "1.1.4" } }, { "identity" : "swift-markdown-ui", "kind" : "remoteSourceControl", - "location" : "https://github.com/gonzalezreal/swift-markdown-ui.git", + "location" : "https://github.com/gonzalezreal/swift-markdown-ui", "state" : { - "branch" : "main", - "revision" : "55441810c0f678c78ed7e2ebd46dde89228e02fc" + "revision" : "5f613358148239d0292c0cef674a3c2314737f9e", + "version" : "2.4.1" } }, { @@ -136,13 +172,22 @@ "version" : "1.0.2" } }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/swiftlang/swift-syntax", + "state" : { + "revision" : "0687f71944021d616d34d922343dcef086855920", + "version" : "600.0.1" + } + }, { "identity" : "swift-transformers", "kind" : "remoteSourceControl", "location" : "https://github.com/huggingface/swift-transformers", "state" : { - "revision" : "d42fdae473c49ea216671da8caae58e102d28709", - "version" : "0.1.14" + "revision" : "55710ddfb1ae804b4b7ce973be75cf2e41272185", + "version" : "0.1.17" } }, { @@ -153,6 +198,15 @@ "revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336", "version" : "1.3.0" } + }, + { + "identity" : "swiftui-shimmer", + "kind" : "remoteSourceControl", + "location" : "https://github.com/markiv/SwiftUI-Shimmer.git", + "state" : { + "revision" : "0226e21f9bf355d40e07e5f5e1c33679d50e167f", + "version" : "1.5.1" + } } ], "version" : 3 diff --git a/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist b/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist index e2cd285..8ccc679 100644 --- a/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist @@ -4,10 +4,40 @@ SchemeUserState + AnyCodable (Playground) 1.xcscheme + + orderHint + 10 + + AnyCodable (Playground).xcscheme + + orderHint + 17 + + Associations (Playground).xcscheme + + orderHint + 13 + ChatMLX.xcscheme_^#shared#^_ orderHint - 3 + 0 + + MyPlayground (Playground).xcscheme + + orderHint + 14 + + Tour (Playground).xcscheme + + orderHint + 15 + + TransactionObserver (Playground).xcscheme + + orderHint + 16 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 0000000..3b44a2b Binary files /dev/null and b/ChatMLX/Assets.xcassets/MCP.imageset/MCP.png differ diff --git a/ChatMLX/Assets.xcassets/MLX.imageset/Contents.json b/ChatMLX/Assets.xcassets/MLX.imageset/Contents.json index 77f368d..bef4d7c 100644 --- a/ChatMLX/Assets.xcassets/MLX.imageset/Contents.json +++ b/ChatMLX/Assets.xcassets/MLX.imageset/Contents.json @@ -1,7 +1,7 @@ { "images" : [ { - "filename" : "1028322432.png", + "filename" : "MLX.png", "idiom" : "universal", "scale" : "1x" }, diff --git a/ChatMLX/Assets.xcassets/MLX.imageset/1028322432.png b/ChatMLX/Assets.xcassets/MLX.imageset/MLX.png similarity index 100% rename from ChatMLX/Assets.xcassets/MLX.imageset/1028322432.png rename to ChatMLX/Assets.xcassets/MLX.imageset/MLX.png diff --git a/ChatMLX/Assets.xcassets/clear.imageset/clear-l.svg b/ChatMLX/Assets.xcassets/clear.imageset/clear-l.svg deleted file mode 100644 index dbbf210..0000000 --- a/ChatMLX/Assets.xcassets/clear.imageset/clear-l.svg +++ /dev/null @@ -1 +0,0 @@ - \ 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 0000000..85aa4f2 Binary files /dev/null and b/ChatMLX/Assets.xcassets/huggingface.imageset/huggingface.png differ diff --git a/ChatMLX/Assets.xcassets/markdown.imageset/Contents.json b/ChatMLX/Assets.xcassets/markdown.imageset/Contents.json deleted file mode 100644 index c6d8d5a..0000000 --- a/ChatMLX/Assets.xcassets/markdown.imageset/Contents.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "images" : [ - { - "filename" : "markdown (2).svg", - "idiom" : "universal", - "scale" : "1x" - }, - { - "idiom" : "universal", - "scale" : "2x" - }, - { - "idiom" : "universal", - "scale" : "3x" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ChatMLX/Assets.xcassets/markdown.imageset/markdown (2).svg b/ChatMLX/Assets.xcassets/markdown.imageset/markdown (2).svg deleted file mode 100644 index 897c9c5..0000000 --- a/ChatMLX/Assets.xcassets/markdown.imageset/markdown (2).svg +++ /dev/null @@ -1 +0,0 @@ - \ 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 0000000..7a7fbe8 Binary files /dev/null and b/ChatMLX/Assets.xcassets/menubarIcon.imageset/menubarIcon@1x.png differ 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 0000000..8fe13c4 Binary files /dev/null and b/ChatMLX/Assets.xcassets/menubarIcon.imageset/menubarIcon@2x.png differ diff --git a/ChatMLX/Assets.xcassets/menubarIcon.imageset/menubarIcon@3x.png b/ChatMLX/Assets.xcassets/menubarIcon.imageset/menubarIcon@3x.png new file mode 100644 index 0000000..dc3d9a6 Binary files /dev/null and b/ChatMLX/Assets.xcassets/menubarIcon.imageset/menubarIcon@3x.png differ 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..c7bcdcd 100644 --- a/ChatMLX/ChatMLX.entitlements +++ b/ChatMLX/ChatMLX.entitlements @@ -2,11 +2,15 @@ - com.apple.security.network.client - + 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-only - + com.apple.security.files.user-selected.read-write + + com.apple.security.network.client + diff --git a/ChatMLX/ChatMLXApp.swift b/ChatMLX/ChatMLXApp.swift index 7148dc5..b9cd0e4 100644 --- a/ChatMLX/ChatMLXApp.swift +++ b/ChatMLX/ChatMLXApp.swift @@ -5,67 +5,36 @@ // Created by John Mai on 2024/8/3. // +import Conversation +import Utilities +import Database 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 appStore: AppStore = .init() + @State private var conversationStore: ConversationStore = .init() + @State private var settingsStore: SettingsStore = .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)") - } - } - } + .environment(appStore) + .environment(conversationStore) + .environment(\.locale, .init(identifier: language.id)) + .environment(\.appDatabase, .shared) } 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 - ) + .environment(appStore) + .environment(settingsStore) + .environment(\.locale, .init(identifier: language.id)) + .environment(\.appDatabase, .shared) } - .environment(\.managedObjectContext, persistenceController.container.viewContext) } } 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/Binding+Extensions.swift b/ChatMLX/Extensions/Binding+Extensions.swift deleted file mode 100644 index 3d058a1..0000000 --- a/ChatMLX/Extensions/Binding+Extensions.swift +++ /dev/null @@ -1,15 +0,0 @@ -// -// Binding+Extensions.swift -// ChatMLX -// -// Created by John Mai on 2024/10/2. -// - -import Foundation -import SwiftUI - -extension Binding { - func toUnwrapped(defaultValue: T) -> Binding where Value == T? { - Binding(get: { self.wrappedValue ?? defaultValue }, set: { self.wrappedValue = $0 }) - } -} 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/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..8be2020 --- /dev/null +++ b/Packages/Conversation/Package.swift @@ -0,0 +1,42 @@ +// 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: "Utilities", path: "../Utilities"), + .package(name: "Database", path: "../Database"), + .package(name: "UltraUI", path: "../UltraUI"), + .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. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "Conversation", + dependencies: [ + "Intelligence", + "UltraUI", + "Utilities", + "Database", + .product(name: "MarkdownUI", package: "swift-markdown-ui"), + .product(name: "Shimmer", package: "SwiftUI-Shimmer"), + ] + ), + .testTarget( + name: "ConversationTests", + dependencies: ["Conversation"] + ), + ] +) 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/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 new file mode 100644 index 0000000..cae7784 --- /dev/null +++ b/Packages/Conversation/Sources/Conversation/ConversationSidebarView.swift @@ -0,0 +1,111 @@ +// +// ConversationSidebarView.swift +// Conversation +// +// Created by John Mai on 2025/2/21. +// + +import Database +import Shimmer +import SwiftUI +import SwiftUIIntrospect +import UltraUI + +struct ConversationSidebarView: View { + var conversations: [Conversation] + @Binding var selectedConversation: Conversation? + + @Environment(ConversationStore.self) var conversationStore + + @State var proxy: ScrollViewProxy? + + 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() + 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)") + } + + } + } + + @ViewBuilder + func item(conversation: Conversation) -> some View { + Button(action: { + Task{ + do { + try await conversationStore.switchToConversation(conversation) + } catch { + print("Failed to switch to conversation: \(error)") + } + } + + }) { + VStack(alignment: .leading, spacing: 8) { + HStack { + Text(conversation.titleUnwrapped) + .font(.headline) + .lineLimit(1) + .help(conversation.titleUnwrapped) + + Spacer() + + Text(conversation.updatedAt.shortFormatted()) + .font(.caption) + .foregroundStyle(.secondary) + + } + + if let description = conversation.description { + Text(description) + .font(.subheadline) + .foregroundStyle(.secondary) + .lineLimit(2) + .truncationMode(.tail) + } + } + .padding(16) + } + .buttonStyle( + UltraSidebarButtonStyle(conversation.id == selectedConversation?.id) + ) + + } +} diff --git a/Packages/Conversation/Sources/Conversation/ConversationStore.swift b/Packages/Conversation/Sources/Conversation/ConversationStore.swift new file mode 100644 index 0000000..1f5d942 --- /dev/null +++ b/Packages/Conversation/Sources/Conversation/ConversationStore.swift @@ -0,0 +1,238 @@ +// +// ConversationStore.swift +// Conversation +// +// Created by John Mai on 2025/2/22. +// + +import Database +import Foundation +import GRDB +import Intelligence +import SwiftUI + +@MainActor +@Observable +public final class ConversationStore { + + public var conversations: [Conversation] = [] + + 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 new file mode 100644 index 0000000..1387b16 --- /dev/null +++ b/Packages/Conversation/Sources/Conversation/ConversationView.swift @@ -0,0 +1,69 @@ +// +// 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("") + + @Environment(ConversationStore.self) var conversationStore + + public init() {} + + public var body: some View { + @Bindable var conversationStore = conversationStore + + UltraNavigationSplitView(showDivider: conversationStore.selectedConversation != nil) { + ConversationSidebarView( + conversations: conversationStore.conversations, + selectedConversation: $conversationStore.selectedConversation + ) + } detail: { + VStack(spacing: .zero) { + + if let conversation = conversationStore.selectedConversation { + ConversationDetailView() + .frame(maxHeight: .infinity) + .ultraNavigationTitle(conversation.titleUnwrapped) + } else { + GreetingView() + .ultraNavigationTitle("") + } + + 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(minWidth: 580, minHeight: 360) + .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 new file mode 100644 index 0000000..f9cacf3 --- /dev/null +++ b/Packages/Conversation/Sources/Conversation/PromptEditorView.swift @@ -0,0 +1,151 @@ +// +// PromptEditor.swift +// Conversation +// +// Created by John Mai on 2025/2/23. +// + +import Utilities +import Database +import Intelligence +import STTextView +import SwiftUI +import UltraUI + +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] = [] + + @Environment(\.utlraRadius) var ultraRadius + + let font: NSFont = .preferredFont(forTextStyle: .title3) + let minHeight: CGFloat = 36 + let maxHeight: CGFloat = 200 + + var body: some View { + @Bindable var conversationStore = conversationStore + + VStack(spacing: .zero) { + TextareaView( + text: $conversationStore.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) + + Spacer() + + UltraPicker( + options: store.activeModels, + selection: $conversationStore.model + ) + + Button("Send", systemImage: "paperplane.fill") { + Task { + do { + try await conversationStore.send() + } catch { + print(error) + } + } + + } + .disabled(conversationStore.prompt.characters.isEmpty || conversationStore.model == nil) + .buttonStyle(.ultra) + } + } + .padding() + .background(utlraViewBackground) + .cornerRadius(ultraRadius) + .shadow() + .padding() + .task { + do { + try store.loadModels() + } 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/Database/.gitignore b/Packages/Database/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Packages/Database/.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/Database/Package.swift b/Packages/Database/Package.swift new file mode 100644 index 0000000..ba29c6a --- /dev/null +++ b/Packages/Database/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: "Database", + platforms: [.macOS(.v14)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "Database", + targets: ["Database"]), + ], + dependencies: [ + .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: "Database", + dependencies: [ + "Intelligence", + .product(name: "GRDB", package: "GRDB.swift") + ] + ), + .testTarget( + 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/Intelligence/.gitignore b/Packages/Intelligence/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/Packages/Intelligence/.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/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/Intelligence/Tests/IntelligenceTests/IntelligenceTests.swift b/Packages/Intelligence/Tests/IntelligenceTests/IntelligenceTests.swift new file mode 100644 index 0000000..aefc7b7 --- /dev/null +++ b/Packages/Intelligence/Tests/IntelligenceTests/IntelligenceTests.swift @@ -0,0 +1,6 @@ +import Testing +@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/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..4fc0f06 --- /dev/null +++ b/Packages/Settings/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: "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: "Utilities", path: "../Utilities"), + .package(name: "UltraUI", path: "../UltraUI"), + ], + 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: [ + "Utilities", + "UltraUI", + ] + ), + .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..170c797 --- /dev/null +++ b/Packages/Settings/Sources/Settings/AboutView.swift @@ -0,0 +1,14 @@ +// +// AboutView.swift +// Settings +// +// 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..91c1f6a --- /dev/null +++ b/Packages/Settings/Sources/Settings/DefaultConversationView.swift @@ -0,0 +1,14 @@ +// +// DefaultConversationView.swift +// Settings +// +// 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/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/ExperimentalFeaturesView.swift b/Packages/Settings/Sources/Settings/ExperimentalFeaturesView.swift new file mode 100644 index 0000000..ef46d16 --- /dev/null +++ b/Packages/Settings/Sources/Settings/ExperimentalFeaturesView.swift @@ -0,0 +1,14 @@ +// +// ExperimentalFeaturesView.swift +// Settings +// +// 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..d63c7ed --- /dev/null +++ b/Packages/Settings/Sources/Settings/GeneralView.swift @@ -0,0 +1,49 @@ +// +// GeneralView.swift +// Settings +// +// Created by John Mai on 2025/2/27. +// + +import Utilities +import Defaults +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..b404e1f --- /dev/null +++ b/Packages/Settings/Sources/Settings/HuggingFaceView.swift @@ -0,0 +1,127 @@ +// +// HuggingFaceView.swift +// Settings +// +// Created by John Mai on 2025/2/27. +// + +import Utilities +import Defaults +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/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/MLXCommunityView.swift b/Packages/Settings/Sources/Settings/MLXCommunityView.swift new file mode 100644 index 0000000..1f46d7d --- /dev/null +++ b/Packages/Settings/Sources/Settings/MLXCommunityView.swift @@ -0,0 +1,14 @@ +// +// MLXCommunityView.swift +// Settings +// +// 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/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/Settings/Sources/Settings/Models/SettingsTab.swift b/Packages/Settings/Sources/Settings/Models/SettingsTab.swift new file mode 100644 index 0000000..f223d71 --- /dev/null +++ b/Packages/Settings/Sources/Settings/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/Settings/Sources/Settings/ModelsView.swift b/Packages/Settings/Sources/Settings/ModelsView.swift new file mode 100644 index 0000000..3c6eac1 --- /dev/null +++ b/Packages/Settings/Sources/Settings/ModelsView.swift @@ -0,0 +1,59 @@ +// +// ModelsView.swift +// 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 { + 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 new file mode 100644 index 0000000..c09b5ca --- /dev/null +++ b/Packages/Settings/Sources/Settings/ProvidersView.swift @@ -0,0 +1,79 @@ +// +// ProvidersView.swift +// 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 { + 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 new file mode 100644 index 0000000..b2a9aab --- /dev/null +++ b/Packages/Settings/Sources/Settings/SearchView.swift @@ -0,0 +1,14 @@ +// +// SearchView.swift +// Settings +// +// 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..6b20c1d --- /dev/null +++ b/Packages/Settings/Sources/Settings/SettingsSidebarView.swift @@ -0,0 +1,77 @@ +// +// SettingsSidebarView.swift +// Settings +// +// Created by John Mai on 2025/2/27. +// + +import Utilities +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..a50b423 --- /dev/null +++ b/Packages/Settings/Sources/Settings/SettingsStore.swift @@ -0,0 +1,41 @@ +// +// SettingsStore.swift +// Settings +// +// Created by John Mai on 2025/2/27. +// + +import Utilities +import Foundation +import HuggingfaceHub + +@MainActor +@Observable +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 new file mode 100644 index 0000000..be9cfe3 --- /dev/null +++ b/Packages/Settings/Sources/Settings/SettingsView.swift @@ -0,0 +1,79 @@ +// +// SettingsView.swift +// Settings +// +// Created by John Mai on 2025/2/27. +// + +import Utilities +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: 720, 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() + } + } +} + +#Preview { + SettingsView() + .background(Color.black.opacity(0.5)) +} 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..b95b292 --- /dev/null +++ b/Packages/UltraUI/Package.swift @@ -0,0 +1,37 @@ +// 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"), + .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. + // Targets can depend on other targets in this package and products from dependencies. + .target( + name: "UltraUI", + dependencies: [ + "STTextView", + "CompactSlider", + .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..f0efb13 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Extensions/EnvironmentValues+Extensions.swift @@ -0,0 +1,25 @@ +// +// 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 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) + + @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..3b8477f --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Extensions/NSWindow+Extensions.swift @@ -0,0 +1,44 @@ +// +// NSWindow+Extensions.swift +// UltraUI +// +// Created by John Mai on 2025/2/21. +// + +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 +) -> OSStatus + +typealias CGSConnection = UInt32 +typealias CGWindowID = Int + +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..bc35ad9 --- /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: 8) + } + + 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..56b9131 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Modifiers/UltraWindowStyleViewModifier.swift @@ -0,0 +1,35 @@ +// +// 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.backgroundColor = .clear + 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..93a0e34 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/Styles/ButtonStyles/UltraButtonStyle.swift @@ -0,0 +1,135 @@ +// +// 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 + @Environment(\.isEnabled) private var isEnabled + + @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 + .foregroundStyle(isEnabled ? .white : .gray) + .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..1400303 --- /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 { + public static var ultraPlain: some ButtonStyle { + UltraPlainButtonStyle() + } +} + +public struct UltraPlainButtonStyle: ButtonStyle { + @Environment(\.utlraSecondaryViewBackground) var utlraSecondaryViewBackground + + @State private var isHovered = false + + public 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..78d6979 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraNavigationSplitView.swift @@ -0,0 +1,318 @@ +// +// 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() + .safeAreaInset(edge: .top, alignment: .trailing, spacing: 0) { + HStack { + leadingToolbarItems() + } + .frame(height: 52) + .padding(.horizontal) + } + .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 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) { + Spacer() + + HStack { + if !isSidebarVisible { + Spacer() + .frame(width: 80) + + leadingToolbarItems() + } + + 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/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/UltraOptionsPanel.swift b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsPanel.swift new file mode 100644 index 0000000..d2b41cb --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsPanel.swift @@ -0,0 +1,13 @@ +// +// UltraOptionsPanel.swift +// UltraUI +// +// Created by John Mai on 2025/2/26. +// + +import AppKit + +class UltraOptionsPanel: NSPanel { + 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 new file mode 100644 index 0000000..fc78590 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraOptionsWindowController.swift @@ -0,0 +1,189 @@ +// +// 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.isOpaque = false + window.backgroundColor = .clear + window.hasShadow = true + + self.init(window: window) + } + + deinit { + Task { [self] in + await stopEventMonitoring() + } + } + + 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 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() { + stopEventMonitoring() + self.window?.close() + } + + private func startEventMonitoring() { + stopEventMonitoring() + + 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: mouseEvents) { + [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 { [weak self] in + self?.closeWindow() + } + } + } + + func stopEventMonitoring() { + if let globalEventMonitor { + NSEvent.removeMonitor(globalEventMonitor) + self.globalEventMonitor = nil + } + + 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 new file mode 100644 index 0000000..0982c56 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraPicker/UltraPicker.swift @@ -0,0 +1,60 @@ +// +// UltraPicker.swift +// UltraUI +// +// Created by John Mai on 2025/2/26. +// + +import SwiftUI + +public struct UltraPicker: View { + 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) + .truncationMode(.head) + Image(systemName: "chevron.down") + } + } + .onGeometryChange(for: CGRect.self) { + $0.frame(in: .global) + } action: { + rect = $0 + } + .buttonStyle(.ultraPlain) + } + + private func showOptionsPanel() { + if controller == nil { + controller = UltraOptionsWindowController( + NSHostingView( + rootView: UltraOptions( + selection: $selection, + options: options, + onSelection: { _ in + controller?.closeWindow() + }) + ) + ) + } + + 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..c88a640 --- /dev/null +++ b/Packages/UltraUI/Sources/UltraUI/UltraSection.swift @@ -0,0 +1,156 @@ +// +// 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(\.ultraViewBackground) 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()) + } +} + +extension UltraSection where Header == EmptyView, Content: View, Footer == EmptyView { + public init(@ViewBuilder content: () -> Content) { + self.init(header: EmptyView(), content: content(), footer: EmptyView()) + } +} + +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()) + } +} + +extension UltraSection where Header: View, Content: View, Footer == EmptyView { + 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..2c29b60 --- /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(\.ultraViewBackground) 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. +} 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/Utilities/Package.swift b/Packages/Utilities/Package.swift new file mode 100644 index 0000000..214c260 --- /dev/null +++ b/Packages/Utilities/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: "Utilities", + platforms: [.macOS(.v14)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "Utilities", + targets: ["Utilities"]), + ], + dependencies: [ + .package(name: "Intelligence", path: "../Intelligence"), + .package(name: "HuggingfaceHub", path: "../../../../maiqingqiang/HuggingfaceHub"), + .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: "Utilities", + dependencies: [ + "Intelligence", + "HuggingfaceHub", + "Defaults" + ] + ), + .testTarget( + 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/Utilities/Sources/Utilities/Extensions/Date+Extensions.swift b/Packages/Utilities/Sources/Utilities/Extensions/Date+Extensions.swift new file mode 100644 index 0000000..242fd94 --- /dev/null +++ b/Packages/Utilities/Sources/Utilities/Extensions/Date+Extensions.swift @@ -0,0 +1,32 @@ +// +// Date+Extensions.swift +// UltraUI +// +// Created by John Mai on 2025/2/22. +// + +import Foundation + +extension Date { + public func shortFormatted() -> 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/Utilities/Sources/Utilities/Extensions/Defaults+Extensions.swift b/Packages/Utilities/Sources/Utilities/Extensions/Defaults+Extensions.swift new file mode 100644 index 0000000..068ecba --- /dev/null +++ b/Packages/Utilities/Sources/Utilities/Extensions/Defaults+Extensions.swift @@ -0,0 +1,29 @@ +// +// Defaults+Extensions.swift +// Utilities +// +// Created by John Mai on 2025/3/1. +// + +import Defaults +import Foundation + +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) + + // 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/Utilities/Sources/Utilities/MarkdownMetadata.swift b/Packages/Utilities/Sources/Utilities/MarkdownMetadata.swift new file mode 100644 index 0000000..ebf74ff --- /dev/null +++ b/Packages/Utilities/Sources/Utilities/MarkdownMetadata.swift @@ -0,0 +1,81 @@ +// +// MarkdownMetadata.swift +// Utilities +// +// 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/Utilities/Sources/Utilities/Models/HuggingFaceEndpoint.swift b/Packages/Utilities/Sources/Utilities/Models/HuggingFaceEndpoint.swift new file mode 100644 index 0000000..35b858b --- /dev/null +++ b/Packages/Utilities/Sources/Utilities/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/Utilities/Sources/Utilities/Models/Language.swift b/Packages/Utilities/Sources/Utilities/Models/Language.swift new file mode 100644 index 0000000..7edc250 --- /dev/null +++ b/Packages/Utilities/Sources/Utilities/Models/Language.swift @@ -0,0 +1,107 @@ +// +// Language.swift +// Utilities +// +// 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/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/Utilities/Tests/UtilitiesTests/UtilitiesTests.swift b/Packages/Utilities/Tests/UtilitiesTests/UtilitiesTests.swift new file mode 100644 index 0000000..1dae078 --- /dev/null +++ b/Packages/Utilities/Tests/UtilitiesTests/UtilitiesTests.swift @@ -0,0 +1,6 @@ +import Testing +@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.