diff --git a/.swift-format b/.swift-format index 962ead4..a4334f0 100644 --- a/.swift-format +++ b/.swift-format @@ -3,5 +3,7 @@ "indentation": { "spaces": 4 }, + "lineLength": 120, + "multiElementCollectionTrailingCommas": true, "spacesAroundRangeFormationOperators": true, } \ No newline at end of file diff --git a/ChatMLX.xcodeproj/project.pbxproj b/ChatMLX.xcodeproj/project.pbxproj index 2e9f4fa..d9f3c11 100644 --- a/ChatMLX.xcodeproj/project.pbxproj +++ b/ChatMLX.xcodeproj/project.pbxproj @@ -3,11 +3,22 @@ archiveVersion = 1; classes = { }; - objectVersion = 56; + objectVersion = 60; objects = { /* Begin PBXBuildFile section */ + 5201638B2CBC2D8D00666E82 /* V2MigrationPolicy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5201638A2CBC2D8C00666E82 /* V2MigrationPolicy.swift */; }; + 520163972CBD6B4B00666E82 /* Conversation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520163962CBD6B4900666E82 /* Conversation.swift */; }; + 5201639B2CBD6BC400666E82 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5201639A2CBD6BC000666E82 /* Message.swift */; }; + 520163A92CBD6CCB00666E82 /* ModelInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520163A82CBD6CCA00666E82 /* ModelInfo.swift */; }; + 520163AB2CBD715900666E82 /* ProviderModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520163AA2CBD715900666E82 /* ProviderModel.swift */; }; + 520163AD2CBD876100666E82 /* Common.swift in Sources */ = {isa = PBXBuildFile; fileRef = 520163AC2CBD875500666E82 /* Common.swift */; }; + 5207D8D42CC6846F008588FA /* Provider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5207D8D32CC6846F008588FA /* Provider.swift */; }; + 5207D8D62CC68BAC008588FA /* DefaultProviderPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5207D8D52CC68BAA008588FA /* DefaultProviderPicker.swift */; }; + 52212ABA2CDFA96100A54AA2 /* OpenAIServiceTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52212AB92CDFA95D00A54AA2 /* OpenAIServiceTests.swift */; }; 523D8C512C9C7FCC0092791C /* Transformers in Frameworks */ = {isa = PBXBuildFile; productRef = 523D8C502C9C7FCC0092791C /* Transformers */; }; + 525C50D72CD79EFE00F48479 /* Theme.swift in Sources */ = {isa = PBXBuildFile; fileRef = 525C50D62CD79EFD00F48479 /* Theme.swift */; }; + 525C50DF2CD7B90400F48479 /* Repository.swift in Sources */ = {isa = PBXBuildFile; fileRef = 525C50DE2CD7B90200F48479 /* Repository.swift */; }; 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 */; }; @@ -16,7 +27,6 @@ 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 */; }; @@ -31,7 +41,7 @@ 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 */; }; + 5266764C2C85F903001EF113 /* ConversationSidebarItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5266760B2C85F903001EF113 /* ConversationSidebarItemView.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 */; }; @@ -59,43 +69,93 @@ 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 */; }; + 5270B9D82CDFBB550029E3A4 /* Conversation+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9D62CDFBB550029E3A4 /* Conversation+CoreDataClass.swift */; }; + 5270B9D92CDFBB550029E3A4 /* Conversation+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9D72CDFBB550029E3A4 /* Conversation+CoreDataProperties.swift */; }; + 5270B9DC2CDFD2090029E3A4 /* ChatService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9DB2CDFD1FD0029E3A4 /* ChatService.swift */; }; + 5270B9DE2CDFD2C40029E3A4 /* MLXChatStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9DD2CDFD2BA0029E3A4 /* MLXChatStrategy.swift */; }; + 5270B9E02CDFD2F20029E3A4 /* OpenAIChatStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9DF2CDFD2E80029E3A4 /* OpenAIChatStrategy.swift */; }; + 5270B9E22CDFD42B0029E3A4 /* ChatStrategy.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9E12CDFD42B0029E3A4 /* ChatStrategy.swift */; }; + 5270B9E62CE0956C0029E3A4 /* SettingsStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9E52CE095600029E3A4 /* SettingsStore.swift */; }; + 5270B9E82CE0AB6C0029E3A4 /* DownloadStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9E72CE0AB620029E3A4 /* DownloadStore.swift */; }; + 5270B9EA2CE0D0570029E3A4 /* MessageBoxView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9E92CE0D0510029E3A4 /* MessageBoxView.swift */; }; + 5270B9EC2CE0D2100029E3A4 /* EditorToolbarView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9EB2CE0D20B0029E3A4 /* EditorToolbarView.swift */; }; + 5270B9EE2CE0DD780029E3A4 /* EditorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9ED2CE0DD730029E3A4 /* EditorView.swift */; }; + 5270B9F22CE0E6A70029E3A4 /* UserMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9F12CE0E6A30029E3A4 /* UserMessageView.swift */; }; + 5270B9F42CE0E7BF0029E3A4 /* AssistantMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9F32CE0E7BB0029E3A4 /* AssistantMessageView.swift */; }; + 5270B9F92CE0FBEB0029E3A4 /* HuggingFaceService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5270B9F82CE0FBDE0029E3A4 /* HuggingFaceService.swift */; }; + 527821592CB817A300638477 /* ModelManagerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 527821582CB8179700638477 /* ModelManagerViewModel.swift */; }; + 5278215B2CB81FEB00638477 /* MarkdownMetadata.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5278215A2CB81FEA00638477 /* MarkdownMetadata.swift */; }; + 527821A52CB821AC00638477 /* ChatMLXTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 527821A32CB821AC00638477 /* ChatMLXTests.swift */; }; + 527821A72CB821B800638477 /* MarkdownMetadataTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 527821A62CB821B800638477 /* MarkdownMetadataTests.swift */; }; 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 */; }; + 528D83192CAD491900163AAB /* ChatMLX2.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = 528D83172CAD491900163AAB /* ChatMLX2.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 */; }; + 528D83372CADB64600163AAB /* ConversationViewModelOld.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528D83362CADB64300163AAB /* ConversationViewModelOld.swift */; }; 528D83392CAE51EC00163AAB /* Role.swift in Sources */ = {isa = PBXBuildFile; fileRef = 528D83382CAE51EC00163AAB /* Role.swift */; }; 528DBE2F2C9C86FB004CDD88 /* Transformers in Frameworks */ = {isa = PBXBuildFile; productRef = 528DBE2E2C9C86FB004CDD88 /* Transformers */; }; + 52977D2B2CCBCCEC00DD4D2F /* Logger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52977D2A2CCBCCEC00DD4D2F /* Logger.swift */; }; + 52977E102CCCF22E00DD4D2F /* MLXProviderLabelView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52977E0F2CCCF21E00DD4D2F /* MLXProviderLabelView.swift */; }; + 52977E122CCCF81300DD4D2F /* MLXProviderContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52977E112CCCF7FE00DD4D2F /* MLXProviderContentView.swift */; }; + 52977E142CCCFF4100DD4D2F /* MLXProviderModelItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52977E132CCCFF3A00DD4D2F /* MLXProviderModelItemView.swift */; }; + 52977E162CCD0F7800DD4D2F /* LabeledToggle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52977E152CCD0F6D00DD4D2F /* LabeledToggle.swift */; }; + 52977E182CCD1AC000DD4D2F /* GPUMemorySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52977E172CCD1AB800DD4D2F /* GPUMemorySettingsView.swift */; }; + 52977E1B2CCD296F00DD4D2F /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52977E1A2CCD296B00DD4D2F /* ErrorView.swift */; }; + 52977E1D2CCD2F1900DD4D2F /* ErrorWrapper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52977E1C2CCD2F1800DD4D2F /* ErrorWrapper.swift */; }; + 52977E202CCD4D3400DD4D2F /* Errors.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52977E1F2CCD4D3000DD4D2F /* Errors.swift */; }; + 52977E242CCDF43E00DD4D2F /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52977E232CCDF43C00DD4D2F /* Constants.swift */; }; + 52A021CF2CB565FC007893F7 /* AnimatedGradientFill.metal in Sources */ = {isa = PBXBuildFile; fileRef = 52A021CE2CB565FC007893F7 /* AnimatedGradientFill.metal */; }; + 52A265F02CBAE63E004F6DD3 /* BaseProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A265EF2CBAE638004F6DD3 /* BaseProvider.swift */; }; + 52A265F32CBAEA9E004F6DD3 /* MLXProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A265F22CBAEA97004F6DD3 /* MLXProvider.swift */; }; + 52A265F52CBBB3C0004F6DD3 /* ProviderFactory.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A265F42CBBB3B8004F6DD3 /* ProviderFactory.swift */; }; + 52A265FA2CBBC5D5004F6DD3 /* ProviderFactoryTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A265F92CBBC5D5004F6DD3 /* ProviderFactoryTests.swift */; }; 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 */; }; + 52A7C83F2CDE78860072D691 /* ConversationStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A7C83E2CDE78830072D691 /* ConversationStore.swift */; }; + 52A7C8502CDF4C390072D691 /* ModelInfo+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A7C84E2CDF4C390072D691 /* ModelInfo+CoreDataClass.swift */; }; + 52A7C8512CDF4C390072D691 /* Message+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A7C84D2CDF4C390072D691 /* Message+CoreDataProperties.swift */; }; + 52A7C8522CDF4C390072D691 /* ModelInfo+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A7C84F2CDF4C390072D691 /* ModelInfo+CoreDataProperties.swift */; }; + 52A7C8542CDF4C390072D691 /* Message+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A7C84C2CDF4C390072D691 /* Message+CoreDataClass.swift */; }; + 52A7C8582CDF6D3E0072D691 /* AppleIntelligenceEffectViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A7C8572CDF6D3C0072D691 /* AppleIntelligenceEffectViewModifier.swift */; }; + 52A7C85A2CDF92FC0072D691 /* Model.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A7C8592CDF92F70072D691 /* Model.swift */; }; + 52A7C85C2CDF94640072D691 /* ModelStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A7C85B2CDF945F0072D691 /* ModelStore.swift */; }; + 52A7C85E2CDF99DA0072D691 /* MLXService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A7C85D2CDF99D40072D691 /* MLXService.swift */; }; + 52A7C8602CDFA3650072D691 /* OpenAIService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52A7C85F2CDFA3600072D691 /* OpenAIService.swift */; }; + 52B0532A2CAFDFB000E8DDBA /* ProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B053292CAFDFAF00E8DDBA /* ProviderView.swift */; }; + 52B0532C2CAFEFD800E8DDBA /* Color+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B0532B2CAFEFD000E8DDBA /* Color+Extensions.swift */; }; + 52B0534D2CB02C4600E8DDBA /* ModelManagerView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B0534C2CB02C3E00E8DDBA /* ModelManagerView.swift */; }; + 52B053502CB02CF900E8DDBA /* MLXProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B0534F2CB02CF100E8DDBA /* MLXProviderView.swift */; }; + 52B053522CB0385800E8DDBA /* OpenAIProviderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B053512CB0384E00E8DDBA /* OpenAIProviderView.swift */; }; + 52B053542CB0F5B300E8DDBA /* LabeledContentStyle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B053532CB0F5AA00E8DDBA /* LabeledContentStyle+Extensions.swift */; }; + 52B053572CB0F8EE00E8DDBA /* Binding+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B053562CB0F8EE00E8DDBA /* Binding+Extensions.swift */; }; 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 */; }; + 52B0C89B2D662B4800BDC5F3 /* HuggingfaceHubService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B0C89A2D662B3200BDC5F3 /* HuggingfaceHubService.swift */; }; + 52B0C89E2D662BCE00BDC5F3 /* HuggingfaceHub in Frameworks */ = {isa = PBXBuildFile; productRef = 52B0C89D2D662BCE00BDC5F3 /* HuggingfaceHub */; }; + 52B9BD072CBE94BA0086C013 /* Bundle+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52B9BD062CBE94B80086C013 /* Bundle+Extensions.swift */; }; + 52CDC3D22CC0208100627147 /* OpenAIProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52CDC3D12CC0207300627147 /* OpenAIProvider.swift */; }; + 52CDC3D52CC020F500627147 /* OpenAI in Frameworks */ = {isa = PBXBuildFile; productRef = 52CDC3D42CC020F500627147 /* OpenAI */; }; + 52CDC3DA2CC03B4A00627147 /* CoreDataEvolution in Frameworks */ = {isa = PBXBuildFile; productRef = 52CDC3D92CC03B4A00627147 /* CoreDataEvolution */; }; 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 */; }; + 52EC64942CC3DC0E00A08069 /* NSManagedObjectContext+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EC64932CC3DC0800A08069 /* NSManagedObjectContext+Extensions.swift */; }; + 52EC64AD2CC4C38600A08069 /* ModelPicker.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EC64AC2CC4C37C00A08069 /* ModelPicker.swift */; }; + 52EC64B12CC531A300A08069 /* ModelType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 52EC64B02CC531A300A08069 /* ModelType.swift */; }; 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 */; }; @@ -104,7 +164,29 @@ 52FD80CA2C8F5E42006C50F1 /* MNIST in Frameworks */ = {isa = PBXBuildFile; productRef = 52FD80C92C8F5E42006C50F1 /* MNIST */; }; /* End PBXBuildFile section */ +/* Begin PBXContainerItemProxy section */ + 5278219E2CB821A600638477 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 5266753A2C85EDCB001EF113 /* Project object */; + proxyType = 1; + remoteGlobalIDString = 526675412C85EDCB001EF113; + remoteInfo = ChatMLX; + }; +/* End PBXContainerItemProxy section */ + /* Begin PBXFileReference section */ + 520163832CBC1C9F00666E82 /* ChatMLX 2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = "ChatMLX 2.xcdatamodel"; sourceTree = ""; }; + 5201638A2CBC2D8C00666E82 /* V2MigrationPolicy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = V2MigrationPolicy.swift; sourceTree = ""; }; + 520163962CBD6B4900666E82 /* Conversation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Conversation.swift; sourceTree = ""; }; + 5201639A2CBD6BC000666E82 /* Message.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Message.swift; sourceTree = ""; }; + 520163A82CBD6CCA00666E82 /* ModelInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelInfo.swift; sourceTree = ""; }; + 520163AA2CBD715900666E82 /* ProviderModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderModel.swift; sourceTree = ""; }; + 520163AC2CBD875500666E82 /* Common.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Common.swift; sourceTree = ""; }; + 5207D8D32CC6846F008588FA /* Provider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Provider.swift; sourceTree = ""; }; + 5207D8D52CC68BAA008588FA /* DefaultProviderPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DefaultProviderPicker.swift; sourceTree = ""; }; + 52212AB92CDFA95D00A54AA2 /* OpenAIServiceTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAIServiceTests.swift; sourceTree = ""; }; + 525C50D62CD79EFD00F48479 /* Theme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Theme.swift; sourceTree = ""; }; + 525C50DE2CD7B90200F48479 /* Repository.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Repository.swift; sourceTree = ""; }; 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 = ""; }; @@ -121,7 +203,7 @@ 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 = ""; }; + 5266760B2C85F903001EF113 /* ConversationSidebarItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConversationSidebarItemView.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 = ""; }; @@ -149,37 +231,84 @@ 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 = ""; }; + 5270B9D62CDFBB550029E3A4 /* Conversation+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Conversation+CoreDataClass.swift"; sourceTree = ""; }; + 5270B9D72CDFBB550029E3A4 /* Conversation+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Conversation+CoreDataProperties.swift"; sourceTree = ""; }; + 5270B9DB2CDFD1FD0029E3A4 /* ChatService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatService.swift; sourceTree = ""; }; + 5270B9DD2CDFD2BA0029E3A4 /* MLXChatStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLXChatStrategy.swift; sourceTree = ""; }; + 5270B9DF2CDFD2E80029E3A4 /* OpenAIChatStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAIChatStrategy.swift; sourceTree = ""; }; + 5270B9E12CDFD42B0029E3A4 /* ChatStrategy.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatStrategy.swift; sourceTree = ""; }; + 5270B9E52CE095600029E3A4 /* SettingsStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsStore.swift; sourceTree = ""; }; + 5270B9E72CE0AB620029E3A4 /* DownloadStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DownloadStore.swift; sourceTree = ""; }; + 5270B9E92CE0D0510029E3A4 /* MessageBoxView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MessageBoxView.swift; sourceTree = ""; }; + 5270B9EB2CE0D20B0029E3A4 /* EditorToolbarView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorToolbarView.swift; sourceTree = ""; }; + 5270B9ED2CE0DD730029E3A4 /* EditorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorView.swift; sourceTree = ""; }; + 5270B9F12CE0E6A30029E3A4 /* UserMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserMessageView.swift; sourceTree = ""; }; + 5270B9F32CE0E7BB0029E3A4 /* AssistantMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AssistantMessageView.swift; sourceTree = ""; }; + 5270B9F82CE0FBDE0029E3A4 /* HuggingFaceService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HuggingFaceService.swift; sourceTree = ""; }; + 527821582CB8179700638477 /* ModelManagerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelManagerViewModel.swift; sourceTree = ""; }; + 5278215A2CB81FEA00638477 /* MarkdownMetadata.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownMetadata.swift; sourceTree = ""; }; + 5278219A2CB821A600638477 /* ChatMLXTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = ChatMLXTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 527821A32CB821AC00638477 /* ChatMLXTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatMLXTests.swift; sourceTree = ""; }; + 527821A62CB821B800638477 /* MarkdownMetadataTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownMetadataTests.swift; 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 = ""; }; + 528D83182CAD491900163AAB /* ChatMLX2.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = ChatMLX2.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 = ""; }; + 528D83362CADB64300163AAB /* ConversationViewModelOld.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationViewModelOld.swift; sourceTree = ""; }; 528D83382CAE51EC00163AAB /* Role.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Role.swift; sourceTree = ""; }; + 52977D2A2CCBCCEC00DD4D2F /* Logger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Logger.swift; sourceTree = ""; }; + 52977E0F2CCCF21E00DD4D2F /* MLXProviderLabelView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLXProviderLabelView.swift; sourceTree = ""; }; + 52977E112CCCF7FE00DD4D2F /* MLXProviderContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLXProviderContentView.swift; sourceTree = ""; }; + 52977E132CCCFF3A00DD4D2F /* MLXProviderModelItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLXProviderModelItemView.swift; sourceTree = ""; }; + 52977E152CCD0F6D00DD4D2F /* LabeledToggle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LabeledToggle.swift; sourceTree = ""; }; + 52977E172CCD1AB800DD4D2F /* GPUMemorySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GPUMemorySettingsView.swift; sourceTree = ""; }; + 52977E1A2CCD296B00DD4D2F /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; + 52977E1C2CCD2F1800DD4D2F /* ErrorWrapper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorWrapper.swift; sourceTree = ""; }; + 52977E1F2CCD4D3000DD4D2F /* Errors.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Errors.swift; sourceTree = ""; }; + 52977E232CCDF43C00DD4D2F /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = ""; }; + 52A021CE2CB565FC007893F7 /* AnimatedGradientFill.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = AnimatedGradientFill.metal; sourceTree = ""; }; + 52A265EF2CBAE638004F6DD3 /* BaseProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseProvider.swift; sourceTree = ""; }; + 52A265F22CBAEA97004F6DD3 /* MLXProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLXProvider.swift; sourceTree = ""; }; + 52A265F42CBBB3B8004F6DD3 /* ProviderFactory.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderFactory.swift; sourceTree = ""; }; + 52A265F92CBBC5D5004F6DD3 /* ProviderFactoryTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderFactoryTests.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 = ""; }; + 52A7C83E2CDE78830072D691 /* ConversationStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationStore.swift; sourceTree = ""; }; + 52A7C84C2CDF4C390072D691 /* Message+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+CoreDataClass.swift"; sourceTree = ""; }; + 52A7C84D2CDF4C390072D691 /* Message+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Message+CoreDataProperties.swift"; sourceTree = ""; }; + 52A7C84E2CDF4C390072D691 /* ModelInfo+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ModelInfo+CoreDataClass.swift"; sourceTree = ""; }; + 52A7C84F2CDF4C390072D691 /* ModelInfo+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ModelInfo+CoreDataProperties.swift"; sourceTree = ""; }; + 52A7C8572CDF6D3C0072D691 /* AppleIntelligenceEffectViewModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppleIntelligenceEffectViewModifier.swift; sourceTree = ""; }; + 52A7C8592CDF92F70072D691 /* Model.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Model.swift; sourceTree = ""; }; + 52A7C85B2CDF945F0072D691 /* ModelStore.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelStore.swift; sourceTree = ""; }; + 52A7C85D2CDF99D40072D691 /* MLXService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLXService.swift; sourceTree = ""; }; + 52A7C85F2CDFA3600072D691 /* OpenAIService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAIService.swift; sourceTree = ""; }; + 52B053292CAFDFAF00E8DDBA /* ProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProviderView.swift; sourceTree = ""; }; + 52B0532B2CAFEFD000E8DDBA /* Color+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color+Extensions.swift"; sourceTree = ""; }; + 52B0534C2CB02C3E00E8DDBA /* ModelManagerView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelManagerView.swift; sourceTree = ""; }; + 52B0534F2CB02CF100E8DDBA /* MLXProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MLXProviderView.swift; sourceTree = ""; }; + 52B053512CB0384E00E8DDBA /* OpenAIProviderView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAIProviderView.swift; sourceTree = ""; }; + 52B053532CB0F5AA00E8DDBA /* LabeledContentStyle+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "LabeledContentStyle+Extensions.swift"; sourceTree = ""; }; + 52B053562CB0F8EE00E8DDBA /* Binding+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Binding+Extensions.swift"; 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 = ""; }; + 52B0C89A2D662B3200BDC5F3 /* HuggingfaceHubService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HuggingfaceHubService.swift; sourceTree = ""; }; + 52B9BD062CBE94B80086C013 /* Bundle+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Bundle+Extensions.swift"; sourceTree = ""; }; + 52CDC3D12CC0207300627147 /* OpenAIProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenAIProvider.swift; sourceTree = ""; }; + 52EC64932CC3DC0800A08069 /* NSManagedObjectContext+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext+Extensions.swift"; sourceTree = ""; }; + 52EC64AC2CC4C37C00A08069 /* ModelPicker.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelPicker.swift; sourceTree = ""; }; + 52EC64B02CC531A300A08069 /* ModelType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ModelType.swift; sourceTree = ""; }; /* End PBXFileReference section */ /* Begin PBXFrameworksBuildPhase section */ @@ -197,29 +326,73 @@ 52FD80BE2C8F21C2006C50F1 /* LLM in Frameworks */, 5266757D2C85F54D001EF113 /* SwiftUIIntrospect in Frameworks */, 52FD80C52C8F288A006C50F1 /* MNIST in Frameworks */, + 52CDC3D52CC020F500627147 /* OpenAI in Frameworks */, 52E50B2B2C8DF111005A89DE /* MNIST in Frameworks */, 5266757A2C85F487001EF113 /* MarkdownUI in Frameworks */, 526675742C85F1F9001EF113 /* Splash in Frameworks */, 52FD80C82C8F5E42006C50F1 /* LLM in Frameworks */, 526675682C85EFDF001EF113 /* CompactSlider in Frameworks */, + 52B0C89E2D662BCE00BDC5F3 /* HuggingfaceHub 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 */, + 52CDC3DA2CC03B4A00627147 /* CoreDataEvolution in Frameworks */, 52E50B292C8DF111005A89DE /* LLM in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; + 527821972CB821A600638477 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 5248415E2CD610B1006A38E5 /* Application */ = { + isa = PBXGroup; + children = ( + 526675452C85EDCB001EF113 /* ChatMLXApp.swift */, + ); + path = Application; + sourceTree = ""; + }; + 5248415F2CD61225006A38E5 /* Configuration */ = { + isa = PBXGroup; + children = ( + 5266754E2C85EDCC001EF113 /* ChatMLX.entitlements */, + 528D83172CAD491900163AAB /* ChatMLX2.xcdatamodeld */, + ); + path = Configuration; + sourceTree = ""; + }; + 525C50D52CD79EDA00F48479 /* Theme */ = { + isa = PBXGroup; + children = ( + 525C50D62CD79EFD00F48479 /* Theme.swift */, + ); + path = Theme; + sourceTree = ""; + }; + 525C50DD2CD7B8D500F48479 /* Persistence */ = { + isa = PBXGroup; + children = ( + 525C50DE2CD7B90200F48479 /* Repository.swift */, + 528D831B2CAD49E600163AAB /* PersistenceController.swift */, + ); + path = Persistence; + sourceTree = ""; + }; 526675392C85EDCB001EF113 = { isa = PBXGroup; children = ( 526675442C85EDCB001EF113 /* ChatMLX */, + 527821A42CB821AC00638477 /* ChatMLXTests */, 526675432C85EDCB001EF113 /* Products */, ); sourceTree = ""; @@ -228,6 +401,7 @@ isa = PBXGroup; children = ( 526675422C85EDCB001EF113 /* ChatMLX.app */, + 5278219A2CB821A600638477 /* ChatMLXTests.xctest */, ); name = Products; sourceTree = ""; @@ -235,17 +409,12 @@ 526675442C85EDCB001EF113 /* ChatMLX */ = { isa = PBXGroup; children = ( - 5266754E2C85EDCC001EF113 /* ChatMLX.entitlements */, - 526676762C85F952001EF113 /* ChatMLXRelease.entitlements */, - 526675452C85EDCB001EF113 /* ChatMLXApp.swift */, - 526675FD2C85F903001EF113 /* Assets.xcassets */, - 526676772C85F9DA001EF113 /* Localizable.xcstrings */, - 526676092C85F903001EF113 /* Components */, - 5266763D2C85F903001EF113 /* Extensions */, + 5248415E2CD610B1006A38E5 /* Application */, + 5248415F2CD61225006A38E5 /* Configuration */, + 52977E1E2CCD4D0000DD4D2F /* Core */, 526676232C85F903001EF113 /* Features */, - 5266762F2C85F903001EF113 /* Models */, 5266754B2C85EDCC001EF113 /* Preview Content */, - 526676372C85F903001EF113 /* Utilities */, + 52977D262CCBC89200DD4D2F /* Resources */, ); path = ChatMLX; sourceTree = ""; @@ -270,11 +439,13 @@ 526676092C85F903001EF113 /* Components */ = { isa = PBXGroup; children = ( - 52B053942CB2B64500E8DDBA /* NoneInteractWindow.swift */, - 52B0538B2CB2B03200E8DDBA /* AppleIntelligenceEffect */, - 52A689F92CAECFDE0078CDF9 /* ErrorAlertModifier.swift */, - 526676002C85F903001EF113 /* SyntaxHighlighter */, + 52B053292CAFDFAF00E8DDBA /* ProviderView.swift */, + 52977E152CCD0F6D00DD4D2F /* LabeledToggle.swift */, + 5207D8D52CC68BAA008588FA /* DefaultProviderPicker.swift */, 526676012C85F903001EF113 /* EffectView.swift */, + 52A689F92CAECFDE0078CDF9 /* ErrorAlertModifier.swift */, + 52EC64AC2CC4C37C00A08069 /* ModelPicker.swift */, + 52B053942CB2B64500E8DDBA /* NoneInteractWindow.swift */, 526676022C85F903001EF113 /* UltramanMinimalistWindowModifier.swift */, 526676032C85F903001EF113 /* UltramanNavigationSplitView.swift */, 526676042C85F903001EF113 /* UltramanSecureField.swift */, @@ -282,6 +453,8 @@ 526676062C85F903001EF113 /* UltramanTextEditorWithPlaceholder.swift */, 526676072C85F903001EF113 /* UltramanTextField.swift */, 526676082C85F903001EF113 /* UltramanWindow.swift */, + 52B0538B2CB2B03200E8DDBA /* AppleIntelligenceEffect */, + 526676002C85F903001EF113 /* SyntaxHighlighter */, ); path = Components; sourceTree = ""; @@ -289,60 +462,55 @@ 526676112C85F903001EF113 /* Conversation */ = { isa = PBXGroup; children = ( - 528D83362CADB64300163AAB /* ConversationViewModel.swift */, - 5266760A2C85F903001EF113 /* ConversationDetailView.swift */, - 5266760B2C85F903001EF113 /* ConversationSidebarItem.swift */, - 5266760C2C85F903001EF113 /* ConversationSidebarView.swift */, + 52F5348C2CD7C1C5001F1F34 /* Sidebar */, + 52F5348B2CD7C1B6001F1F34 /* Detail */, 5266760D2C85F903001EF113 /* ConversationView.swift */, - 5266760E2C85F903001EF113 /* EmptyConversation.swift */, - 5266760F2C85F903001EF113 /* MessageBubbleView.swift */, - 526676102C85F903001EF113 /* RightSidebarView.swift */, ); path = Conversation; sourceTree = ""; }; - 526676142C85F903001EF113 /* DownloadManager */ = { + 526676142C85F903001EF113 /* Download Manager */ = { isa = PBXGroup; children = ( 526676122C85F903001EF113 /* DownloadManagerView.swift */, 526676132C85F903001EF113 /* DownloadTaskView.swift */, ); - path = DownloadManager; + path = "Download Manager"; sourceTree = ""; }; - 526676172C85F903001EF113 /* LocalModels */ = { + 526676172C85F903001EF113 /* Local Models */ = { isa = PBXGroup; children = ( 526676152C85F903001EF113 /* LocalModelItemView.swift */, 526676162C85F903001EF113 /* LocalModelsView.swift */, ); - path = LocalModels; + path = "Local Models"; sourceTree = ""; }; - 5266761A2C85F903001EF113 /* MLXCommunity */ = { + 5266761A2C85F903001EF113 /* MLX Community */ = { isa = PBXGroup; children = ( 526676182C85F903001EF113 /* MLXCommunityItemView.swift */, 526676192C85F903001EF113 /* MLXCommunityView.swift */, ); - path = MLXCommunity; + path = "MLX Community"; sourceTree = ""; }; 526676222C85F903001EF113 /* Settings */ = { isa = PBXGroup; children = ( - 52B053A52CB38E7F00E8DDBA /* ExperimentalFeaturesView.swift */, - 52A689F72CAE8DA00078CDF9 /* SettingsViewModel.swift */, - 526676142C85F903001EF113 /* DownloadManager */, - 526676172C85F903001EF113 /* LocalModels */, - 5266761A2C85F903001EF113 /* MLXCommunity */, 5266761B2C85F903001EF113 /* AboutView.swift */, 5266761C2C85F903001EF113 /* DefaultConversationView.swift */, + 52B053A52CB38E7F00E8DDBA /* ExperimentalFeaturesView.swift */, 5266761D2C85F903001EF113 /* GeneralView.swift */, 5266761E2C85F903001EF113 /* HuggingFaceView.swift */, 5266761F2C85F903001EF113 /* SettingsSidebarItemView.swift */, 526676202C85F903001EF113 /* SettingsSidebarView.swift */, 526676212C85F903001EF113 /* SettingsView.swift */, + 526676142C85F903001EF113 /* Download Manager */, + 526676172C85F903001EF113 /* Local Models */, + 5266761A2C85F903001EF113 /* MLX Community */, + 52B053282CAFDF8500E8DDBA /* Model Manager */, ); path = Settings; sourceTree = ""; @@ -350,6 +518,8 @@ 526676232C85F903001EF113 /* Features */ = { isa = PBXGroup; children = ( + 525C50D52CD79EDA00F48479 /* Theme */, + 527821572CB8176200638477 /* View Models */, 526676112C85F903001EF113 /* Conversation */, 526676222C85F903001EF113 /* Settings */, ); @@ -359,43 +529,34 @@ 5266762F2C85F903001EF113 /* Models */ = { isa = PBXGroup; children = ( + 52A7C8592CDF92F70072D691 /* Model.swift */, + 52B053A12CB2F6F900E8DDBA /* AppleIntelligenceEffectDisplay.swift */, 526676252C85F903001EF113 /* DisplayStyle.swift */, 526676262C85F903001EF113 /* DownloadTask.swift */, 526676272C85F903001EF113 /* Language.swift */, - 52B053A12CB2F6F900E8DDBA /* AppleIntelligenceEffectDisplay.swift */, + 52EC64B02CC531A300A08069 /* ModelType.swift */, + 5207D8D32CC6846F008588FA /* Provider.swift */, 526676282C85F903001EF113 /* LocalModel.swift */, 526676292C85F903001EF113 /* LocalModelGroup.swift */, - 528D83382CAE51EC00163AAB /* Role.swift */, + 520163AA2CBD715900666E82 /* ProviderModel.swift */, 5266762B2C85F903001EF113 /* RemoteModel.swift */, + 528D83382CAE51EC00163AAB /* Role.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 */, + 52B9BD092CBEB5960086C013 /* CoreData Models */, + 52B9BD082CBEB4E20086C013 /* Migrations */, ); path = Models; sourceTree = ""; }; - 526676332C85F903001EF113 /* Huggingface */ = { - isa = PBXGroup; - children = ( - 526676302C85F903001EF113 /* Downloader.swift */, - 526676312C85F903001EF113 /* Hub.swift */, - 526676322C85F903001EF113 /* HubApi.swift */, - ); - path = Huggingface; - sourceTree = ""; - }; 526676372C85F903001EF113 /* Utilities */ = { isa = PBXGroup; children = ( + 52977D2A2CCBCCEC00DD4D2F /* Logger.swift */, + 520163AC2CBD875500666E82 /* Common.swift */, + 5278215A2CB81FEA00638477 /* MarkdownMetadata.swift */, 526676342C85F903001EF113 /* LLMRunner.swift */, - 526676352C85F903001EF113 /* Logger.swift */, - 528D831B2CAD49E600163AAB /* PersistenceController.swift */, - 526676332C85F903001EF113 /* Huggingface */, ); path = Utilities; sourceTree = ""; @@ -403,6 +564,11 @@ 5266763D2C85F903001EF113 /* Extensions */ = { isa = PBXGroup; children = ( + 52B9BD062CBE94B80086C013 /* Bundle+Extensions.swift */, + 52EC64932CC3DC0800A08069 /* NSManagedObjectContext+Extensions.swift */, + 52B053562CB0F8EE00E8DDBA /* Binding+Extensions.swift */, + 52B053532CB0F5AA00E8DDBA /* LabeledContentStyle+Extensions.swift */, + 52B0532B2CAFEFD000E8DDBA /* Color+Extensions.swift */, 528D82252CABE19000163AAB /* Date+Extensions.swift */, 52A689F52CAE8AAB0078CDF9 /* TimeInterval+Extensions.swift */, 526676382C85F903001EF113 /* Defaults+Extensions.swift */, @@ -414,17 +580,215 @@ path = Extensions; sourceTree = ""; }; + 5270B9DA2CDFD1EF0029E3A4 /* ChatStrategies */ = { + isa = PBXGroup; + children = ( + 5270B9DF2CDFD2E80029E3A4 /* OpenAIChatStrategy.swift */, + 5270B9E12CDFD42B0029E3A4 /* ChatStrategy.swift */, + 5270B9DD2CDFD2BA0029E3A4 /* MLXChatStrategy.swift */, + ); + path = ChatStrategies; + sourceTree = ""; + }; + 527821572CB8176200638477 /* View Models */ = { + isa = PBXGroup; + children = ( + 527821582CB8179700638477 /* ModelManagerViewModel.swift */, + 528D83362CADB64300163AAB /* ConversationViewModelOld.swift */, + 52A689F72CAE8DA00078CDF9 /* SettingsViewModel.swift */, + ); + path = "View Models"; + sourceTree = ""; + }; + 527821A42CB821AC00638477 /* ChatMLXTests */ = { + isa = PBXGroup; + children = ( + 52212AB92CDFA95D00A54AA2 /* OpenAIServiceTests.swift */, + 527821A32CB821AC00638477 /* ChatMLXTests.swift */, + 527821A62CB821B800638477 /* MarkdownMetadataTests.swift */, + 52A265F92CBBC5D5004F6DD3 /* ProviderFactoryTests.swift */, + ); + path = ChatMLXTests; + sourceTree = ""; + }; + 52977D262CCBC89200DD4D2F /* Resources */ = { + isa = PBXGroup; + children = ( + 526675FD2C85F903001EF113 /* Assets.xcassets */, + 526676772C85F9DA001EF113 /* Localizable.xcstrings */, + ); + path = Resources; + sourceTree = ""; + }; + 52977E0E2CCCF20C00DD4D2F /* MLX */ = { + isa = PBXGroup; + children = ( + 52977E172CCD1AB800DD4D2F /* GPUMemorySettingsView.swift */, + 52977E132CCCFF3A00DD4D2F /* MLXProviderModelItemView.swift */, + 52977E112CCCF7FE00DD4D2F /* MLXProviderContentView.swift */, + 52977E0F2CCCF21E00DD4D2F /* MLXProviderLabelView.swift */, + 52B0534F2CB02CF100E8DDBA /* MLXProviderView.swift */, + ); + path = MLX; + sourceTree = ""; + }; + 52977E192CCD295800DD4D2F /* Errors */ = { + isa = PBXGroup; + children = ( + 52977E1F2CCD4D3000DD4D2F /* Errors.swift */, + 52977E1C2CCD2F1800DD4D2F /* ErrorWrapper.swift */, + 52977E1A2CCD296B00DD4D2F /* ErrorView.swift */, + ); + path = Errors; + sourceTree = ""; + }; + 52977E1E2CCD4D0000DD4D2F /* Core */ = { + isa = PBXGroup; + children = ( + 52977E232CCDF43C00DD4D2F /* Constants.swift */, + 526676092C85F903001EF113 /* Components */, + 52977E192CCD295800DD4D2F /* Errors */, + 5266763D2C85F903001EF113 /* Extensions */, + 5266762F2C85F903001EF113 /* Models */, + 525C50DD2CD7B8D500F48479 /* Persistence */, + 52A265EE2CBAE54F004F6DD3 /* Services */, + 52A7C83D2CDE78720072D691 /* Stores */, + 526676372C85F903001EF113 /* Utilities */, + 52A7C8562CDF6D1F0072D691 /* View Modifiers */, + ); + path = Core; + sourceTree = ""; + }; + 52977E212CCDE24900DD4D2F /* OpenAI */ = { + isa = PBXGroup; + children = ( + 52B053512CB0384E00E8DDBA /* OpenAIProviderView.swift */, + ); + path = OpenAI; + sourceTree = ""; + }; + 52A265EE2CBAE54F004F6DD3 /* Services */ = { + isa = PBXGroup; + children = ( + 52B0C89A2D662B3200BDC5F3 /* HuggingfaceHubService.swift */, + 5270B9F82CE0FBDE0029E3A4 /* HuggingFaceService.swift */, + 5270B9DB2CDFD1FD0029E3A4 /* ChatService.swift */, + 52A7C85D2CDF99D40072D691 /* MLXService.swift */, + 52A7C85F2CDFA3600072D691 /* OpenAIService.swift */, + 52A265F12CBAEA89004F6DD3 /* AI */, + 5270B9DA2CDFD1EF0029E3A4 /* ChatStrategies */, + ); + path = Services; + sourceTree = ""; + }; + 52A265F12CBAEA89004F6DD3 /* AI */ = { + isa = PBXGroup; + children = ( + 52CDC3D12CC0207300627147 /* OpenAIProvider.swift */, + 52A265F42CBBB3B8004F6DD3 /* ProviderFactory.swift */, + 52A265F22CBAEA97004F6DD3 /* MLXProvider.swift */, + 52A265EF2CBAE638004F6DD3 /* BaseProvider.swift */, + ); + path = AI; + sourceTree = ""; + }; + 52A7C83D2CDE78720072D691 /* Stores */ = { + isa = PBXGroup; + children = ( + 5270B9E72CE0AB620029E3A4 /* DownloadStore.swift */, + 5270B9E52CE095600029E3A4 /* SettingsStore.swift */, + 52A7C85B2CDF945F0072D691 /* ModelStore.swift */, + 52A7C83E2CDE78830072D691 /* ConversationStore.swift */, + ); + path = Stores; + sourceTree = ""; + }; + 52A7C8562CDF6D1F0072D691 /* View Modifiers */ = { + isa = PBXGroup; + children = ( + 52A7C8572CDF6D3C0072D691 /* AppleIntelligenceEffectViewModifier.swift */, + ); + path = "View Modifiers"; + sourceTree = ""; + }; + 52B053282CAFDF8500E8DDBA /* Model Manager */ = { + isa = PBXGroup; + children = ( + 52B0534E2CB02CDF00E8DDBA /* Providers */, + 52B0534C2CB02C3E00E8DDBA /* ModelManagerView.swift */, + ); + path = "Model Manager"; + sourceTree = ""; + }; + 52B0534E2CB02CDF00E8DDBA /* Providers */ = { + isa = PBXGroup; + children = ( + 52977E212CCDE24900DD4D2F /* OpenAI */, + 52977E0E2CCCF20C00DD4D2F /* MLX */, + ); + path = Providers; + sourceTree = ""; + }; 52B0538B2CB2B03200E8DDBA /* AppleIntelligenceEffect */ = { isa = PBXGroup; children = ( 52B0539F2CB2C2CD00E8DDBA /* AppleIntelligenceEffectManager.swift */, + 52A021CE2CB565FC007893F7 /* AnimatedGradientFill.metal */, 52B053992CB2BF0D00E8DDBA /* AppleIntelligenceEffectController.swift */, 52B0539A2CB2BF0D00E8DDBA /* AppleIntelligenceEffectView.swift */, - 52B053922CB2B0D300E8DDBA /* ColorWheel.metal */, ); path = AppleIntelligenceEffect; sourceTree = ""; }; + 52B9BD082CBEB4E20086C013 /* Migrations */ = { + isa = PBXGroup; + children = ( + 5201638A2CBC2D8C00666E82 /* V2MigrationPolicy.swift */, + ); + path = Migrations; + sourceTree = ""; + }; + 52B9BD092CBEB5960086C013 /* CoreData Models */ = { + isa = PBXGroup; + children = ( + 520163962CBD6B4900666E82 /* Conversation.swift */, + 5270B9D62CDFBB550029E3A4 /* Conversation+CoreDataClass.swift */, + 5270B9D72CDFBB550029E3A4 /* Conversation+CoreDataProperties.swift */, + 5201639A2CBD6BC000666E82 /* Message.swift */, + 52A7C84C2CDF4C390072D691 /* Message+CoreDataClass.swift */, + 52A7C84D2CDF4C390072D691 /* Message+CoreDataProperties.swift */, + 520163A82CBD6CCA00666E82 /* ModelInfo.swift */, + 52A7C84E2CDF4C390072D691 /* ModelInfo+CoreDataClass.swift */, + 52A7C84F2CDF4C390072D691 /* ModelInfo+CoreDataProperties.swift */, + ); + path = "CoreData Models"; + sourceTree = ""; + }; + 52F5348B2CD7C1B6001F1F34 /* Detail */ = { + isa = PBXGroup; + children = ( + 5270B9F32CE0E7BB0029E3A4 /* AssistantMessageView.swift */, + 5270B9F12CE0E6A30029E3A4 /* UserMessageView.swift */, + 5270B9ED2CE0DD730029E3A4 /* EditorView.swift */, + 5270B9EB2CE0D20B0029E3A4 /* EditorToolbarView.swift */, + 5270B9E92CE0D0510029E3A4 /* MessageBoxView.swift */, + 5266760E2C85F903001EF113 /* EmptyConversation.swift */, + 5266760F2C85F903001EF113 /* MessageBubbleView.swift */, + 526676102C85F903001EF113 /* RightSidebarView.swift */, + 5266760A2C85F903001EF113 /* ConversationDetailView.swift */, + ); + path = Detail; + sourceTree = ""; + }; + 52F5348C2CD7C1C5001F1F34 /* Sidebar */ = { + isa = PBXGroup; + children = ( + 5266760B2C85F903001EF113 /* ConversationSidebarItemView.swift */, + 5266760C2C85F903001EF113 /* ConversationSidebarView.swift */, + ); + path = Sidebar; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXNativeTarget section */ @@ -448,7 +812,6 @@ 5266756A2C85F0E8001EF113 /* Defaults */, 5266756D2C85F0FF001EF113 /* Luminare */, 526675732C85F1F9001EF113 /* Splash */, - 526675762C85F26B001EF113 /* Logging */, 526675792C85F487001EF113 /* MarkdownUI */, 5266757C2C85F54D001EF113 /* SwiftUIIntrospect */, 52E50B1C2C8D6E81005A89DE /* LLM */, @@ -465,11 +828,34 @@ 523D8C502C9C7FCC0092791C /* Transformers */, 528DBE2E2C9C86FB004CDD88 /* Transformers */, 527F48142C9EFD5D006AF9FA /* LLM */, + 52CDC3D42CC020F500627147 /* OpenAI */, + 52CDC3D92CC03B4A00627147 /* CoreDataEvolution */, + 52B0C89D2D662BCE00BDC5F3 /* HuggingfaceHub */, ); productName = ChatMLX; productReference = 526675422C85EDCB001EF113 /* ChatMLX.app */; productType = "com.apple.product-type.application"; }; + 527821992CB821A600638477 /* ChatMLXTests */ = { + isa = PBXNativeTarget; + buildConfigurationList = 527821A02CB821A600638477 /* Build configuration list for PBXNativeTarget "ChatMLXTests" */; + buildPhases = ( + 527821962CB821A600638477 /* Sources */, + 527821972CB821A600638477 /* Frameworks */, + 527821982CB821A600638477 /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 5278219F2CB821A600638477 /* PBXTargetDependency */, + ); + name = ChatMLXTests; + packageProductDependencies = ( + ); + productName = ChatMLXTests; + productReference = 5278219A2CB821A600638477 /* ChatMLXTests.xctest */; + productType = "com.apple.product-type.bundle.unit-test"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -483,6 +869,10 @@ 526675412C85EDCB001EF113 = { CreatedOnToolsVersion = 15.4; }; + 527821992CB821A600638477 = { + CreatedOnToolsVersion = 16.0; + TestTargetID = 526675412C85EDCB001EF113; + }; }; }; buildConfigurationList = 5266753D2C85EDCB001EF113 /* Build configuration list for PBXProject "ChatMLX" */; @@ -505,16 +895,19 @@ 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" */, + 52CDC3D32CC020F500627147 /* XCRemoteSwiftPackageReference "OpenAI" */, + 52CDC3D82CC03B4A00627147 /* XCRemoteSwiftPackageReference "CoreDataEvolution" */, + 52B0C89C2D662BCE00BDC5F3 /* XCLocalSwiftPackageReference "../../HuggingfaceHub" */, ); productRefGroup = 526675432C85EDCB001EF113 /* Products */; projectDirPath = ""; projectRoot = ""; targets = ( 526675412C85EDCB001EF113 /* ChatMLX */, + 527821992CB821A600638477 /* ChatMLXTests */, ); }; /* End PBXProject section */ @@ -530,6 +923,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 527821982CB821A600638477 /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -537,57 +937,94 @@ isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; files = ( + 52977E1D2CCD2F1900DD4D2F /* ErrorWrapper.swift in Sources */, 526676422C85F903001EF113 /* TextOutputFormat.swift in Sources */, 526676512C85F903001EF113 /* RightSidebarView.swift in Sources */, + 52977E122CCCF81300DD4D2F /* MLXProviderContentView.swift in Sources */, 52B053A62CB38E8700E8DDBA /* ExperimentalFeaturesView.swift in Sources */, + 52A7C8602CDFA3650072D691 /* OpenAIService.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 */, + 520163A92CBD6CCB00666E82 /* ModelInfo.swift in Sources */, + 5207D8D42CC6846F008588FA /* Provider.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 */, + 5270B9EC2CE0D2100029E3A4 /* EditorToolbarView.swift in Sources */, + 528D83192CAD491900163AAB /* ChatMLX2.xcdatamodeld in Sources */, 526676502C85F903001EF113 /* MessageBubbleView.swift in Sources */, + 52A7C8502CDF4C390072D691 /* ModelInfo+CoreDataClass.swift in Sources */, + 52A7C8512CDF4C390072D691 /* Message+CoreDataProperties.swift in Sources */, + 52A7C8522CDF4C390072D691 /* ModelInfo+CoreDataProperties.swift in Sources */, + 52A7C8542CDF4C390072D691 /* Message+CoreDataClass.swift in Sources */, 526676582C85F903001EF113 /* AboutView.swift in Sources */, 526676552C85F903001EF113 /* LocalModelsView.swift in Sources */, 52A689FA2CAECFE00078CDF9 /* ErrorAlertModifier.swift in Sources */, + 525C50DF2CD7B90400F48479 /* Repository.swift in Sources */, 526676662C85F903001EF113 /* RemoteModel.swift in Sources */, + 52EC64942CC3DC0E00A08069 /* NSManagedObjectContext+Extensions.swift in Sources */, 526676562C85F903001EF113 /* MLXCommunityItemView.swift in Sources */, + 52A265F02CBAE63E004F6DD3 /* BaseProvider.swift in Sources */, 526676442C85F903001EF113 /* UltramanMinimalistWindowModifier.swift in Sources */, 5266764A2C85F903001EF113 /* UltramanWindow.swift in Sources */, + 52B0532A2CAFDFB000E8DDBA /* ProviderView.swift in Sources */, + 52B0532C2CAFEFD800E8DDBA /* Color+Extensions.swift in Sources */, 526676702C85F903001EF113 /* Defaults+Extensions.swift in Sources */, + 52A265F32CBAEA9E004F6DD3 /* MLXProvider.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 */, + 5270B9E02CDFD2F20029E3A4 /* OpenAIChatStrategy.swift in Sources */, + 5270B9F42CE0E7BF0029E3A4 /* AssistantMessageView.swift in Sources */, + 52B0C89B2D662B4800BDC5F3 /* HuggingfaceHubService.swift in Sources */, 528D82262CABE19900163AAB /* Date+Extensions.swift in Sources */, 5266766D2C85F903001EF113 /* LLMRunner.swift in Sources */, + 52B053542CB0F5B300E8DDBA /* LabeledContentStyle+Extensions.swift in Sources */, + 52A021CF2CB565FC007893F7 /* AnimatedGradientFill.metal in Sources */, + 52B0534D2CB02C4600E8DDBA /* ModelManagerView.swift in Sources */, + 52EC64AD2CC4C38600A08069 /* ModelPicker.swift in Sources */, + 52A7C85A2CDF92FC0072D691 /* Model.swift in Sources */, + 5201639B2CBD6BC400666E82 /* Message.swift in Sources */, + 5270B9DE2CDFD2C40029E3A4 /* MLXChatStrategy.swift in Sources */, 5266764E2C85F903001EF113 /* ConversationView.swift in Sources */, - 5266766A2C85F903001EF113 /* Downloader.swift in Sources */, + 5270B9D82CDFBB550029E3A4 /* Conversation+CoreDataClass.swift in Sources */, + 5270B9D92CDFBB550029E3A4 /* Conversation+CoreDataProperties.swift in Sources */, + 52977E162CCD0F7800DD4D2F /* LabeledToggle.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 */, + 52B9BD072CBE94BA0086C013 /* Bundle+Extensions.swift in Sources */, + 5266764C2C85F903001EF113 /* ConversationSidebarItemView.swift in Sources */, + 52B053572CB0F8EE00E8DDBA /* Binding+Extensions.swift in Sources */, + 52CDC3D22CC0208100627147 /* OpenAIProvider.swift in Sources */, 526676622C85F903001EF113 /* Language.swift in Sources */, + 520163AD2CBD876100666E82 /* Common.swift in Sources */, 526676722C85F903001EF113 /* NSWindow+Extensions.swift in Sources */, + 52977E142CCCFF4100DD4D2F /* MLXProviderModelItemView.swift in Sources */, + 52B053502CB02CF900E8DDBA /* MLXProviderView.swift in Sources */, + 52977E182CCD1AC000DD4D2F /* GPUMemorySettingsView.swift in Sources */, + 52A7C8582CDF6D3E0072D691 /* AppleIntelligenceEffectViewModifier.swift in Sources */, 526676412C85F903001EF113 /* SplashCodeSyntaxHighlighter.swift in Sources */, 526676542C85F903001EF113 /* LocalModelItemView.swift in Sources */, 526676632C85F903001EF113 /* LocalModel.swift in Sources */, + 52977D2B2CCBCCEC00DD4D2F /* Logger.swift in Sources */, + 52EC64B12CC531A300A08069 /* ModelType.swift in Sources */, + 5270B9EA2CE0D0570029E3A4 /* MessageBoxView.swift in Sources */, + 5270B9E22CDFD42B0029E3A4 /* ChatStrategy.swift in Sources */, 52B0539D2CB2BF0D00E8DDBA /* AppleIntelligenceEffectView.swift in Sources */, + 520163972CBD6B4B00666E82 /* Conversation.swift in Sources */, + 5270B9F22CE0E6A70029E3A4 /* UserMessageView.swift in Sources */, + 52A7C85E2CDF99DA0072D691 /* MLXService.swift in Sources */, + 52977E202CCD4D3400DD4D2F /* Errors.swift in Sources */, 52B0539E2CB2BF0D00E8DDBA /* AppleIntelligenceEffectController.swift in Sources */, 526676692C85F903001EF113 /* Styles.swift in Sources */, 5266765A2C85F903001EF113 /* GeneralView.swift in Sources */, @@ -595,22 +1032,58 @@ 526676492C85F903001EF113 /* UltramanTextField.swift in Sources */, 5266765E2C85F903001EF113 /* SettingsView.swift in Sources */, 528D831C2CAD49E600163AAB /* PersistenceController.swift in Sources */, - 5266766B2C85F903001EF113 /* Hub.swift in Sources */, + 5270B9F92CE0FBEB0029E3A4 /* HuggingFaceService.swift in Sources */, + 52A7C85C2CDF94640072D691 /* ModelStore.swift in Sources */, + 525C50D72CD79EFE00F48479 /* Theme.swift in Sources */, + 52977E102CCCF22E00DD4D2F /* MLXProviderLabelView.swift in Sources */, + 52977E1B2CCD296F00DD4D2F /* ErrorView.swift in Sources */, + 5207D8D62CC68BAC008588FA /* DefaultProviderPicker.swift in Sources */, + 5270B9E82CE0AB6C0029E3A4 /* DownloadStore.swift in Sources */, + 527821592CB817A300638477 /* ModelManagerViewModel.swift in Sources */, 5266764D2C85F903001EF113 /* ConversationSidebarView.swift in Sources */, - 528D83372CADB64600163AAB /* ConversationViewModel.swift in Sources */, + 528D83372CADB64600163AAB /* ConversationViewModelOld.swift in Sources */, 5266765D2C85F903001EF113 /* SettingsSidebarView.swift in Sources */, 5266764B2C85F903001EF113 /* ConversationDetailView.swift in Sources */, + 5201638B2CBC2D8D00666E82 /* V2MigrationPolicy.swift in Sources */, 52B053A22CB2F6F900E8DDBA /* AppleIntelligenceEffectDisplay.swift in Sources */, 526676602C85F903001EF113 /* DisplayStyle.swift in Sources */, + 5278215B2CB81FEB00638477 /* MarkdownMetadata.swift in Sources */, 5266765B2C85F903001EF113 /* HuggingFaceView.swift in Sources */, 526676642C85F903001EF113 /* LocalModelGroup.swift in Sources */, 526676522C85F903001EF113 /* DownloadManagerView.swift in Sources */, + 5270B9E62CE0956C0029E3A4 /* SettingsStore.swift in Sources */, + 5270B9DC2CDFD2090029E3A4 /* ChatService.swift in Sources */, + 52A265F52CBBB3C0004F6DD3 /* ProviderFactory.swift in Sources */, + 52A7C83F2CDE78860072D691 /* ConversationStore.swift in Sources */, + 520163AB2CBD715900666E82 /* ProviderModel.swift in Sources */, + 52977E242CCDF43E00DD4D2F /* Constants.swift in Sources */, + 5270B9EE2CE0DD780029E3A4 /* EditorView.swift in Sources */, + 52B053522CB0385800E8DDBA /* OpenAIProviderView.swift in Sources */, 526676482C85F903001EF113 /* UltramanTextEditorWithPlaceholder.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; + 527821962CB821A600638477 /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 52A265FA2CBBC5D5004F6DD3 /* ProviderFactoryTests.swift in Sources */, + 527821A52CB821AC00638477 /* ChatMLXTests.swift in Sources */, + 527821A72CB821B800638477 /* MarkdownMetadataTests.swift in Sources */, + 52212ABA2CDFA96100A54AA2 /* OpenAIServiceTests.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; /* End PBXSourcesBuildPhase section */ +/* Begin PBXTargetDependency section */ + 5278219F2CB821A600638477 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 526675412C85EDCB001EF113 /* ChatMLX */; + targetProxy = 5278219E2CB821A600638477 /* PBXContainerItemProxy */; + }; +/* End PBXTargetDependency section */ + /* Begin XCBuildConfiguration section */ 5266754F2C85EDCC001EF113 /* Debug */ = { isa = XCBuildConfiguration; @@ -674,6 +1147,7 @@ SDKROOT = macosx; SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; SWIFT_OPTIMIZATION_LEVEL = "-Onone"; + SWIFT_VERSION = 6.0; }; name = Debug; }; @@ -731,6 +1205,7 @@ MTL_FAST_MATH = YES; SDKROOT = macosx; SWIFT_COMPILATION_MODE = wholemodule; + SWIFT_VERSION = 6.0; }; name = Release; }; @@ -739,7 +1214,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = ChatMLX/ChatMLX.entitlements; + CODE_SIGN_ENTITLEMENTS = ChatMLX/Configuration/ChatMLX.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 5; @@ -755,12 +1231,13 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 14; MARKETING_VERSION = 1.1.3; PRODUCT_BUNDLE_IDENTIFIER = johnmai.ChatMLX; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_VERSION = 6.0; }; name = Debug; }; @@ -769,7 +1246,8 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; - CODE_SIGN_ENTITLEMENTS = ChatMLX/ChatMLXRelease.entitlements; + CODE_SIGN_ENTITLEMENTS = ChatMLX/Configuration/ChatMLX.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; CURRENT_PROJECT_VERSION = 5; @@ -785,12 +1263,49 @@ "$(inherited)", "@executable_path/../Frameworks", ); - MACOSX_DEPLOYMENT_TARGET = 14.0; + MACOSX_DEPLOYMENT_TARGET = 14; MARKETING_VERSION = 1.1.3; PRODUCT_BUNDLE_IDENTIFIER = johnmai.ChatMLX; PRODUCT_NAME = "$(TARGET_NAME)"; SWIFT_EMIT_LOC_STRINGS = YES; - SWIFT_VERSION = 5.0; + SWIFT_STRICT_CONCURRENCY = complete; + SWIFT_VERSION = 6.0; + }; + name = Release; + }; + 527821A12CB821A600638477 /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = RFGFKQEKRH; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 14; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = johnmai.ChatMLXTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 6.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChatMLX.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ChatMLX"; + }; + name = Debug; + }; + 527821A22CB821A600638477 /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + BUNDLE_LOADER = "$(TEST_HOST)"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = RFGFKQEKRH; + GENERATE_INFOPLIST_FILE = YES; + MACOSX_DEPLOYMENT_TARGET = 14; + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = johnmai.ChatMLXTests; + PRODUCT_NAME = "$(TARGET_NAME)"; + SWIFT_EMIT_LOC_STRINGS = NO; + SWIFT_VERSION = 6.0; + TEST_HOST = "$(BUILT_PRODUCTS_DIR)/ChatMLX.app/$(BUNDLE_EXECUTABLE_FOLDER_PATH)/ChatMLX"; }; name = Release; }; @@ -815,8 +1330,24 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 527821A02CB821A600638477 /* Build configuration list for PBXNativeTarget "ChatMLXTests" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 527821A12CB821A600638477 /* Debug */, + 527821A22CB821A600638477 /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ +/* Begin XCLocalSwiftPackageReference section */ + 52B0C89C2D662BCE00BDC5F3 /* XCLocalSwiftPackageReference "../../HuggingfaceHub" */ = { + isa = XCLocalSwiftPackageReference; + relativePath = ../../HuggingfaceHub; + }; +/* End XCLocalSwiftPackageReference section */ + /* Begin XCRemoteSwiftPackageReference section */ 526675602C85EFB3001EF113 /* XCRemoteSwiftPackageReference "Alamofire" */ = { isa = XCRemoteSwiftPackageReference; @@ -866,14 +1397,6 @@ 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"; @@ -898,6 +1421,22 @@ kind = branch; }; }; + 52CDC3D32CC020F500627147 /* XCRemoteSwiftPackageReference "OpenAI" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/MacPaw/OpenAI.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.3.0; + }; + }; + 52CDC3D82CC03B4A00627147 /* XCRemoteSwiftPackageReference "CoreDataEvolution" */ = { + isa = XCRemoteSwiftPackageReference; + repositoryURL = "https://github.com/fatbobman/CoreDataEvolution.git"; + requirement = { + kind = upToNextMajorVersion; + minimumVersion = 0.3.1; + }; + }; /* End XCRemoteSwiftPackageReference section */ /* Begin XCSwiftPackageProductDependency section */ @@ -935,11 +1474,6 @@ 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" */; @@ -959,6 +1493,20 @@ isa = XCSwiftPackageProductDependency; productName = Transformers; }; + 52B0C89D2D662BCE00BDC5F3 /* HuggingfaceHub */ = { + isa = XCSwiftPackageProductDependency; + productName = HuggingfaceHub; + }; + 52CDC3D42CC020F500627147 /* OpenAI */ = { + isa = XCSwiftPackageProductDependency; + package = 52CDC3D32CC020F500627147 /* XCRemoteSwiftPackageReference "OpenAI" */; + productName = OpenAI; + }; + 52CDC3D92CC03B4A00627147 /* CoreDataEvolution */ = { + isa = XCSwiftPackageProductDependency; + package = 52CDC3D82CC03B4A00627147 /* XCRemoteSwiftPackageReference "CoreDataEvolution" */; + productName = CoreDataEvolution; + }; 52E50B1C2C8D6E81005A89DE /* LLM */ = { isa = XCSwiftPackageProductDependency; productName = LLM; @@ -1006,13 +1554,14 @@ /* End XCSwiftPackageProductDependency section */ /* Begin XCVersionGroup section */ - 528D83172CAD491900163AAB /* ChatMLX.xcdatamodeld */ = { + 528D83172CAD491900163AAB /* ChatMLX2.xcdatamodeld */ = { isa = XCVersionGroup; children = ( - 528D83182CAD491900163AAB /* ChatMLX.xcdatamodel */, + 520163832CBC1C9F00666E82 /* ChatMLX 2.xcdatamodel */, + 528D83182CAD491900163AAB /* ChatMLX2.xcdatamodel */, ); - currentVersion = 528D83182CAD491900163AAB /* ChatMLX.xcdatamodel */; - path = ChatMLX.xcdatamodeld; + currentVersion = 520163832CBC1C9F00666E82 /* ChatMLX 2.xcdatamodel */; + path = ChatMLX2.xcdatamodeld; sourceTree = ""; versionGroupType = wrapper.xcdatamodel; }; diff --git a/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index f70955a..ece2993 100644 --- a/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ChatMLX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -1,5 +1,5 @@ { - "originHash" : "91755e46d4857336740696612733433e7fa7ef978bc35290de8f756037756422", + "originHash" : "8b17ce993eabb8acc9a586f8f2954a4513d6ab871abc6590ca9536d2975b7c4f", "pins" : [ { "identity" : "alamofire", @@ -19,6 +19,15 @@ "version" : "1.3.9" } }, + { + "identity" : "anycodable", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Flight-School/AnyCodable", + "state" : { + "revision" : "862808b2070cd908cb04f9aafe7de83d35f81b05", + "version" : "0.6.7" + } + }, { "identity" : "compactslider", "kind" : "remoteSourceControl", @@ -28,6 +37,15 @@ "version" : "1.1.6" } }, + { + "identity" : "coredataevolution", + "kind" : "remoteSourceControl", + "location" : "https://github.com/fatbobman/CoreDataEvolution.git", + "state" : { + "revision" : "5ef939b16ca5f95a132458e4e14a81d8bf452390", + "version" : "0.3.1" + } + }, { "identity" : "defaults", "kind" : "remoteSourceControl", @@ -91,6 +109,24 @@ "version" : "6.0.1" } }, + { + "identity" : "openai", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MacPaw/OpenAI.git", + "state" : { + "revision" : "843e087929aa806adb611dbca93f9a4a7f28be04", + "version" : "0.3.0" + } + }, + { + "identity" : "semaphore", + "kind" : "remoteSourceControl", + "location" : "https://github.com/groue/Semaphore", + "state" : { + "revision" : "2543679282aa6f6c8ecf2138acd613ed20790bc2", + "version" : "0.1.0" + } + }, { "identity" : "splash", "kind" : "remoteSourceControl", @@ -109,15 +145,6 @@ "version" : "1.5.0" } }, - { - "identity" : "swift-log", - "kind" : "remoteSourceControl", - "location" : "https://github.com/apple/swift-log", - "state" : { - "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", - "version" : "1.6.1" - } - }, { "identity" : "swift-markdown-ui", "kind" : "remoteSourceControl", @@ -136,6 +163,15 @@ "version" : "1.0.2" } }, + { + "identity" : "swift-syntax", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-syntax.git", + "state" : { + "revision" : "0687f71944021d616d34d922343dcef086855920", + "version" : "600.0.1" + } + }, { "identity" : "swift-transformers", "kind" : "remoteSourceControl", diff --git a/ChatMLX.xcodeproj/xcshareddata/xcschemes/ChatMLX.xcscheme b/ChatMLX.xcodeproj/xcshareddata/xcschemes/ChatMLX.xcscheme index aa1ee8e..1d52ea8 100644 --- a/ChatMLX.xcodeproj/xcshareddata/xcschemes/ChatMLX.xcscheme +++ b/ChatMLX.xcodeproj/xcshareddata/xcschemes/ChatMLX.xcscheme @@ -55,6 +55,22 @@ argument = "-com.apple.CoreData.ConcurrencyDebug 1" isEnabled = "YES"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist b/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist index e2cd285..9f7e491 100644 --- a/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist +++ b/ChatMLX.xcodeproj/xcuserdata/john.xcuserdatad/xcschemes/xcschememanagement.plist @@ -7,7 +7,12 @@ ChatMLX.xcscheme_^#shared#^_ orderHint - 3 + 0 + + ChatMLXTests.xcscheme_^#shared#^_ + + orderHint + 7 SuppressBuildableAutocreation @@ -17,6 +22,11 @@ primary + 527821992CB821A600638477 + + primary + + diff --git a/ChatMLX/Application/ChatMLXApp.swift b/ChatMLX/Application/ChatMLXApp.swift new file mode 100644 index 0000000..1e7aba7 --- /dev/null +++ b/ChatMLX/Application/ChatMLXApp.swift @@ -0,0 +1,131 @@ +// +// ChatMLXApp.swift +// ChatMLX +// +// Created by John Mai on 2024/8/3. +// + +import Defaults +import SwiftUI +import os + +@main +struct ChatMLXApp: App { + // MARK: - Properties + + private let currentVersion = getVersion() + private let viewContext = PersistenceController.shared.container.viewContext + private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ChatMLXApp") + + // MARK: - Environment + + @Environment(\.scenePhase) private var scenePhase + @Environment(\.openSettings) private var openSettings + @Environment(\.dismissWindow) private var dismissWindow + @Environment(\.openWindow) private var openWindow + + // MARK: - State + + @State private var conversationStore: ConversationStore = .init() + @State private var settingsViewModel: SettingsViewModel = .init() + @State private var errorWrapper: ErrorWrapper? + @State private var settingsStore = SettingsStore() + + + // MARK: - User Defaults + + @Default(.language) var language + @Default(.lastLaunchedVersion) var lastLaunchedVersion + + init() { + updateVersionIfNeeded() + } + + var body: some Scene { + Group { + mainWindow() + settingsWindow() + } + .environment(conversationStore) + .environment(ModelStore()) + .environment(settingsStore) + .environment(DownloadStore.shared) + .environment(settingsViewModel) + .environment(\.managedObjectContext, viewContext) + .environment(\.locale, .init(identifier: language.rawValue)) + .environment(\.appError) { error in + Task { @MainActor in + errorWrapper = ErrorWrapper(error: error, guidance: "") + } + } + .onChange(of: scenePhase) { _, newValue in + if newValue == .background { + try? viewContext.saveChanges() + } + } + + menu() + } +} + +// MARK: - Scenes + +extension ChatMLXApp { + // MARK: - Main Window + + private func mainWindow() -> some Scene { + WindowGroup(id: Constants.mainWindowID) { + ConversationView(selectedConversation: $conversationStore.selectedConversation) + .frame(minWidth: 900, minHeight: 580) + .sheet(item: $errorWrapper) { errorWrapper in + ErrorView(errorWrapper: errorWrapper) + } + } + } + + // MARK: - Settings Window + + private func settingsWindow() -> some Scene { + Settings { + SettingsView() + .frame(width: 650, height: 480) + .sheet(item: $errorWrapper) { errorWrapper in + ErrorView(errorWrapper: errorWrapper) + } + } + } + + // MARK: - Menu + + private func menu() -> some Scene { + MenuBarExtra { + Button("Open \(Bundle.main.name)") { + dismissWindow(id: Constants.mainWindowID) + NSApp.activate(ignoringOtherApps: true) + openWindow(id: Constants.mainWindowID) + } + Button("New Conversation") {} + Button("Settings") { + openSettings() + } + .keyboardShortcut(",") + Button("Quit") { + NSApp.terminate(nil) + } + .keyboardShortcut("q") + } label: { + Image("menubarIcon") + .renderingMode(.template) + } + } +} + +// MARK: - Private Methods + +extension ChatMLXApp { + private func updateVersionIfNeeded() { + if currentVersion != lastLaunchedVersion { + Defaults[.lastLaunchedVersion] = currentVersion + } + } +} diff --git a/ChatMLX/Assets.xcassets/AccentColor.colorset/Contents.json b/ChatMLX/Assets.xcassets/AccentColor.colorset/Contents.json deleted file mode 100644 index 22c4bb0..0000000 --- a/ChatMLX/Assets.xcassets/AccentColor.colorset/Contents.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "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" - } - ], - "info" : { - "author" : "xcode", - "version" : 1 - } -} diff --git a/ChatMLX/ChatMLXApp.swift b/ChatMLX/ChatMLXApp.swift deleted file mode 100644 index 7148dc5..0000000 --- a/ChatMLX/ChatMLXApp.swift +++ /dev/null @@ -1,71 +0,0 @@ -// -// ChatMLXApp.swift -// ChatMLX -// -// Created by John Mai on 2024/8/3. -// - -import Defaults -import SwiftUI - -@main -struct ChatMLXApp: App { - @Environment(\.scenePhase) private var scenePhase - - @State private var conversationViewModel: ConversationViewModel = .init() - @State private var settingsViewModel: SettingsViewModel = .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)") - } - } - } - } - - 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(\.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/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/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/ChatMLX.entitlements b/ChatMLX/Configuration/ChatMLX.entitlements similarity index 75% rename from ChatMLX/ChatMLX.entitlements rename to ChatMLX/Configuration/ChatMLX.entitlements index 7c7a703..c107fcd 100644 --- a/ChatMLX/ChatMLX.entitlements +++ b/ChatMLX/Configuration/ChatMLX.entitlements @@ -2,11 +2,15 @@ - com.apple.security.network.client - com.apple.security.app-sandbox + + com.apple.security.files.downloads.read-only com.apple.security.files.user-selected.read-only + com.apple.security.network.client + + com.apple.security.network.server + diff --git a/ChatMLX/Configuration/ChatMLX2.xcdatamodeld/.xccurrentversion b/ChatMLX/Configuration/ChatMLX2.xcdatamodeld/.xccurrentversion new file mode 100644 index 0000000..f703c20 --- /dev/null +++ b/ChatMLX/Configuration/ChatMLX2.xcdatamodeld/.xccurrentversion @@ -0,0 +1,8 @@ + + + + + _XCCurrentVersionName + ChatMLX 2.xcdatamodel + + diff --git a/ChatMLX/Configuration/ChatMLX2.xcdatamodeld/ChatMLX 2.xcdatamodel/contents b/ChatMLX/Configuration/ChatMLX2.xcdatamodeld/ChatMLX 2.xcdatamodel/contents new file mode 100644 index 0000000..20d6715 --- /dev/null +++ b/ChatMLX/Configuration/ChatMLX2.xcdatamodeld/ChatMLX 2.xcdatamodel/contents @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ChatMLX/Models/ChatMLX.xcdatamodeld/ChatMLX.xcdatamodel/contents b/ChatMLX/Configuration/ChatMLX2.xcdatamodeld/ChatMLX2.xcdatamodel/contents similarity index 92% rename from ChatMLX/Models/ChatMLX.xcdatamodeld/ChatMLX.xcdatamodel/contents rename to ChatMLX/Configuration/ChatMLX2.xcdatamodeld/ChatMLX2.xcdatamodel/contents index d7ee4c8..ca47eef 100644 --- a/ChatMLX/Models/ChatMLX.xcdatamodeld/ChatMLX.xcdatamodel/contents +++ b/ChatMLX/Configuration/ChatMLX2.xcdatamodeld/ChatMLX2.xcdatamodel/contents @@ -1,11 +1,12 @@ - + + - + diff --git a/ChatMLX/Core/Components/AppleIntelligenceEffect/AnimatedGradientFill.metal b/ChatMLX/Core/Components/AppleIntelligenceEffect/AnimatedGradientFill.metal new file mode 100644 index 0000000..d5ba816 --- /dev/null +++ b/ChatMLX/Core/Components/AppleIntelligenceEffect/AnimatedGradientFill.metal @@ -0,0 +1,14 @@ +// +// AnimatedGradientFill.metal +// Inferno https://github.com/twostraws/Inferno/blob/main/Sources/Inferno/Shaders/Transformation/AnimatedGradientFill.metal +// + +#include +using namespace metal; + +[[ stitchable ]] half4 animatedGradientFill(float2 position, half4 color, float2 size, float time) { + half2 uv = half2(position / size) * 2.0h - 1.0h; + half angle = atan2(uv.y, uv.x) + time; + half3 sinValues = abs(sin(half3(angle, angle + 2.0h, angle + 4.0h))); + return half4(sinValues, 1.0h) * color.a; +} diff --git a/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectController.swift b/ChatMLX/Core/Components/AppleIntelligenceEffect/AppleIntelligenceEffectController.swift similarity index 94% rename from ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectController.swift rename to ChatMLX/Core/Components/AppleIntelligenceEffect/AppleIntelligenceEffectController.swift index 009773d..62030fc 100644 --- a/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectController.swift +++ b/ChatMLX/Core/Components/AppleIntelligenceEffect/AppleIntelligenceEffectController.swift @@ -1,8 +1,8 @@ // -// FireworkController.swift -// Firework +// AppleIntelligenceEffectController.swift +// ChatMLX // -// Created by 秋星桥 on 2024/2/7. +// Created by John Mai on 2024/10/6. // import AppKit diff --git a/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectManager.swift b/ChatMLX/Core/Components/AppleIntelligenceEffect/AppleIntelligenceEffectManager.swift similarity index 94% rename from ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectManager.swift rename to ChatMLX/Core/Components/AppleIntelligenceEffect/AppleIntelligenceEffectManager.swift index 50d2920..3ade7d2 100644 --- a/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectManager.swift +++ b/ChatMLX/Core/Components/AppleIntelligenceEffect/AppleIntelligenceEffectManager.swift @@ -7,7 +7,8 @@ import AppKit -class AppleIntelligenceEffectManager { +@MainActor +final class AppleIntelligenceEffectManager { static let shared = AppleIntelligenceEffectManager() private var effectController: AppleIntelligenceEffectController? diff --git a/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectView.swift b/ChatMLX/Core/Components/AppleIntelligenceEffect/AppleIntelligenceEffectView.swift similarity index 62% rename from ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectView.swift rename to ChatMLX/Core/Components/AppleIntelligenceEffect/AppleIntelligenceEffectView.swift index 5438658..43265fd 100644 --- a/ChatMLX/Components/AppleIntelligenceEffect/AppleIntelligenceEffectView.swift +++ b/ChatMLX/Core/Components/AppleIntelligenceEffect/AppleIntelligenceEffectView.swift @@ -8,36 +8,42 @@ import SwiftUI struct AppleIntelligenceEffectView: View { - private let shader = ShaderLibrary.colorWheel(.boundingRect, .float(1)) - private let angles = [133.0, -133.0] + private let shader = ShaderLibrary.colorWheel(.boundingRect) + private let angles = [1, -1] private let maxBlurRadiusBase: CGFloat = 18 private let minBlurRadiusBase: CGFloat = 6 + private let startTime = Date.now 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]) + ForEach(angles, id: \.self) { angle in + colorWheelRectangle(for: timeline.date, angle: angle) } } } } - @MainActor - private func colorWheelRectangle(for date: Date, angle: Double) -> some View { - let time = date.timeIntervalSince1970 + private func colorWheelRectangle(for date: Date, angle: Int) -> some View { + let elapsed = startTime.distance(to: date) + let blurRadius = angle > 0 - ? maxBlurRadiusBase + 6 * sin(time * 2) - : minBlurRadiusBase + 3 * sin(time * 4) + ? maxBlurRadiusBase + 6 * sin(elapsed * 2) + : minBlurRadiusBase + 3 * sin(elapsed * 4) return Rectangle() - .fill(shader) - .rotationEffect(.degrees(time * 60)) - .scaleEffect(2.4) - .rotationEffect(.degrees(time * angle)) + .visualEffect { content, proxy in + content + .colorEffect( + ShaderLibrary.animatedGradientFill( + .float2(proxy.size), + .float(elapsed) + ) + ) + } .mask(alignment: .center) { if useRoundedRectangle { UnevenRoundedRectangle( diff --git a/ChatMLX/Core/Components/DefaultProviderPicker.swift b/ChatMLX/Core/Components/DefaultProviderPicker.swift new file mode 100644 index 0000000..3ae1619 --- /dev/null +++ b/ChatMLX/Core/Components/DefaultProviderPicker.swift @@ -0,0 +1,28 @@ +//// +//// DefaultProviderPicker.swift +//// ChatMLX +//// +//// Created by John Mai on 2024/10/21. +//// +// +//import Defaults +//import SwiftUI +// +//struct DefaultProviderPicker:View { +// @Binding var provider: Provider +// +// @Default(.enableOpenAI) private var enableOpenAI +// +// var body: some View { +// Picker("Provider", selection: $provider) { +// Text("MLX").tag(Provider.mlx) +// +// if enableOpenAI { +// Text("OpenAI").tag(Provider.openAI) +// } +// } +// .pickerStyle(.menu) +// .labelsHidden() +// .tint(.white) +// } +//} diff --git a/ChatMLX/Components/EffectView.swift b/ChatMLX/Core/Components/EffectView.swift similarity index 100% rename from ChatMLX/Components/EffectView.swift rename to ChatMLX/Core/Components/EffectView.swift diff --git a/ChatMLX/Components/ErrorAlertModifier.swift b/ChatMLX/Core/Components/ErrorAlertModifier.swift similarity index 99% rename from ChatMLX/Components/ErrorAlertModifier.swift rename to ChatMLX/Core/Components/ErrorAlertModifier.swift index 8285a22..07494bc 100644 --- a/ChatMLX/Components/ErrorAlertModifier.swift +++ b/ChatMLX/Core/Components/ErrorAlertModifier.swift @@ -9,6 +9,7 @@ import SwiftUI struct ErrorAlertModifier: ViewModifier { @Binding var showErrorAlert: Bool + @Binding var errorTitle: String? @Binding var error: Error? diff --git a/ChatMLX/Core/Components/LabeledToggle.swift b/ChatMLX/Core/Components/LabeledToggle.swift new file mode 100644 index 0000000..a0a6ee6 --- /dev/null +++ b/ChatMLX/Core/Components/LabeledToggle.swift @@ -0,0 +1,22 @@ +// +// LabeledToggle.swift +// ChatMLX +// +// Created by John Mai on 2024/10/26. +// + +import SwiftUI + +struct LabeledToggle: View { + let title: String + @Binding var isOn: Bool + + var body: some View { + LabeledContent(title) { + Toggle("", isOn: $isOn) + .labelsHidden() + .toggleStyle(.switch) + } + .labeledContentStyle(.horizontal) + } +} diff --git a/ChatMLX/Core/Components/ModelPicker.swift b/ChatMLX/Core/Components/ModelPicker.swift new file mode 100644 index 0000000..654a565 --- /dev/null +++ b/ChatMLX/Core/Components/ModelPicker.swift @@ -0,0 +1,46 @@ +// +// ModelPicker.swift +// ChatMLX +// +// Created by John Mai on 2024/10/20. +// + +import Defaults +import SwiftUI + +struct ModelPicker: View { + @Binding var selection: ProviderModel.Identifier? + let models: [ProviderModel] + + var groupedModels: [String: [ProviderModel]] { + Dictionary(grouping: models) { + switch $0.id { + case .id(_, let provider), .directory(_, let provider): + provider.rawValue + } + } + } + + var body: some View { + Picker( + selection: $selection, + label: Image(systemName: "brain") + ) { + Text("Not selected").tag(nil as ProviderModel.Identifier?) + ForEach(groupedModels.keys.sorted(), id: \.self) { provider in + Section(header: Text(provider)) { + ForEach(groupedModels[provider]!) { model in + if let name = model.name { + Text(name).tag(model.id) + } else if case .id(let id, _) = model.id { + Text(id).tag(model.id) + } + } + } + } + } + .pickerStyle(.menu) + .labelsHidden() + .tint(.white) + } +} diff --git a/ChatMLX/Components/NoneInteractWindow.swift b/ChatMLX/Core/Components/NoneInteractWindow.swift similarity index 100% rename from ChatMLX/Components/NoneInteractWindow.swift rename to ChatMLX/Core/Components/NoneInteractWindow.swift diff --git a/ChatMLX/Core/Components/ProviderView.swift b/ChatMLX/Core/Components/ProviderView.swift new file mode 100644 index 0000000..04a0fff --- /dev/null +++ b/ChatMLX/Core/Components/ProviderView.swift @@ -0,0 +1,61 @@ +// +// ProviderView.swift +// ChatMLX +// +// Created by John Mai on 2024/10/4. +// + +import SwiftUI + +struct ProviderView: View { + @State var isExpanded: Bool = false + @Binding var isEnabled: Bool? + @ViewBuilder let label: () -> Label + @ViewBuilder let content: () -> Content + + let cornerRadius: CGFloat = 12 + + var body: some View { + VStack(spacing: 0) { + header + if isExpanded { + content() + } + } + .background(.quinary) + .clipShape(.rect(cornerRadius: cornerRadius)) + .overlay { + RoundedRectangle(cornerRadius: cornerRadius) + .strokeBorder(.quaternary, lineWidth: 1) + } + } + + @ViewBuilder + private var header: some View { + HStack { + Image(systemName: "chevron.down") + .font(.title3.weight(.semibold)) + .rotationEffect(.degrees(isExpanded ? -180 : 0)) + + label() + + Spacer() + + if isEnabled != nil { + Toggle("", isOn: $isEnabled.toUnwrapped(defaultValue: false)) + .labelsHidden() + .toggleStyle(.switch) + } + } + .padding(.horizontal) + .frame(height: 45) + .background(Color.black.opacity(0.1)) + .onTapGesture { + withAnimation(.easeIn) { + isExpanded.toggle() + } + } + .zIndex(10) + .transition(.move(edge: .top)) + } +} diff --git a/ChatMLX/Components/SyntaxHighlighter/SplashCodeSyntaxHighlighter.swift b/ChatMLX/Core/Components/SyntaxHighlighter/SplashCodeSyntaxHighlighter.swift similarity index 100% rename from ChatMLX/Components/SyntaxHighlighter/SplashCodeSyntaxHighlighter.swift rename to ChatMLX/Core/Components/SyntaxHighlighter/SplashCodeSyntaxHighlighter.swift diff --git a/ChatMLX/Components/SyntaxHighlighter/TextOutputFormat.swift b/ChatMLX/Core/Components/SyntaxHighlighter/TextOutputFormat.swift similarity index 87% rename from ChatMLX/Components/SyntaxHighlighter/TextOutputFormat.swift rename to ChatMLX/Core/Components/SyntaxHighlighter/TextOutputFormat.swift index c003075..8105443 100644 --- a/ChatMLX/Components/SyntaxHighlighter/TextOutputFormat.swift +++ b/ChatMLX/Core/Components/SyntaxHighlighter/TextOutputFormat.swift @@ -9,9 +9,9 @@ import Splash import SwiftUI struct TextOutputFormat: OutputFormat { - private let theme: Theme + private let theme: Splash.Theme - init(theme: Theme) { + init(theme: Splash.Theme) { self.theme = theme } @@ -22,10 +22,10 @@ struct TextOutputFormat: OutputFormat { extension TextOutputFormat { struct Builder: OutputBuilder { - private let theme: Theme + private let theme: Splash.Theme private var accumulatedText: [Text] - fileprivate init(theme: Theme) { + fileprivate init(theme: Splash.Theme) { self.theme = theme self.accumulatedText = [] } diff --git a/ChatMLX/Core/Components/UltramanMinimalistWindowModifier.swift b/ChatMLX/Core/Components/UltramanMinimalistWindowModifier.swift new file mode 100644 index 0000000..a0b0117 --- /dev/null +++ b/ChatMLX/Core/Components/UltramanMinimalistWindowModifier.swift @@ -0,0 +1,69 @@ +// +// 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 + + func body(content: Content) -> some View { + content + .ignoresSafeArea() + .introspect(.window, on: .macOS(.v14, .v15)) { window in + configureWindow(window) + setupFullScreenObservers(for: window) + } + } + + private func configureWindow(_ window: NSWindow) { + 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 + } + + private func setupFullScreenObservers(for window: NSWindow) { + let notificationCenter = NotificationCenter.default + + notificationCenter.addObserver(forName: NSWindow.didEnterFullScreenNotification, object: window, queue: .main) { + _ in + Task { @MainActor in + handleFullScreenEnter(window) + } + } + + notificationCenter.addObserver(forName: NSWindow.didExitFullScreenNotification, object: window, queue: .main) { + _ in + Task { @MainActor in + handleFullScreenExit(window) + } + } + } + + private func handleFullScreenEnter(_ window: NSWindow) { + window.toolbar?.isVisible = false + NSApp.presentationOptions = [.autoHideToolbar, .autoHideMenuBar] + } + + private func handleFullScreenExit(_ window: NSWindow) { + window.toolbar?.isVisible = true + NSApp.presentationOptions = [] + } +} + +extension View { + func ultramanMinimalistWindowStyle() -> some View { + modifier(UltramanMinimalistWindowModifier()) + } +} diff --git a/ChatMLX/Core/Components/UltramanNavigationSplitView.swift b/ChatMLX/Core/Components/UltramanNavigationSplitView.swift new file mode 100644 index 0000000..1225275 --- /dev/null +++ b/ChatMLX/Core/Components/UltramanNavigationSplitView.swift @@ -0,0 +1,219 @@ +// +// UltramanNavigationTitleKey.swift +// ChatMLXUI +// +// Created by John Mai on 2025/2/22. +// + +import SwiftUI + +extension AnyView: @unchecked @retroactive Sendable {} + +struct UltramanToolbarItem: Identifiable, Equatable, Sendable { + 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 + } + + nonisolated init(alignment: ToolbarAlignment = .trailing, @ViewBuilder content: () -> some View) { + self.content = AnyView(content()) + self.alignment = alignment + } +} + +@resultBuilder +struct UltramanToolbarBuilder { + static func buildBlock(_ components: UltramanToolbarItem...) -> [UltramanToolbarItem] { + components + } +} + +private struct UltramanNavigationTitleKey: EnvironmentKey { + static let defaultValue: String = "" +} + +struct UltramanNavigationToolbarKey: EnvironmentKey { + static let defaultValue: [UltramanToolbarItem] = [] +} + +struct UltramanNavigationStateKey: EnvironmentKey { + static let defaultValue: UltramanNavigationState = .init() +} + +extension EnvironmentValues { + var ultramanNavigationState: UltramanNavigationState { + get { self[UltramanNavigationStateKey.self] } + set { self[UltramanNavigationStateKey.self] = newValue } + } +} + +struct UltramanNavigationTitleViewModifier: ViewModifier { + let title: String + + @Environment(\.ultramanNavigationState) var state + + func body(content: Content) -> some View { + content + .onAppear { + state.title = title + } + .onChange(of: title) { oldValue, newValue in + print("title changed from \(oldValue) to \(newValue)") + state.title = newValue + } + .onDisappear { + // 在视图消失时清除标题 + state.title = "" + } + } +} + +struct UltramanNavigationToolbarViewModifier: ViewModifier { + let items: [UltramanToolbarItem] + + @Environment(UltramanNavigationState.self) var state + + func body(content: Content) -> some View { + content + .onAppear { + state.toolbarItems = items + } + .onChange(of: items) { oldValue, newValue in + print("toolbar items changed") + state.toolbarItems = newValue + } + .onDisappear { + // 在视图消失时清除标题 + state.toolbarItems = [] + } + } +} + +@Observable +class UltramanNavigationState: @unchecked Sendable { + var title: String = "" + var toolbarItems: [UltramanToolbarItem] = [] +} + +extension View { + func ultramanNavigationTitle(_ title: String) -> some View { + modifier(UltramanNavigationTitleViewModifier(title: title)) + } + + func ultramanToolbar( + alignment: UltramanToolbarItem.ToolbarAlignment = .trailing, + @ViewBuilder content: () -> some View + ) -> some View { + modifier( + UltramanNavigationToolbarViewModifier(items: [ + 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 isDragging = false + @State private var isSidebarVisible = true + + let minSidebarWidth: CGFloat = 200 + let maxSidebarWidth: CGFloat = 400 + + @State private var state = UltramanNavigationState() + + var body: some View { + HStack(spacing: .zero) { + if isSidebarVisible { + sidebar() + .frame(width: sidebarWidth) + .transition(.move(edge: .leading)) + .zIndex(10) + } + + VStack(spacing: .zero) { + Divider() + detail() + .frame(maxWidth: .infinity, maxHeight: .infinity) + } + + .safeAreaInset(edge: .top, alignment: .center, spacing: 0) { + header().frame(height: 52) + } + } + .environment(state) + } + + @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(state.toolbarItems.filter { $0.alignment == .leading }) { + item in + item.content + } + + Spacer() + Text(LocalizedStringKey(state.title)) + .font(.headline) + + Spacer() + + ForEach(state.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/Core/Components/UltramanSecureField.swift similarity index 100% rename from ChatMLX/Components/UltramanSecureField.swift rename to ChatMLX/Core/Components/UltramanSecureField.swift diff --git a/ChatMLX/Components/UltramanSidebarButtonStyle.swift b/ChatMLX/Core/Components/UltramanSidebarButtonStyle.swift similarity index 100% rename from ChatMLX/Components/UltramanSidebarButtonStyle.swift rename to ChatMLX/Core/Components/UltramanSidebarButtonStyle.swift diff --git a/ChatMLX/Components/UltramanTextEditorWithPlaceholder.swift b/ChatMLX/Core/Components/UltramanTextEditorWithPlaceholder.swift similarity index 98% rename from ChatMLX/Components/UltramanTextEditorWithPlaceholder.swift rename to ChatMLX/Core/Components/UltramanTextEditorWithPlaceholder.swift index a12583c..fa8c620 100644 --- a/ChatMLX/Components/UltramanTextEditorWithPlaceholder.swift +++ b/ChatMLX/Core/Components/UltramanTextEditorWithPlaceholder.swift @@ -61,6 +61,7 @@ struct UltramanTextEditor: NSViewRepresentable { super.init() } + @MainActor func setupPlaceholder(for textView: NSTextView) { let placeholder = NSTextView(frame: textView.bounds) placeholder.isSelectable = false @@ -98,15 +99,16 @@ struct UltramanTextEditor: NSViewRepresentable { if commandSelector == #selector(NSResponder.insertNewline(_:)) { if NSEvent.modifierFlags.contains(.shift) { textView.insertNewlineIgnoringFieldEditor(nil) - return true } else { parent.onSubmit() - return true } + + return true } return false } + @MainActor func updatePlaceholder(for textView: NSTextView) { placeholderView?.isHidden = !textView.string.isEmpty || textView.selectedRange().length > 0 diff --git a/ChatMLX/Components/UltramanTextField.swift b/ChatMLX/Core/Components/UltramanTextField.swift similarity index 100% rename from ChatMLX/Components/UltramanTextField.swift rename to ChatMLX/Core/Components/UltramanTextField.swift diff --git a/ChatMLX/Components/UltramanWindow.swift b/ChatMLX/Core/Components/UltramanWindow.swift similarity index 100% rename from ChatMLX/Components/UltramanWindow.swift rename to ChatMLX/Core/Components/UltramanWindow.swift diff --git a/ChatMLX/Core/Constants.swift b/ChatMLX/Core/Constants.swift new file mode 100644 index 0000000..b56c3d0 --- /dev/null +++ b/ChatMLX/Core/Constants.swift @@ -0,0 +1,10 @@ +// +// Constants.swift +// ChatMLX +// +// Created by John Mai on 2024/10/27. +// + +struct Constants { + static let mainWindowID = "main-window" +} diff --git a/ChatMLX/Core/Errors/ErrorView.swift b/ChatMLX/Core/Errors/ErrorView.swift new file mode 100644 index 0000000..4982dc0 --- /dev/null +++ b/ChatMLX/Core/Errors/ErrorView.swift @@ -0,0 +1,40 @@ +// +// ErrorView.swift +// ChatMLX +// +// Created by John Mai on 2024/10/26. +// + +import SwiftUI + +struct ErrorEnvironmentKey: EnvironmentKey { + static let defaultValue: @Sendable (Error) -> Void = { _ in } +} + +extension EnvironmentValues { + var appError: @Sendable (Error) -> Void { + get { self[ErrorEnvironmentKey.self] } + set { self[ErrorEnvironmentKey.self] = newValue } + } +} + +struct ErrorView: View { + let errorWrapper: ErrorWrapper + + var body: some View { + VStack { + Text("An error has occurred!") + .font(.title) + .padding(.bottom) + Text(errorWrapper.error.localizedDescription) + .font(.headline) + Text(errorWrapper.guidance) + .font(.caption) + .padding(.top) + Spacer() + } + .padding() + .background(.ultraThinMaterial) + .cornerRadius(16) + } +} diff --git a/ChatMLX/Core/Errors/ErrorWrapper.swift b/ChatMLX/Core/Errors/ErrorWrapper.swift new file mode 100644 index 0000000..4464b8d --- /dev/null +++ b/ChatMLX/Core/Errors/ErrorWrapper.swift @@ -0,0 +1,20 @@ +// +// ErrorWrapper.swift +// ChatMLX +// +// Created by John Mai on 2024/10/26. +// + +import Foundation + +struct ErrorWrapper: Identifiable { + let id: UUID + let error: Error + let guidance: String + + init(id: UUID = UUID(), error: Error, guidance: String) { + self.id = id + self.error = error + self.guidance = guidance + } +} diff --git a/ChatMLX/Core/Errors/Errors.swift b/ChatMLX/Core/Errors/Errors.swift new file mode 100644 index 0000000..d190bec --- /dev/null +++ b/ChatMLX/Core/Errors/Errors.swift @@ -0,0 +1,19 @@ +// +// Errors.swift +// ChatMLX +// +// Created by John Mai on 2024/10/27. +// + +import Foundation + +enum Errors: Error, LocalizedError { + case documentDirectoryNotFound + + var errorDescription: String? { + switch self { + case .documentDirectoryNotFound: + "Document directory not found." + } + } +} diff --git a/ChatMLX/Core/Extensions/Binding+Extensions.swift b/ChatMLX/Core/Extensions/Binding+Extensions.swift new file mode 100644 index 0000000..6934988 --- /dev/null +++ b/ChatMLX/Core/Extensions/Binding+Extensions.swift @@ -0,0 +1,24 @@ +// +// 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 }) + } +} + +extension Binding where Value: Sendable { + func asDouble() -> Binding where Value: BinaryInteger { + Binding( + get: { Double(self.wrappedValue) }, + set: { self.wrappedValue = Value($0) } + ) + } +} diff --git a/ChatMLX/Core/Extensions/Bundle+Extensions.swift b/ChatMLX/Core/Extensions/Bundle+Extensions.swift new file mode 100644 index 0000000..b1852af --- /dev/null +++ b/ChatMLX/Core/Extensions/Bundle+Extensions.swift @@ -0,0 +1,26 @@ +// +// Bundle+Extensions.swift +// ChatMLX +// +// Created by John Mai on 2024/10/15. +// + +import Foundation + +extension Bundle { + func getInfoDictionary(_ str: String) -> String? { + infoDictionary?[str] as? String + } + + var version: String { + getInfoDictionary("CFBundleVersion") ?? "Unknown" + } + + var shortVersion: String { + getInfoDictionary("CFBundleShortVersionString") ?? "Unknown" + } + + var name: String { + getInfoDictionary("CFBundleName") ?? "ChatMLX" + } +} diff --git a/ChatMLX/Core/Extensions/Color+Extensions.swift b/ChatMLX/Core/Extensions/Color+Extensions.swift new file mode 100644 index 0000000..17e6ad6 --- /dev/null +++ b/ChatMLX/Core/Extensions/Color+Extensions.swift @@ -0,0 +1,37 @@ +// +// Color+Extensions.swift +// ChatMLX +// +// Created by John Mai on 2024/10/4. +// +import SwiftUI + +extension Color { + init(hex: String) { + let hex = hex.trimmingCharacters(in: CharacterSet.alphanumerics.inverted) + var int: UInt64 = 0 + Scanner(string: hex).scanHexInt64(&int) + let a: UInt64 + let r: UInt64 + let g: UInt64 + let b: UInt64 + switch hex.count { + case 3: // RGB (12-bit) + (a, r, g, b) = (255, (int >> 8) * 17, (int >> 4 & 0xF) * 17, (int & 0xF) * 17) + case 6: // RGB (24-bit) + (a, r, g, b) = (255, int >> 16, int >> 8 & 0xFF, int & 0xFF) + case 8: // ARGB (32-bit) + (a, r, g, b) = (int >> 24, int >> 16 & 0xFF, int >> 8 & 0xFF, int & 0xFF) + default: + (a, r, g, b) = (1, 1, 1, 0) + } + + self.init( + .sRGB, + red: Double(r) / 255, + green: Double(g) / 255, + blue: Double(b) / 255, + opacity: Double(a) / 255 + ) + } +} diff --git a/ChatMLX/Extensions/Date+Extensions.swift b/ChatMLX/Core/Extensions/Date+Extensions.swift similarity index 100% rename from ChatMLX/Extensions/Date+Extensions.swift rename to ChatMLX/Core/Extensions/Date+Extensions.swift diff --git a/ChatMLX/Extensions/Defaults+Extensions.swift b/ChatMLX/Core/Extensions/Defaults+Extensions.swift similarity index 71% rename from ChatMLX/Extensions/Defaults+Extensions.swift rename to ChatMLX/Core/Extensions/Defaults+Extensions.swift index 4d2f0eb..2106b05 100644 --- a/ChatMLX/Extensions/Defaults+Extensions.swift +++ b/ChatMLX/Core/Extensions/Defaults+Extensions.swift @@ -9,7 +9,7 @@ import Defaults import SwiftUI extension Defaults.Keys { - static let defaultModel = Key("defaultModel", default: "") + static let lastLaunchedVersion = Key("lastLaunchedVersion", default: getVersion()) static let language = Key("language", default: .english) static let backgroundBlurRadius = Key("backgroundBlurRadius", default: 35) static let backgroundColor = Key("backgroundColor", default: .black.opacity(0.4)) @@ -18,7 +18,7 @@ extension Defaults.Keys { static let customHuggingFaceEndpoints = Key<[String]>("customHuggingFaceEndpoints", default: []) static let useCustomHuggingFaceEndpoint = Key( "useCustomHuggingFaceEndpoint", default: false) - static let huggingFaceToken = Key("huggingFaceToken", default: nil) + static let huggingFaceToken = Key("huggingFaceToken", default: "") static let defaultTitle = Key("defaultTitle", default: "Default Conversation") static let defaultTemperature = Key("defaultTemperature", default: 0.6) @@ -35,10 +35,20 @@ extension Defaults.Keys { static let defaultUseSystemPrompt = Key("defaultUseSystemPrompt", default: false) static let defaultSystemPrompt = Key("defaultSystemPrompt", default: "") - static let gpuCacheLimit = Key("gpuCacheLimit", default: 128) + static let enableGPUMemorySettings = Key("enableGPUMemorySettings", default: false) + static let gpuCacheLimit = Key("gpuCacheLimit", default: 128) + static let gpuMemoryLimit = Key("gpuMemoryLimit", default: 1024) static let enableAppleIntelligenceEffect = Key( "enableAppleIntelligenceEffect", default: false) static let appleIntelligenceEffectDisplay = Key( "appleIntelligenceEffectDisplay", default: .appInternal) + + static let defaultProvider = Key("defaultProvider", default: .mlx) + static let defaultModel = Key("defaultModel", default: nil) + + static let enableOpenAI = Key("enableOpenAI", default: false) + + static let openAIBaseURL = Key("openAIBaseURL", default: "https://api.openai.com") + static let openAIApiKey = Key("openAIApiKey", default: "") } diff --git a/ChatMLX/Core/Extensions/LabeledContentStyle+Extensions.swift b/ChatMLX/Core/Extensions/LabeledContentStyle+Extensions.swift new file mode 100644 index 0000000..be26e45 --- /dev/null +++ b/ChatMLX/Core/Extensions/LabeledContentStyle+Extensions.swift @@ -0,0 +1,38 @@ +// +// LabeledContentStyle+Extensions.swift +// ChatMLX +// +// Created by John Mai on 2024/10/5. +// + +import SwiftUI + +struct HorizontalLabeledContentStyle: LabeledContentStyle { + func makeBody(configuration: Configuration) -> some View { + HStack { + configuration.label + Spacer() + configuration.content + } + .frame(minHeight: 35) + .padding(.horizontal) + + } +} + +extension LabeledContentStyle where Self == HorizontalLabeledContentStyle { + static var horizontal: HorizontalLabeledContentStyle { .init() } +} + +struct VerticalLabeledContentStyle: LabeledContentStyle { + func makeBody(configuration: Configuration) -> some View { + VStack(alignment: .leading, spacing: 0) { + configuration.label + configuration.content + } + } +} + +extension LabeledContentStyle where Self == VerticalLabeledContentStyle { + static var vertical: VerticalLabeledContentStyle { .init() } +} diff --git a/ChatMLX/Extensions/MarkdownUI+Theme+Extensions.swift b/ChatMLX/Core/Extensions/MarkdownUI+Theme+Extensions.swift similarity index 94% rename from ChatMLX/Extensions/MarkdownUI+Theme+Extensions.swift rename to ChatMLX/Core/Extensions/MarkdownUI+Theme+Extensions.swift index a6aebbd..4b27350 100644 --- a/ChatMLX/Extensions/MarkdownUI+Theme+Extensions.swift +++ b/ChatMLX/Core/Extensions/MarkdownUI+Theme+Extensions.swift @@ -5,11 +5,12 @@ // Created by John Mai on 2024/8/18. // -import MarkdownUI +@preconcurrency import MarkdownUI import SwiftUI +@MainActor extension MarkdownUI.Theme { - static let customGitHub = Theme.gitHub.text { + static let customGitHub = MarkdownUI.Theme.gitHub.text { ForegroundColor(.white) BackgroundColor(.clear) } diff --git a/ChatMLX/Core/Extensions/NSManagedObjectContext+Extensions.swift b/ChatMLX/Core/Extensions/NSManagedObjectContext+Extensions.swift new file mode 100644 index 0000000..1fabf16 --- /dev/null +++ b/ChatMLX/Core/Extensions/NSManagedObjectContext+Extensions.swift @@ -0,0 +1,16 @@ +// +// NSManagedObjectContext+Extensions.swift +// ChatMLX +// +// Created by John Mai on 2024/10/19. +// + +import CoreData + +extension NSManagedObjectContext { + func saveChanges() throws { + if hasChanges { + try save() + } + } +} diff --git a/ChatMLX/Core/Extensions/NSWindow+Extensions.swift b/ChatMLX/Core/Extensions/NSWindow+Extensions.swift new file mode 100644 index 0000000..fd62502 --- /dev/null +++ b/ChatMLX/Core/Extensions/NSWindow+Extensions.swift @@ -0,0 +1,43 @@ +// +// 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 and color. + /// + /// - Parameters: + /// - radius: The blur radius to apply. Defaults to 0 if an error occurs. + /// - color: The background color. Defaults to semi-transparent black. + func setBackgroundBlur(radius: Int, color: NSColor = .black.withAlphaComponent(0.4)) { + guard let connection = CGSDefaultConnectionForThread() else { + NSLog("Failed to get CGS connection") + return + } + + let status = CGSSetWindowBackgroundBlurRadius(connection, windowNumber, radius) + if status != noErr { + NSLog("Error setting blur radius: \(status)") + } + + backgroundColor = .white.withAlphaComponent(0.001) + ignoresMouseEvents = false + } +} + +// MARK: - Private APIs and Helper Functions + +@_silgen_name("CGSDefaultConnectionForThread") +func CGSDefaultConnectionForThread() -> UInt32? + +@_silgen_name("CGSSetWindowBackgroundBlurRadius") +@discardableResult +func CGSSetWindowBackgroundBlurRadius( + _ connection: UInt32, + _ windowNum: NSInteger, + _ radius: Int +) -> OSStatus diff --git a/ChatMLX/Extensions/String+Extensions.swift b/ChatMLX/Core/Extensions/String+Extensions.swift similarity index 100% rename from ChatMLX/Extensions/String+Extensions.swift rename to ChatMLX/Core/Extensions/String+Extensions.swift diff --git a/ChatMLX/Extensions/TimeInterval+Extensions.swift b/ChatMLX/Core/Extensions/TimeInterval+Extensions.swift similarity index 100% rename from ChatMLX/Extensions/TimeInterval+Extensions.swift rename to ChatMLX/Core/Extensions/TimeInterval+Extensions.swift diff --git a/ChatMLX/Extensions/View+Extensions.swift b/ChatMLX/Core/Extensions/View+Extensions.swift similarity index 92% rename from ChatMLX/Extensions/View+Extensions.swift rename to ChatMLX/Core/Extensions/View+Extensions.swift index 7d77253..183b4b3 100644 --- a/ChatMLX/Extensions/View+Extensions.swift +++ b/ChatMLX/Core/Extensions/View+Extensions.swift @@ -8,7 +8,7 @@ import SwiftUI struct SafeAreaInsetsKey: PreferenceKey { - static var defaultValue = EdgeInsets() + static let defaultValue = EdgeInsets() static func reduce(value: inout EdgeInsets, nextValue: () -> EdgeInsets) { value = nextValue() } @@ -45,7 +45,7 @@ extension View { .preference(key: SafeAreaInsetsKey.self, value: proxy.safeAreaInsets) } .onPreferenceChange(SafeAreaInsetsKey.self) { value in - logger.debug("\(id) insets:\(value)") + NSLog("\(id) insets:\(value)") } ) } diff --git a/ChatMLX/Models/AppleIntelligenceEffectDisplay.swift b/ChatMLX/Core/Models/AppleIntelligenceEffectDisplay.swift similarity index 100% rename from ChatMLX/Models/AppleIntelligenceEffectDisplay.swift rename to ChatMLX/Core/Models/AppleIntelligenceEffectDisplay.swift diff --git a/ChatMLX/Models/Conversation+CoreDataClass.swift b/ChatMLX/Core/Models/CoreData Models/Conversation+CoreDataClass.swift similarity index 57% rename from ChatMLX/Models/Conversation+CoreDataClass.swift rename to ChatMLX/Core/Models/CoreData Models/Conversation+CoreDataClass.swift index 6557201..70c313a 100644 --- a/ChatMLX/Models/Conversation+CoreDataClass.swift +++ b/ChatMLX/Core/Models/CoreData Models/Conversation+CoreDataClass.swift @@ -2,7 +2,7 @@ // Conversation+CoreDataClass.swift // ChatMLX // -// Created by John Mai on 2024/10/2. +// Created by John Mai on 2024/11/9. // // @@ -10,6 +10,4 @@ import CoreData import Foundation @objc(Conversation) -public class Conversation: NSManagedObject { - -} +class Conversation: NSManagedObject {} diff --git a/ChatMLX/Core/Models/CoreData Models/Conversation+CoreDataProperties.swift b/ChatMLX/Core/Models/CoreData Models/Conversation+CoreDataProperties.swift new file mode 100644 index 0000000..9c79a2c --- /dev/null +++ b/ChatMLX/Core/Models/CoreData Models/Conversation+CoreDataProperties.swift @@ -0,0 +1,77 @@ +// +// Conversation+CoreDataProperties.swift +// ChatMLX +// +// Created by John Mai on 2024/11/9. +// +// + +import CoreData +import Foundation + +extension Conversation { + @nonobjc class func fetchRequest() -> NSFetchRequest { + NSFetchRequest(entityName: "Conversation") + } + + @NSManaged var createdAt: Date? + @NSManaged var generateTime: Double + @NSManaged var inferring: Bool + @NSManaged var maxLength: Int + @NSManaged var maxMessagesLimit: Int32 + @NSManaged var modelType: String? + @NSManaged var modelRaw: String? + @NSManaged var promptTime: Double + @NSManaged var promptTokensPerSecond: Double + @NSManaged var repetitionContextSize: Int + @NSManaged var repetitionPenalty: Float + @NSManaged var systemPrompt: String? + @NSManaged var temperature: Float + @NSManaged var title: String + @NSManaged var tokensPerSecond: Double + @NSManaged var topP: Float + @NSManaged var updatedAt: Date? + @NSManaged var useMaxLength: Bool + @NSManaged var useMaxMessagesLimit: Bool + @NSManaged var useRepetitionPenalty: Bool + @NSManaged var useSystemPrompt: Bool + @NSManaged var modelValue: String? + @NSManaged var modelProvider: String? + @NSManaged var messages: [Message] +} + +// MARK: Generated accessors for messages + +extension Conversation { + @objc(insertObject:inMessagesAtIndex:) + @NSManaged func insertIntoMessages(_ value: Message, at idx: Int) + + @objc(removeObjectFromMessagesAtIndex:) + @NSManaged func removeFromMessages(at idx: Int) + + @objc(insertMessages:atIndexes:) + @NSManaged func insertIntoMessages(_ values: [Message], at indexes: NSIndexSet) + + @objc(removeMessagesAtIndexes:) + @NSManaged func removeFromMessages(at indexes: NSIndexSet) + + @objc(replaceObjectInMessagesAtIndex:withObject:) + @NSManaged func replaceMessages(at idx: Int, with value: Message) + + @objc(replaceMessagesAtIndexes:withMessages:) + @NSManaged func replaceMessages(at indexes: NSIndexSet, with values: [Message]) + + @objc(addMessagesObject:) + @NSManaged func addToMessages(_ value: Message) + + @objc(removeMessagesObject:) + @NSManaged func removeFromMessages(_ value: Message) + + @objc(addMessages:) + @NSManaged func addToMessages(_ values: NSOrderedSet) + + @objc(removeMessages:) + @NSManaged func removeFromMessages(_ values: NSOrderedSet) +} + +extension Conversation: Identifiable {} diff --git a/ChatMLX/Core/Models/CoreData Models/Conversation.swift b/ChatMLX/Core/Models/CoreData Models/Conversation.swift new file mode 100644 index 0000000..d495fb3 --- /dev/null +++ b/ChatMLX/Core/Models/CoreData Models/Conversation.swift @@ -0,0 +1,142 @@ +// +// Conversation.swift +// ChatMLX +// +// Created by John Mai on 2024/10/14. +// + +import CoreData +import Defaults + +extension Conversation { + // var modelIdentifier: ProviderModel.Identifier? { + // get { + // guard let modelType, let modelValue, let modelProvider, let provider = Provider(rawValue: modelProvider) else { + // return nil + // } + // + // switch modelType { + // case "id": + // return .id(modelValue, provider) + // case "directory": + // guard let url = URL(string: modelValue) else { return nil } + // return .directory(url, provider) + // default: + // return nil + // } + // } + // set { + // switch newValue { + // case .id(let idString, let provider): + // modelType = "id" + // modelValue = idString + // modelProvider = provider.rawValue + // case .directory(let url, let provider): + // modelType = "directory" + // modelValue = url.absoluteString + // modelProvider = provider.rawValue + // case nil: + // modelType = nil + // modelValue = nil + // modelProvider = nil + // } + // } + // } + + var model: ProviderModel.Identifier? { + get { + guard let modelRaw = modelRaw?.data(using: .utf8) else { return nil } + return try? JSONDecoder().decode(ProviderModel.Identifier.self, from: modelRaw) + } + set { + guard let data = try? JSONEncoder().encode(newValue) else { return } + modelRaw = String(data: data, encoding: .utf8) + } + } + + override public 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)) + } + + override public func willSave() { + super.willSave() + setPrimitiveValue(Date.now, forKey: #keyPath(Conversation.updatedAt)) + } + + func getLastAssistantMessage(context: NSManagedObjectContext) -> Message { + if let message = messages.last, message.role == .assistant { + message + } else { + Message(context: context).assistant(conversation: self) + } + } + + func prepareMessages() -> [[String: String]] { + var messages = self.messages + if self.useMaxMessagesLimit { + let maxCount = self.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 self.useSystemPrompt, let systemPrompt = self.systemPrompt, !systemPrompt.isEmpty { + dictionary.insert( + self.formatMessage( + role: .system, + content: systemPrompt), + at: 0) + } + + return dictionary + } + + private func formatMessage(role: Role, content: String) -> [String: String] { + [ + "role": role.rawValue, + "content": content, + ] + } +} + +extension Conversation: @unchecked Sendable {} diff --git a/ChatMLX/Core/Models/CoreData Models/Message+CoreDataClass.swift b/ChatMLX/Core/Models/CoreData Models/Message+CoreDataClass.swift new file mode 100644 index 0000000..f0b1bcf --- /dev/null +++ b/ChatMLX/Core/Models/CoreData Models/Message+CoreDataClass.swift @@ -0,0 +1,13 @@ +// +// Message+CoreDataClass.swift +// ChatMLX +// +// Created by John Mai on 2024/10/14. +// +// + +import CoreData +import Foundation + +@objc(Message) +class Message: NSManagedObject {} diff --git a/ChatMLX/Core/Models/CoreData Models/Message+CoreDataProperties.swift b/ChatMLX/Core/Models/CoreData Models/Message+CoreDataProperties.swift new file mode 100644 index 0000000..694c978 --- /dev/null +++ b/ChatMLX/Core/Models/CoreData Models/Message+CoreDataProperties.swift @@ -0,0 +1,26 @@ +// +// Message+CoreDataProperties.swift +// ChatMLX +// +// Created by John Mai on 2024/10/14. +// +// + +import CoreData +import Foundation + +extension Message { + @nonobjc class func fetchRequest() -> NSFetchRequest { + NSFetchRequest(entityName: "Message") + } + + @NSManaged var content: String + @NSManaged var createdAt: Date? + @NSManaged var error: String? + @NSManaged var inferring: Bool + @NSManaged var roleRaw: String + @NSManaged var updatedAt: Date? + @NSManaged var conversation: Conversation? +} + +extension Message: Identifiable {} diff --git a/ChatMLX/Core/Models/CoreData Models/Message.swift b/ChatMLX/Core/Models/CoreData Models/Message.swift new file mode 100644 index 0000000..8ec2d88 --- /dev/null +++ b/ChatMLX/Core/Models/CoreData Models/Message.swift @@ -0,0 +1,69 @@ +// +// Message.swift +// ChatMLX +// +// Created by John Mai on 2024/10/14. +// + +import Foundation + +extension Message { + var role: Role { + get { Role(rawValue: roleRaw) ?? .user } + set { roleRaw = newValue.rawValue } + } + + 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)) + } + + @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, + ] + } + + func suffixMessages() -> [Message] { + guard let conversation = self.conversation else { + return [] + } + + let messages = conversation.messages + + guard let index = messages.firstIndex(of: self) else { + return [] + } + + return Array(messages[index...]) + } +} + +extension Message: @unchecked Sendable {} diff --git a/ChatMLX/Core/Models/CoreData Models/ModelInfo+CoreDataClass.swift b/ChatMLX/Core/Models/CoreData Models/ModelInfo+CoreDataClass.swift new file mode 100644 index 0000000..38a6943 --- /dev/null +++ b/ChatMLX/Core/Models/CoreData Models/ModelInfo+CoreDataClass.swift @@ -0,0 +1,13 @@ +// +// ModelInfo+CoreDataClass.swift +// ChatMLX +// +// Created by John Mai on 2024/10/14. +// +// + +import CoreData +import Foundation + +@objc(ModelInfo) +class ModelInfo: NSManagedObject {} diff --git a/ChatMLX/Core/Models/CoreData Models/ModelInfo+CoreDataProperties.swift b/ChatMLX/Core/Models/CoreData Models/ModelInfo+CoreDataProperties.swift new file mode 100644 index 0000000..4af2e75 --- /dev/null +++ b/ChatMLX/Core/Models/CoreData Models/ModelInfo+CoreDataProperties.swift @@ -0,0 +1,43 @@ +// +// ModelInfo+CoreDataProperties.swift +// ChatMLX +// +// Created by John Mai on 2024/10/14. +// +// + +import CoreData +import Foundation + +extension ModelInfo { + @nonobjc class func fetchRequest() -> NSFetchRequest { + NSFetchRequest(entityName: "ModelInfo") + } + + @NSManaged var id: String + @NSManaged var providerRaw: String + @NSManaged var name: String? + @NSManaged var path: URL? + @NSManaged var maxInputLength: Int + @NSManaged var maxOutputLength: Int + @NSManaged var toolCall: Bool + @NSManaged var vision: Bool +} + +// MARK: Generated accessors for conversation + +extension ModelInfo { + @objc(addConversationObject:) + @NSManaged func addToConversation(_ value: Conversation) + + @objc(removeConversationObject:) + @NSManaged func removeFromConversation(_ value: Conversation) + + @objc(addConversation:) + @NSManaged func addToConversation(_ values: NSSet) + + @objc(removeConversation:) + @NSManaged func removeFromConversation(_ values: NSSet) +} + +extension ModelInfo: Identifiable {} diff --git a/ChatMLX/Core/Models/CoreData Models/ModelInfo.swift b/ChatMLX/Core/Models/CoreData Models/ModelInfo.swift new file mode 100644 index 0000000..e258bba --- /dev/null +++ b/ChatMLX/Core/Models/CoreData Models/ModelInfo.swift @@ -0,0 +1,15 @@ +// +// ModelInfo.swift +// ChatMLX +// +// Created by John Mai on 2024/10/14. +// + +extension ModelInfo { + var provider: Provider { + get { Provider(rawValue: providerRaw ?? "mlx") ?? .mlx } + set { providerRaw = newValue.rawValue } + } +} + +extension ModelInfo: @unchecked Sendable {} diff --git a/ChatMLX/Models/DisplayStyle.swift b/ChatMLX/Core/Models/DisplayStyle.swift similarity index 100% rename from ChatMLX/Models/DisplayStyle.swift rename to ChatMLX/Core/Models/DisplayStyle.swift diff --git a/ChatMLX/Core/Models/DownloadTask.swift b/ChatMLX/Core/Models/DownloadTask.swift new file mode 100644 index 0000000..c37256e --- /dev/null +++ b/ChatMLX/Core/Models/DownloadTask.swift @@ -0,0 +1,103 @@ +// +// DownloadTask.swift +// ChatMLX +// +// Created by John Mai on 2024/8/11. +// + +import Defaults +import Foundation +import os + +@Observable +class DownloadTask: Identifiable, Equatable { + private let logger = Logger(subsystem: "com.johnmai.ChatMLX", category: "DownloadTask") + + 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 +// +// // todo: token & custom endpoint +// 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 self.moveToDocumentsDirectory(from: temporaryModelDirectory) +// +// await MainActor.run { +// self.isDownloading = false +// self.isCompleted = true +// self.progress = 1.0 +// } +// } catch { +// self.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) + + self.logger.info("Model moved to: \(destinationPath.path)") + } +} diff --git a/ChatMLX/Models/Language.swift b/ChatMLX/Core/Models/Language.swift similarity index 98% rename from ChatMLX/Models/Language.swift rename to ChatMLX/Core/Models/Language.swift index e6cf078..f710fde 100644 --- a/ChatMLX/Models/Language.swift +++ b/ChatMLX/Core/Models/Language.swift @@ -92,7 +92,7 @@ enum Language: String, CaseIterable, Identifiable, Defaults.Serializable { case .thai: return "ไทย" case .turkish: return "Türkçe" case .ukrainian: return "Українська" - case .vietnamese: return "Tiếng Việt" + case .vietnamese: return "Tiếng Việt" } } } diff --git a/ChatMLX/Models/LocalModel.swift b/ChatMLX/Core/Models/LocalModel.swift similarity index 100% rename from ChatMLX/Models/LocalModel.swift rename to ChatMLX/Core/Models/LocalModel.swift diff --git a/ChatMLX/Models/LocalModelGroup.swift b/ChatMLX/Core/Models/LocalModelGroup.swift similarity index 100% rename from ChatMLX/Models/LocalModelGroup.swift rename to ChatMLX/Core/Models/LocalModelGroup.swift diff --git a/ChatMLX/Core/Models/Migrations/V2MigrationPolicy.swift b/ChatMLX/Core/Models/Migrations/V2MigrationPolicy.swift new file mode 100644 index 0000000..249fa73 --- /dev/null +++ b/ChatMLX/Core/Models/Migrations/V2MigrationPolicy.swift @@ -0,0 +1,33 @@ +// +// V2MigrationPolicy.swift +// ChatMLX +// +// Created by John Mai on 2024/10/14. +// + +import CoreData + +class V2MigrationPolicy: NSEntityMigrationPolicy { + override func createDestinationInstances( + forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager + ) throws { + let sourceKeys = sInstance.entity.attributesByName.keys + let sourceValues = sInstance.dictionaryWithValues(forKeys: sourceKeys.map { $0 as String }) + + let destinationInstance = NSEntityDescription.insertNewObject( + forEntityName: mapping.destinationEntityName!, into: manager.destinationContext) + let destinationKeys = destinationInstance.entity.attributesByName.keys.map { $0 as String } + + for key in destinationKeys { + if let value = sourceValues[key] { + destinationInstance.setValue(value, forKey: key) + } + } + + if sourceValues["model"] is String { + destinationInstance.setValue(nil, forKey: "model") + } + + manager.associate(sourceInstance: sInstance, withDestinationInstance: destinationInstance, for: mapping) + } +} diff --git a/ChatMLX/Core/Models/Model.swift b/ChatMLX/Core/Models/Model.swift new file mode 100644 index 0000000..f44357c --- /dev/null +++ b/ChatMLX/Core/Models/Model.swift @@ -0,0 +1,6 @@ +// +// Model.swift +// ChatMLX +// +// Created by John Mai on 2024/11/9. +// diff --git a/ChatMLX/Core/Models/ModelType.swift b/ChatMLX/Core/Models/ModelType.swift new file mode 100644 index 0000000..1e8d650 --- /dev/null +++ b/ChatMLX/Core/Models/ModelType.swift @@ -0,0 +1,29 @@ +// +// TaskType.swift +// ChatMLX +// +// Created by John Mai on 2024/10/20. +// + +import Foundation +import SwiftUI + +enum ModelType: String { + case textGeneration = "text-generation" + case imageTextToText = "image-text-to-text" + case anyToAny = "any-to-any" + case unknown + + var name: LocalizedStringKey { + switch self { + case .textGeneration: + "Text Generation" + case .imageTextToText: + "Image Text to Text (Vision)" + case .anyToAny: + "Any to Any" + case .unknown: + "Unknown" + } + } +} diff --git a/ChatMLX/Core/Models/Provider.swift b/ChatMLX/Core/Models/Provider.swift new file mode 100644 index 0000000..550a455 --- /dev/null +++ b/ChatMLX/Core/Models/Provider.swift @@ -0,0 +1,12 @@ +// +// Provider.swift +// ChatMLX +// +// Created by John Mai on 2024/10/21. +// +import Defaults + +enum Provider: String, Codable, Defaults.Serializable { + case mlx = "MLX" + case openAI = "OpenAI" +} diff --git a/ChatMLX/Core/Models/ProviderModel.swift b/ChatMLX/Core/Models/ProviderModel.swift new file mode 100644 index 0000000..0ad1444 --- /dev/null +++ b/ChatMLX/Core/Models/ProviderModel.swift @@ -0,0 +1,92 @@ +// +// ProviderModel.swift +// ChatMLX +// +// Created by John Mai on 2024/10/14. +// + +import Defaults +import Foundation + +struct ProviderModel: Identifiable { + enum Identifier: Sendable, Equatable, Hashable, Codable, Defaults.Serializable { + case id(String, Provider) + case directory(URL, Provider) + + private enum IdentifierType: String, Codable { + case id + case directory + } + + private enum CodingKeys: String, CodingKey { + case type + case provider + case model + } + + func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .id(let model, let provider): + try container.encode(IdentifierType.id, forKey: .type) + try container.encode(model, forKey: .model) + try container.encode(provider, forKey: .provider) + + case .directory(let url, let provider): + try container.encode(IdentifierType.directory, forKey: .type) + try container.encode(url.absoluteString, forKey: .model) + try container.encode(provider, forKey: .provider) + } + } + + init(from decoder: any Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(IdentifierType.self, forKey: .type) + switch type { + case .id: + let model = try container.decode(String.self, forKey: .model) + let provider = try container.decode(Provider.self, forKey: .provider) + self = .id(model, provider) + case .directory: + let model = try container.decode(String.self, forKey: .model) + let provider = try container.decode(Provider.self, forKey: .provider) + self = .directory(URL(string: model)!, provider) + } + } + } + + let id: Identifier + let name: String? + let path: URL? + let maxInputLength: Int? + let maxOutputLength: Int? + let toolCall: Bool + let vision: Bool + + init( + id: Identifier, + name: String? = nil, + path: URL? = nil, + maxInputLength: Int? = nil, + maxOutputLength: Int? = nil, + toolCall: Bool = false, + vision: Bool = false + ) { + self.id = id + self.name = name + self.path = path + self.maxInputLength = maxInputLength + self.maxOutputLength = maxOutputLength + self.toolCall = toolCall + self.vision = vision + } +} + +extension ProviderModel: Equatable { + static func == (lhs: ProviderModel, rhs: ProviderModel) -> Bool { + lhs.id == rhs.id + } +} + +extension ProviderModel: Hashable {} diff --git a/ChatMLX/Models/RemoteModel.swift b/ChatMLX/Core/Models/RemoteModel.swift similarity index 100% rename from ChatMLX/Models/RemoteModel.swift rename to ChatMLX/Core/Models/RemoteModel.swift diff --git a/ChatMLX/Models/Role.swift b/ChatMLX/Core/Models/Role.swift similarity index 80% rename from ChatMLX/Models/Role.swift rename to ChatMLX/Core/Models/Role.swift index 14819da..af7123e 100644 --- a/ChatMLX/Models/Role.swift +++ b/ChatMLX/Core/Models/Role.swift @@ -5,10 +5,11 @@ // Created by John Mai on 2024/10/3. // -public enum Role: String, Codable { +enum Role: String, Codable { case user case assistant case system + case tool var description: String { "\(self)" diff --git a/ChatMLX/Models/SettingsTab.swift b/ChatMLX/Core/Models/SettingsTab.swift similarity index 90% rename from ChatMLX/Models/SettingsTab.swift rename to ChatMLX/Core/Models/SettingsTab.swift index 61629ea..bb2278b 100644 --- a/ChatMLX/Models/SettingsTab.swift +++ b/ChatMLX/Core/Models/SettingsTab.swift @@ -17,6 +17,7 @@ struct SettingsTab: Identifiable, Equatable { case defaultConversation = "Default Conversation" case huggingFace = "Hugging Face" case models = "Models" + case providers = "Providers" case mlxCommunity = "MLX Community" case downloadManager = "Download Manager" case experimentalFeatures = "Experimental Features" @@ -25,9 +26,9 @@ struct SettingsTab: Identifiable, Equatable { let id: ID let icon: Image - let showIndicator: ((SettingsViewModel) -> Bool)? + let showIndicator: (() -> Bool)? - init(_ id: ID, _ icon: Image, showIndicator: ((SettingsViewModel) -> Bool)? = nil) { + init(_ id: ID, _ icon: Image, showIndicator: (() -> Bool)? = nil) { self.id = id self.icon = icon self.showIndicator = showIndicator diff --git a/ChatMLX/Models/SettingsTabGroup.swift b/ChatMLX/Core/Models/SettingsTabGroup.swift similarity index 100% rename from ChatMLX/Models/SettingsTabGroup.swift rename to ChatMLX/Core/Models/SettingsTabGroup.swift diff --git a/ChatMLX/Models/Styles.swift b/ChatMLX/Core/Models/Styles.swift similarity index 100% rename from ChatMLX/Models/Styles.swift rename to ChatMLX/Core/Models/Styles.swift diff --git a/ChatMLX/Core/Persistence/PersistenceController.swift b/ChatMLX/Core/Persistence/PersistenceController.swift new file mode 100644 index 0000000..793dc27 --- /dev/null +++ b/ChatMLX/Core/Persistence/PersistenceController.swift @@ -0,0 +1,133 @@ +// +// Persistence.swift +// ChatMLX +// +// Created by John Mai on 2024/10/2. +// + +import CoreData + +struct PersistenceController { + static let shared = PersistenceController() + + @MainActor + static let preview: PersistenceController = { + let controller = PersistenceController(inMemory: true) + return controller + }() + + let container: NSPersistentContainer + + init(inMemory: Bool = false) { + container = NSPersistentContainer(name: "ChatMLX2") + if inMemory { + container.persistentStoreDescriptions.first!.url = URL(fileURLWithPath: "/dev/null") + } + + container.viewContext.automaticallyMergesChangesFromParent = true + + if let description = container.persistentStoreDescriptions.first { + description.shouldMigrateStoreAutomatically = true + description.shouldInferMappingModelAutomatically = false + } + + container.loadPersistentStores(completionHandler: { _, error in + if let error = error as NSError? { + fatalError("Unresolved error \(error), \(error.userInfo)") + } + }) + } + + func newBackgroundContext() -> NSManagedObjectContext { + let context = container.newBackgroundContext() + context.automaticallyMergesChangesFromParent = true + context.mergePolicy = NSMergePolicy.mergeByPropertyStoreTrump + return context + } + + var viewContext: NSManagedObjectContext { + container.viewContext + } + + // 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.detached(priority: .background) { + // try await viewContext.saveIfNeeded() + // } + // } + + func executeAndMergeChanges(using request: NSBatchDeleteRequest, in context: NSManagedObjectContext) throws { + try executeAndMergeChanges(using: [request], in: context) + } + + func executeAndMergeChanges(using requests: [NSBatchDeleteRequest], in context: NSManagedObjectContext) throws { + let changes = try requests.flatMap { try execute(request: $0, in: context) } + mergeChanges(changes) + } + + private func execute(request: NSBatchDeleteRequest, in context: NSManagedObjectContext) throws + -> [NSManagedObjectID] + { + request.resultType = .resultTypeObjectIDs + let result = try context.execute(request) as? NSBatchDeleteResult + return result?.result as? [NSManagedObjectID] ?? [] + } + + private func mergeChanges( + _ changes: [NSManagedObjectID], + in context: NSManagedObjectContext? = nil + ) { + guard !changes.isEmpty else { return } + + NSManagedObjectContext.mergeChanges( + fromRemoteContextSave: [NSDeletedObjectsKey: changes], + into: [context ?? viewContext] + ) + } + + func delete( + _ id: NSManagedObjectID, + in context: NSManagedObjectContext, + saveImmediately: Bool = true + ) throws { + let object = try context.existingObject(with: id) + context.delete(object) + if saveImmediately { + // try save() + } + } +} diff --git a/ChatMLX/Core/Persistence/Repository.swift b/ChatMLX/Core/Persistence/Repository.swift new file mode 100644 index 0000000..aae5f28 --- /dev/null +++ b/ChatMLX/Core/Persistence/Repository.swift @@ -0,0 +1,91 @@ +// +// Repository.swift +// ChatMLX +// +// Created by John Mai on 2024/11/3. +// + +import CoreData +import CoreDataEvolution + +@NSModelActor +actor Repository { + @discardableResult + func createConversation() throws -> NSManagedObjectID { + let conversation = Conversation(context: modelContext) + try modelContext.saveChanges() + return conversation.objectID + } + + private func deleteConversation(_ conversation: Conversation) throws { + modelContext.delete(conversation) + try modelContext.saveChanges() + } + + func deleteConversation(_ objectID: NSManagedObjectID) throws { + guard let conversation = self[objectID, as: Conversation.self] else { + fatalError("Can't load Conversation by ID:\(objectID)") + } + try deleteConversation(conversation) + } + + func deleteMessages(_ messages: [Message]) throws { + try deleteMessages(messages.map(\.objectID)) + } + + func deleteMessages(_ objectIDs: [NSManagedObjectID]) throws { + let request = NSBatchDeleteRequest(objectIDs: objectIDs) + request.resultType = .resultTypeObjectIDs + let result = try modelContext.execute(request) as? NSBatchDeleteResult + let changes = result?.result as? [NSManagedObjectID] ?? [] + guard !changes.isEmpty else { return } + + mergeChanges([NSDeletedObjectIDsKey: changes]) + } + + func mergeChanges(_ fromRemoteContextSave: [AnyHashable: Any]) { + NSManagedObjectContext.mergeChanges( + fromRemoteContextSave: fromRemoteContextSave, + into: [modelContainer.viewContext] + ) + } + + func clearMessages() throws -> [NSManagedObjectID] { + let request = NSBatchDeleteRequest(fetchRequest: Message.fetchRequest()) + request.resultType = .resultTypeObjectIDs + + let result = try modelContext.execute(request) as? NSBatchDeleteResult + + return result?.result as? [NSManagedObjectID] ?? [] + } + + func clearConversations() throws -> [NSManagedObjectID] { + let request = NSBatchDeleteRequest(fetchRequest: Conversation.fetchRequest()) + request.resultType = .resultTypeObjectIDs + + let result = try modelContext.execute(request) as? NSBatchDeleteResult + + return result?.result as? [NSManagedObjectID] ?? [] + } + + @discardableResult + func createMessage(in conversationObjectID: NSManagedObjectID, content: String, role: Role) throws + -> NSManagedObjectID + { + try createMessage(in: conversationObjectID, content: content, role: role).objectID + } + + @discardableResult + func createMessage(in conversationObjectID: NSManagedObjectID, content: String, role: Role) throws -> Message { + guard let conversation = self[conversationObjectID, as: Conversation.self] else { + fatalError("Can't load Conversation by ID:\(conversationObjectID)") + } + + let message = Message(context: modelContext) + message.conversation = conversation + message.content = content + message.role = role + try modelContext.saveChanges() + return message + } +} diff --git a/ChatMLX/Core/Services/AI/BaseProvider.swift b/ChatMLX/Core/Services/AI/BaseProvider.swift new file mode 100644 index 0000000..42c306d --- /dev/null +++ b/ChatMLX/Core/Services/AI/BaseProvider.swift @@ -0,0 +1,71 @@ +// +// BaseProvider.swift +// ChatMLX +// +// Created by John Mai on 2024/10/13. +// + +import Foundation +import MLXLLM + +struct ModelConfig { + struct Model { + var name: String + var path: URL? + } + + let model: Model + let temperature: Double? = nil + let useMaxTokens: Bool = false + let maxTokens: Int? = nil + let topP: Double? = nil + let frequencyPenalty: Double? = nil + let repetitionContextSize: Int? = nil + let presencePenalty: Double? = nil + let stop: [String]? = nil +} + +extension GenerateParameters { + init(from config: ModelConfig) { + self.init( + temperature: Float(config.temperature ?? 0.6), + topP: Float(config.topP ?? 1.0), + repetitionPenalty: config.frequencyPenalty.map(Float.init), + repetitionContextSize: config.repetitionContextSize ?? 20 + ) + } +} + +extension ModelConfig: Sendable {} + +struct Usage { + let promptTokens: Int? + let promptTime: Double? + let promptTokensPerSecond: Double? + let completionTokens: Int? + let completionTime: Double? + let completionTokensPerSecond: Double? + let totalTokens: Int? +} + +struct ChatResult { + let content: String + let usage: Usage? +} + +typealias ChatResultHandler = (Result) -> Void + +//enum ProviderModel { +// case name(String) +// case path(URL) +//} + +protocol BaseProvider { + func chat( + messages: [[String: String]], + config: ModelConfig, + onResult: @escaping ChatResultHandler + ) async + + static func fetchModels() throws -> [ProviderModel] +} diff --git a/ChatMLX/Core/Services/AI/MLXProvider.swift b/ChatMLX/Core/Services/AI/MLXProvider.swift new file mode 100644 index 0000000..de0bdf2 --- /dev/null +++ b/ChatMLX/Core/Services/AI/MLXProvider.swift @@ -0,0 +1,158 @@ +//// +//// MLXProvider.swift +//// ChatMLX +//// +//// Created by John Mai on 2024/10/13. +//// +// +//import Defaults +//import Metal +//import MLX +//import MLXLLM +//import MLXRandom +//import os +//import SwiftUI +//import Tokenizers +// +//struct MLXProvider: BaseProvider { +// // MARK: - Properties +// +// private let displayEveryNTokens = 4 +// private let maxTokens = 240 +// private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "MLXProvider") +// private let fileManager = FileManager.default +// +// // MARK: - Chat +// +// func chat( +// messages: [[String: String]], +// config: ModelConfig, +// onResult: @escaping ChatResultHandler +// ) async { +// do { +// guard let modelPath = config.model.path else { +// throw LLMRunnerError.modelConfigurationNotSet +// } +// +// let modelContainer = try await MLXLLM.ModelContainer( +// hub: .init(), +// modelDirectory: modelPath, +// configuration: ModelConfiguration(directory: modelPath) +// ) +// +// 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: GenerateParameters(from: config), +// model: model, +// tokenizer: tokenizer, +// extraEOSTokens: Set(config.stop ?? [ +// "<|im_end|>", +// "<|end|>", +// ]) +// ) { tokens in +// if tokens.count % displayEveryNTokens == 0 { +// let content = tokenizer.decode(tokens: tokens) +// onResult( +// .success( +// .init( +// content: content, +// usage: nil +// ) +// ) +// ) +// } +// +// if config.useMaxTokens, tokens.count >= config.maxTokens ?? maxTokens { +// return .stop +// } else { +// return .more +// } +// } +// } +// +// onResult( +// .success( +// .init( +// content: result.output, +// usage: .init( +// promptTokens: result.promptTokens.count, +// promptTime: result.promptTime, +// promptTokensPerSecond: result.promptTokensPerSecond, +// completionTokens: result.tokens.count, +// completionTime: result.generateTime, +// completionTokensPerSecond: result.tokensPerSecond, +// totalTokens: result.tokens.count + result.promptTokens.count +// ) +// ) +// ) +// ) +// } catch { +// onResult(.failure(error)) +// } +// } +// +// // MARK: - Fetch Models +// +// static func fetchModels() throws -> [ProviderModel] { +// let fileManager = FileManager.default +// +// guard let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first else { +// return [] +// } +// +// let organizations = try fileManager.contentsOfDirectory( +// at: FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("huggingface/models"), +// includingPropertiesForKeys: nil, +// options: [.skipsHiddenFiles] +// ) +// +// guard !organizations.isEmpty else { +// return [] +// } +// +// return try organizations.flatMap { organization -> [ProviderModel] in +// guard organization.hasDirectoryPath else { +// return [] +// } +// +// let huggingfaceModels = try fileManager.contentsOfDirectory( +// at: organization, +// includingPropertiesForKeys: nil, +// options: [.skipsHiddenFiles] +// ) +// +//// return try huggingfaceModels.compactMap { model -> ProviderModel? in +//// guard model.hasDirectoryPath else { +//// return nil +//// } +//// +//// let data = try Data(contentsOf: model.appendingPathComponent("tokenizer_config.json")) +//// let tokenizerConfig = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] +//// +//// let modelMaxLength = tokenizerConfig?["model_max_length"] as? Int +//// let toolCall = (tokenizerConfig?["chat_template"] as? String)?.contains("tool_calls") ?? false +//// let vision = false +//// +//// return .init( +//// id: model.absoluteString, +//// provider: .mlx, +//// name: model.lastPathComponent, +//// path: model, +//// maxInputLength: modelMaxLength, +//// maxOutputLength: modelMaxLength, +//// toolCall: toolCall, +//// vision: false +//// ) +//// } +// return [] +// } +// } +//} diff --git a/ChatMLX/Core/Services/AI/OpenAIProvider.swift b/ChatMLX/Core/Services/AI/OpenAIProvider.swift new file mode 100644 index 0000000..ec999ff --- /dev/null +++ b/ChatMLX/Core/Services/AI/OpenAIProvider.swift @@ -0,0 +1,108 @@ +//// +//// OpenAIProvider.swift +//// ChatMLX +//// +//// Created by John Mai on 2024/10/17. +//// +// +//import Defaults +//import Foundation +//import OpenAI +//import SwiftUI +// +//struct OpenAIProvider: BaseProvider { +// static func fetchModels() -> [ProviderModel] { +// [ +// // GPT-4o +// .init( +// id: "gpt-4o", +// provider: .openAI, +// name: "GPT-4o", +// maxInputLength: 128000, +// maxOutputLength: 16384, +// toolCall: true, +// vision: true +// ), +// // GPT-4o mini +// .init( +// id: "gpt-4o-mini", +// provider: .openAI, +// name: "GPT-4o Mini", +// maxInputLength: 128000, +// maxOutputLength: 16384, +// toolCall: true, +// vision: true +// ), +// // o1-preview and o1-mini +// .init( +// id: "o1-preview", +// provider: .openAI, +// name: "O1 Preview", +// maxInputLength: 128000, +// maxOutputLength: 32768 +// ), +// .init( +// id: "o1-mini", +// provider: .openAI, +// name: "O1 Mini", +// maxInputLength: 128000, +// maxOutputLength: 65536 +// ), +// // GPT-4 Turbo and GPT-4 +// .init( +// id: "gpt-4-turbo", +// provider: .openAI, +// name: "GPT-4 Turbo", +// maxInputLength: 128000, +// maxOutputLength: 4096 +// ), +// .init( +// id: "gpt-4", +// provider: .openAI, +// name: "GPT-4", +// maxInputLength: 8192, +// maxOutputLength: 8192 +// ), +// // GPT-3.5 Turbo +// .init( +// id: "gpt-3.5-turbo", +// provider: .openAI, +// name: "GPT-3.5 Turbo", +// maxInputLength: 16385, +// maxOutputLength: 4096 +// ) +// ] +// } +// +// func chat( +// messages: [[String: String]], +// config: ModelConfig, +// onResult: @escaping ChatResultHandler +// ) async { +// let apiKey = Defaults[.openAIApiKey] +// let baseURL = Defaults[.openAIBaseURL] +// +// let client = OpenAI(configuration: .init(token: apiKey, host: baseURL)) +// +// var chatMessages: [ChatQuery.ChatCompletionMessageParam] = [] +// for message in messages { +// chatMessages.append(.init(role: message["role"] == "user" ? .user : .assistant, content: message["content"]!)!) +// } +// +// let query = ChatQuery(messages: chatMessages, model: config.model.name) +// +// do { +// var content = "" +// for try await result in client.chatsStream(query: query) { +// for choice in result.choices { +// if let c = choice.delta.content { +// content += c +// } +// onResult(.success(.init(content: content, usage: nil))) +// } +// } +// } catch { +// onResult(.failure(error)) +// } +// } +//} diff --git a/ChatMLX/Core/Services/AI/ProviderFactory.swift b/ChatMLX/Core/Services/AI/ProviderFactory.swift new file mode 100644 index 0000000..9e0218a --- /dev/null +++ b/ChatMLX/Core/Services/AI/ProviderFactory.swift @@ -0,0 +1,46 @@ +//// +//// ProviderFactory.swift +//// ChatMLX +//// +//// Created by John Mai on 2024/10/13. +//// +// +//import Defaults +// +//actor ProviderFactory { +// static let shared = ProviderFactory() +// +// private init() {} +// +// private var providers: [Provider: BaseProvider] = [:] +// +// func provider(_ type: Provider) async -> BaseProvider { +//// if let provider = providers[type] { +//// return provider +//// } +//// +//// let provider: BaseProvider = switch type { +//// case .mlx: +//// MLXProvider() +//// case .openAI: +//// OpenAIProvider() +//// } +//// +//// providers[type] = provider +//// +//// return provider +// return MLXProvider() +// } +// +//// func fetchModels() -> [ProviderModel] { +//// var models: [ProviderModel] = [] +//// +//// models = models + MLXProvider.fetchModels() +//// +//// if Defaults[.enableOpenAI] { +//// models = models + OpenAIProvider.fetchModels() +//// } +//// +//// return models +//// } +//} diff --git a/ChatMLX/Core/Services/ChatService.swift b/ChatMLX/Core/Services/ChatService.swift new file mode 100644 index 0000000..2fa5b17 --- /dev/null +++ b/ChatMLX/Core/Services/ChatService.swift @@ -0,0 +1,42 @@ +// +// ChatService.swift +// ChatMLX +// +// Created by John Mai on 2024/11/10. +// + +class ChatService { + private var strategy: ChatStrategy + private var model: ProviderModel + + init(_ model: ProviderModel) { + self.model = model + + let provider: Provider = + switch model.id { + case .id(_, let provider): + provider + case .directory(_, let provider): + provider + } + + switch provider { + case .mlx: + strategy = MLXChatStrategy() + case .openAI: + strategy = OpenAIChatStrategy() + } + } + + func setStrategy(_ strategy: ChatStrategy) { + self.strategy = strategy + } + + func send(messages: [[String: String]], parameters: ChatParameters) async throws -> AsyncStream { + try await strategy.send( + model: model, + messages: messages, + parameters: parameters + ) + } +} diff --git a/ChatMLX/Core/Services/ChatStrategies/ChatStrategy.swift b/ChatMLX/Core/Services/ChatStrategies/ChatStrategy.swift new file mode 100644 index 0000000..f6696c2 --- /dev/null +++ b/ChatMLX/Core/Services/ChatStrategies/ChatStrategy.swift @@ -0,0 +1,39 @@ +// +// ChatStrategy.swift +// ChatMLX +// +// Created by John Mai on 2024/11/10. +// + +struct ChatParameters: Sendable { + var temperature: Float = 0.6 + var topP: Float = 1.0 + var repetitionPenalty: Float? + var repetitionContextSize: Int = 20 + var extraEOSTokens: Set? + var maxTokens: Int = 1024 + + init( + temperature: Float = 0.6, + topP: Float = 1.0, + repetitionPenalty: Float? = nil, + repetitionContextSize: Int = 20, + extraEOSTokens: Set? = nil, + maxTokens: Int = 1024 + ) { + self.temperature = temperature + self.topP = topP + self.repetitionPenalty = repetitionPenalty + self.repetitionContextSize = repetitionContextSize + self.extraEOSTokens = extraEOSTokens + self.maxTokens = maxTokens + } +} + +protocol ChatStrategy { + func send( + model: ProviderModel, + messages: [[String: String]], + parameters: ChatParameters? + ) async throws -> AsyncStream +} diff --git a/ChatMLX/Core/Services/ChatStrategies/MLXChatStrategy.swift b/ChatMLX/Core/Services/ChatStrategies/MLXChatStrategy.swift new file mode 100644 index 0000000..3b75967 --- /dev/null +++ b/ChatMLX/Core/Services/ChatStrategies/MLXChatStrategy.swift @@ -0,0 +1,106 @@ +// +// MLXChatService.swift +// ChatMLX +// +// Created by John Mai on 2024/11/10. +// + +import Defaults +import MLX +import MLXLLM +import MLXRandom +import Metal +import SwiftUI +import Tokenizers +import os + +actor MLXModelManager { + enum LoadState { + case idle + case loaded(ProviderModel, ModelContainer) + } + + var state: LoadState = .idle + var running: Bool = false + + func load(model: ProviderModel) async throws -> ModelContainer? { + switch state { + case .idle: + MLX.GPU.set(cacheLimit: 20 * 1024 * 1024) + + guard case .directory(let modelURL, _) = model.id else { + return nil + } + + let modelConfiguration = ModelConfiguration(directory: modelURL) + let modelContainer = try await MLXLLM.loadModelContainer(configuration: modelConfiguration) + + state = .loaded(model, modelContainer) + + return modelContainer + case .loaded(let m, let modelContainer): + if model != m { + state = .idle + return try await load(model: model) + } + + return modelContainer + } + } +} + +class MLXChatStrategy: ChatStrategy { + func send( + model: ProviderModel, + messages: [[String: String]], + parameters: ChatParameters? = nil + ) async throws -> AsyncStream { + AsyncStream { continuation in + Task { + let manager = MLXModelManager() + + guard let modelContainer = try await manager.load(model: model) else { + continuation.finish() + return + } + + let promptTokens = try await modelContainer.perform { _, tokenizer in + try tokenizer.applyChatTemplate(messages: messages) + } + + let result = await modelContainer.perform { model, tokenizer in + var generateParameters = GenerateParameters() + + if let parameters { + generateParameters.temperature = parameters.temperature + generateParameters.topP = parameters.topP + generateParameters.repetitionContextSize = parameters.repetitionContextSize + generateParameters.repetitionPenalty = parameters.repetitionPenalty + } + + return MLXLLM.generate( + promptTokens: promptTokens, + parameters: generateParameters, + model: model, + tokenizer: tokenizer, + extraEOSTokens: [] + ) { tokens in + if tokens.count % 4 == 0 { + let text = tokenizer.decode(tokens: tokens) + continuation.yield(text) + } + + if tokens.count >= parameters?.maxTokens ?? 1024 { + return .stop + } else { + return .more + } + } + } + + continuation.yield(result.output) + continuation.finish() + } + } + } +} diff --git a/ChatMLX/Core/Services/ChatStrategies/OpenAIChatStrategy.swift b/ChatMLX/Core/Services/ChatStrategies/OpenAIChatStrategy.swift new file mode 100644 index 0000000..afc7216 --- /dev/null +++ b/ChatMLX/Core/Services/ChatStrategies/OpenAIChatStrategy.swift @@ -0,0 +1,19 @@ +// +// OpenAIChatStrategy.swift +// ChatMLX +// +// Created by John Mai on 2024/11/10. +// + +class OpenAIChatStrategy: ChatStrategy { + func send( + model: ProviderModel, + messages: [[String: String]], + parameters: ChatParameters? + ) async throws -> AsyncStream { + AsyncStream { continuation in + continuation.yield("OpenAI: \(messages)") + continuation.finish() + } + } +} diff --git a/ChatMLX/Core/Services/HuggingFaceService.swift b/ChatMLX/Core/Services/HuggingFaceService.swift new file mode 100644 index 0000000..41fa4a6 --- /dev/null +++ b/ChatMLX/Core/Services/HuggingFaceService.swift @@ -0,0 +1,43 @@ +// +// HuggingFaceService.swift +// ChatMLX +// +// Created by John Mai on 2024/11/10. +// +import Alamofire +import Foundation + +struct ModelQuery { + +} + +struct HuggingFaceService { + static let shared = HuggingFaceService() + + func fetchMLXCommunityModels(search: String? = nil) async -> [RemoteModel] { + 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, !search.isEmpty { + queryItems.append(URLQueryItem(name: "search", value: search)) + } + + urlComponents.queryItems = queryItems + + guard let url = urlComponents.url else { + return [] + } + + do { + AF.request("https://huggingface.co/api/models") + + return [] + } + } +} diff --git a/ChatMLX/Core/Services/HuggingfaceHubService.swift b/ChatMLX/Core/Services/HuggingfaceHubService.swift new file mode 100644 index 0000000..f149c15 --- /dev/null +++ b/ChatMLX/Core/Services/HuggingfaceHubService.swift @@ -0,0 +1,45 @@ +// +// HuggingfaceHubService.swift +// ChatMLX +// +// Created by John Mai on 2025/2/19. +// +import HuggingfaceHub + +struct HuggingfaceHubService { + func scanMLXModels() throws -> [String: [CachedRepoInfo]] { + let hfCacheInfo = try CacheManager().scanCacheDir() + + let models = hfCacheInfo.repos.filter { repo in + repo.repoId.hasPrefix("mlx-community/") || repo.repoId.contains("-MLX") || isMLX(repo: repo) + } + + return Dictionary(grouping: models) { repo in + repo.repoId.split(separator: "/").first.map(String.init) ?? "Other" + } + } + + 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/ChatMLX/Core/Services/MLXService.swift b/ChatMLX/Core/Services/MLXService.swift new file mode 100644 index 0000000..0e00ac9 --- /dev/null +++ b/ChatMLX/Core/Services/MLXService.swift @@ -0,0 +1,66 @@ +// +// MLXService.swift +// ChatMLX +// +// Created by John Mai on 2024/11/9. +// + +import Foundation + +struct MLXService { + static let shared = MLXService() + + func fetchModels() async throws -> [ProviderModel] { + let fileManager = FileManager.default + + guard let documentDirectoryURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first + else { + return [] + } + + let organizations = try fileManager.contentsOfDirectory( + at: documentDirectoryURL.appendingPathComponent("huggingface/models"), + includingPropertiesForKeys: nil, + options: [.skipsHiddenFiles] + ) + + guard !organizations.isEmpty else { + return [] + } + + return try organizations.flatMap { organization -> [ProviderModel] in + guard organization.hasDirectoryPath else { + return [] + } + + let huggingfaceModels = try fileManager.contentsOfDirectory( + at: organization, + includingPropertiesForKeys: nil, + options: [.skipsHiddenFiles] + ) + + return try huggingfaceModels.compactMap { model -> ProviderModel? in + guard model.hasDirectoryPath else { + return nil + } + + let data = try Data(contentsOf: model.appendingPathComponent("tokenizer_config.json")) + let tokenizerConfig = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any] + + let modelMaxLength = tokenizerConfig?["model_max_length"] as? Int + let toolCall = (tokenizerConfig?["chat_template"] as? String)?.contains("tool_calls") ?? false + let vision = false + + return .init( + id: .directory(model, .mlx), + name: model.lastPathComponent, + path: model, + maxInputLength: modelMaxLength, + maxOutputLength: modelMaxLength, + toolCall: toolCall, + vision: vision + ) + } + } + } +} diff --git a/ChatMLX/Core/Services/OpenAIService.swift b/ChatMLX/Core/Services/OpenAIService.swift new file mode 100644 index 0000000..b552233 --- /dev/null +++ b/ChatMLX/Core/Services/OpenAIService.swift @@ -0,0 +1,37 @@ +// +// OpenAIService.swift +// ChatMLX +// +// Created by John Mai on 2024/11/9. +// + +import Defaults +import Foundation +import OpenAI + +struct OpenAIService { + var client: OpenAI + + init(apiKey: String, baseURL: String? = nil) { + if let baseURL, let url = URL(string: baseURL), let host = url.host { + self.client = OpenAI( + configuration: .init( + token: apiKey, + host: host, + port: url.port ?? 443, + scheme: url.scheme ?? "https" + ) + ) + } else { + self.client = OpenAI(configuration: .init(token: apiKey)) + } + } + + func fetchModels() async throws -> [ProviderModel] { + let models = try await client.models() + + return models.data.map { model in + .init(id: .id(model.id, .openAI)) + } + } +} diff --git a/ChatMLX/Core/Stores/ConversationStore.swift b/ChatMLX/Core/Stores/ConversationStore.swift new file mode 100644 index 0000000..6524cca --- /dev/null +++ b/ChatMLX/Core/Stores/ConversationStore.swift @@ -0,0 +1,166 @@ +// +// ConversationStore.swift +// ChatMLX +// +// Created by John Mai on 2024/11/9. +// + +import SwiftUI + +@MainActor +@Observable +final class ConversationStore { + static let shared = ConversationStore() + + var selectedConversation: Conversation? + + private let viewContext: NSManagedObjectContext = PersistenceController.shared.container.viewContext + private let repository: Repository = .init(container: PersistenceController.shared.container) + + // Create a new conversation + nonisolated func createConversation() { + Task { + do { + let objectID = try await repository.createConversation() + + try await MainActor.run { + if let conversation = try viewContext.existingObject(with: objectID) as? Conversation { + selectConversation(conversation) + } + } + } catch { + // TODO: Handle error + } + } + } + + // Select a conversation + func selectConversation(_ conversation: Conversation) { + selectedConversation = conversation + } + + // Delete a conversation + nonisolated func deleteConversation(_ conversation: Conversation) { + Task { + do { + try await repository.deleteConversation(conversation.objectID) + + await MainActor.run { + if selectedConversation == conversation { + selectedConversation = nil + } + } + } catch { + // TODO: Handle error + } + } + } + + // Delete messages + nonisolated func deleteMessages(_ messages: [Message]) { + Task { + do { + try await repository.deleteMessages(messages) + } catch { + // TODO: Handle error + } + } + } + + // Clear Conversations + nonisolated func clearConversations() { + Task { + let messageObjectIDs = try await repository.clearMessages() + let conversationObjectIDs = try await repository.clearConversations() + + await MainActor.run { + selectedConversation = nil + } + + await repository.mergeChanges([NSDeletedObjectsKey: messageObjectIDs + conversationObjectIDs]) + } + } + + // Send a message + func send(conversation: Conversation, model: ProviderModel, content: String = "") async { + guard !conversation.inferring else { return } + conversation.inferring = true + + do { + let service = ChatService(model) + + let assistantMessage: Message = + if let message = conversation.messages.last, message.role == .assistant { + message + } else { + Message(context: viewContext).assistant(conversation: conversation) + } + + assistantMessage.inferring = true + + let messages = prepare(conversation) + + let responseStream = try await service.send( + messages: messages, + parameters: .init( + temperature: conversation.temperature, + topP: conversation.topP, + repetitionPenalty: conversation.useRepetitionPenalty ? conversation.repetitionPenalty : nil, + repetitionContextSize: conversation.repetitionContextSize, + extraEOSTokens: nil, + maxTokens: conversation.maxLength + ) + ) + + assistantMessage.content = "" + for await response in responseStream { + assistantMessage.content = response + print(response) + } + + assistantMessage.inferring = false + conversation.inferring = false + try viewContext.saveChanges() + } catch { + // TODO: Handle error + print("Error: \(error)") + } + } + + private 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()) + } + } + } + + let 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 + } + + private func formatMessage(role: Role, content: String) -> [String: String] { + [ + "role": role.rawValue, + "content": content, + ] + } +} diff --git a/ChatMLX/Core/Stores/DownloadStore.swift b/ChatMLX/Core/Stores/DownloadStore.swift new file mode 100644 index 0000000..cc2db4e --- /dev/null +++ b/ChatMLX/Core/Stores/DownloadStore.swift @@ -0,0 +1,16 @@ +// +// DownloadStore.swift +// ChatMLX +// +// Created by John Mai on 2024/11/10. +// + +import SwiftUI + +@MainActor +@Observable +final class DownloadStore { + static let shared = DownloadStore() + + var tasks: [DownloadTask] = [] +} diff --git a/ChatMLX/Core/Stores/ModelStore.swift b/ChatMLX/Core/Stores/ModelStore.swift new file mode 100644 index 0000000..ff27cd2 --- /dev/null +++ b/ChatMLX/Core/Stores/ModelStore.swift @@ -0,0 +1,61 @@ +// +// ModelStore.swift +// ChatMLX +// +// Created by John Mai on 2024/11/9. +// + +import Defaults +import SwiftUI + +@MainActor +@Observable +final class ModelStore { + static let shared = ModelStore() + + var models: [ProviderModel] = [] + + // Fetch Models + nonisolated func fetchModels() async { + do { + async let mlxModels = try await fetchMLXModels() + async let openAIModels = await fetchOpenAIModels() + + let (mlxModelsResult, openAIModelsResult) = try await (mlxModels, openAIModels) + + await MainActor.run { + self.models = mlxModelsResult + openAIModelsResult + } + } catch { + // TODO: Handle error + print("Error: \(error)") + } + } + + // Fetch MLX Models + private func fetchMLXModels() async throws -> [ProviderModel] { + try await MLXService.shared.fetchModels() + } + + // Fetch OpenAI Models + private func fetchOpenAIModels() async -> [ProviderModel] { + do { + guard Defaults[.enableOpenAI] else { return [] } + + let openAIService = OpenAIService( + apiKey: Defaults[.openAIApiKey], + baseURL: Defaults[.openAIBaseURL] + ) + + return try await openAIService.fetchModels() + } catch { + // TODO: Handle error + print("Error: \(error)") + } + return [] + } + + func model(_ model: ProviderModel.Identifier?) -> ProviderModel? { + models.first(where: { $0.id == model }) + } +} diff --git a/ChatMLX/Core/Stores/SettingsStore.swift b/ChatMLX/Core/Stores/SettingsStore.swift new file mode 100644 index 0000000..d4bc8b1 --- /dev/null +++ b/ChatMLX/Core/Stores/SettingsStore.swift @@ -0,0 +1,15 @@ +// +// SettingsStore.swift +// ChatMLX +// +// Created by John Mai on 2024/11/10. +// + +import Defaults +import SwiftUI + +@MainActor +@Observable +class SettingsStore { + var activeTabID: SettingsTab.ID = .general +} diff --git a/ChatMLX/Core/Utilities/Common.swift b/ChatMLX/Core/Utilities/Common.swift new file mode 100644 index 0000000..650438d --- /dev/null +++ b/ChatMLX/Core/Utilities/Common.swift @@ -0,0 +1,12 @@ +// +// Common.swift +// ChatMLX +// +// Created by John Mai on 2024/10/15. +// + +import Foundation + +func getVersion() -> String { + Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown" +} diff --git a/ChatMLX/Core/Utilities/LLMRunner.swift b/ChatMLX/Core/Utilities/LLMRunner.swift new file mode 100644 index 0000000..7fe8bb0 --- /dev/null +++ b/ChatMLX/Core/Utilities/LLMRunner.swift @@ -0,0 +1,259 @@ +//// +//// LLMRunner.swift +//// ChatMLX +//// +//// Created by John Mai on 2024/8/24. +//// +// +//import Defaults +//import Metal +//import MLX +//import MLXLLM +//import MLXRandom +//import SwiftUI +//import Tokenizers +//import os +// +//@Observable +//@MainActor +//class LLMRunner { +// var running = false +// var model: ModelInfo? +// +// let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "LLMRunner") +// +// enum LoadState { +// case idle +// case loaded(ModelContainer) +// } +// +// @ObservationIgnored +// var loadState: LoadState = .idle +// +// @ObservationIgnored +// 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 enableGPUMemorySettings = Defaults[.enableGPUMemorySettings] +// if enableGPUMemorySettings { +// let cacheLimit = Defaults[.gpuCacheLimit] * 1024 * 1024 +// MLX.GPU.set(cacheLimit: cacheLimit) +// +// let memoryLimit = Defaults[.gpuMemoryLimit] * 1024 * 1024 +// MLX.GPU.set(memoryLimit: memoryLimit) +// } +// +//// let modelContainer = try await MLXLLM.loadModelContainer( +//// configuration: modelConfiguration +//// ) +// +// let modelContainer = try await MLXLLM.ModelContainer( +// hub: .init(), modelDirectory: URL(fileURLWithPath: "/Users/john/Downloads/Llama-3.2-1B-Instruct-4bit"), 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 generate2( +//// conversation: Conversation, +//// in context: NSManagedObjectContext, +//// progressing: @escaping () -> Void = {}, +//// completion: (() -> Void)? +//// ) { +//// let service = ChatService() +//// +//// Task{ +//// await service.chat( +//// model: .init( +//// name: "Llama-3.2mo-1B-Instruct-4bit", +//// path: URL(fileURLWithPath: "Llama-3.2-1B-Instruct-4bit"), +//// ), +//// conversation: conversation, +//// context: context +//// ) +//// +//// } +//// } +// +// func generate( +// conversation: Conversation, +// in context: NSManagedObjectContext, +// progressing: @escaping () -> Void = {}, +// completion: (() -> Void)? +// ) { +// guard !running else { return } +// withAnimation { +// running = true +// } +// +// let assistantMessage: Message = if let message = conversation.messages.last, message.role == .assistant { +// message +// } else { +// 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)") +// 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/Core/Utilities/Logger.swift similarity index 56% rename from ChatMLX/Utilities/Logger.swift rename to ChatMLX/Core/Utilities/Logger.swift index 098a56a..ab46753 100644 --- a/ChatMLX/Utilities/Logger.swift +++ b/ChatMLX/Core/Utilities/Logger.swift @@ -6,6 +6,5 @@ // import Foundation -import Logging -let logger = Logger(label: Bundle.main.bundleIdentifier!) +//let logger = Logger(label: Bundle.main.bundleIdentifier!) diff --git a/ChatMLX/Core/Utilities/MarkdownMetadata.swift b/ChatMLX/Core/Utilities/MarkdownMetadata.swift new file mode 100644 index 0000000..5a08b8b --- /dev/null +++ b/ChatMLX/Core/Utilities/MarkdownMetadata.swift @@ -0,0 +1,74 @@ +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/ChatMLX/Core/View Modifiers/AppleIntelligenceEffectViewModifier.swift b/ChatMLX/Core/View Modifiers/AppleIntelligenceEffectViewModifier.swift new file mode 100644 index 0000000..978966a --- /dev/null +++ b/ChatMLX/Core/View Modifiers/AppleIntelligenceEffectViewModifier.swift @@ -0,0 +1,33 @@ +// +// AppleIntelligenceEffectViewModifier.swift +// ChatMLX +// +// Created by John Mai on 2024/11/9. +// + +import Defaults +import SwiftUI + +struct AppleIntelligenceEffectViewModifier: ViewModifier { + @Default(.enableAppleIntelligenceEffect) var enableAppleIntelligenceEffect + @Default(.appleIntelligenceEffectDisplay) var appleIntelligenceEffectDisplay + + @Binding var isPresented: Bool + + func body(content: Content) -> some View { + content.overlay { + if self.enableAppleIntelligenceEffect, self.appleIntelligenceEffectDisplay == .appInternal, self.isPresented + { + AppleIntelligenceEffectView(useRoundedRectangle: false) + .ignoresSafeArea() + .allowsHitTesting(false) + } + } + } +} + +extension View { + func appleIntelligenceEffect(isPresented: Binding) -> some View { + self.modifier(AppleIntelligenceEffectViewModifier(isPresented: isPresented)) + } +} 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/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/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 index d990561..c4248f0 100644 --- a/ChatMLX/Features/Conversation/ConversationView.swift +++ b/ChatMLX/Features/Conversation/ConversationView.swift @@ -9,52 +9,32 @@ 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 + @Binding var selectedConversation: Conversation? var body: some View { - @Bindable var conversationViewModel = conversationViewModel - UltramanNavigationSplitView( sidebar: { - ConversationSidebarView( - selectedConversation: $conversationViewModel.selectedConversation) + ConversationSidebarView(selectedConversation: $selectedConversation) }, detail: { - Detail() + detailView() + } ) .foregroundColor(.white) .ultramanMinimalistWindowStyle() - .overlay { - if enableAppleIntelligenceEffect, appleIntelligenceEffectDisplay == .appInternal, - runner.running - { - AppleIntelligenceEffectView(useRoundedRectangle: false) - .ignoresSafeArea() - .allowsHitTesting(false) - } - } + .appleIntelligenceEffect(isPresented: .constant(false)) } - @MainActor @ViewBuilder - private func Detail() -> some View { + private func detailView() -> some View { Group { - if let conversation = conversationViewModel.selectedConversation { - ConversationDetailView( - conversation: conversation - ).id(conversation.id) + if let conversation = selectedConversation { + ConversationDetailView(conversation: conversation) + } 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/Detail/AssistantMessageView.swift b/ChatMLX/Features/Conversation/Detail/AssistantMessageView.swift new file mode 100644 index 0000000..f3c2c6b --- /dev/null +++ b/ChatMLX/Features/Conversation/Detail/AssistantMessageView.swift @@ -0,0 +1,110 @@ +// +// AssistantMessageView.swift +// ChatMLX +// +// Created by John Mai on 2024/11/10. +// + +import MarkdownUI +import SwiftUI + +struct AssistantMessageView: View { + let displayStyle: DisplayStyle + + @ObservedObject var message: Message + + var body: 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: copy) { + Image(systemName: "doc.on.doc") + .help("Copy") + } + + Button(action: regenerate) { + Image(systemName: "arrow.clockwise") + .help("Regenerate") + } + .disabled(message.conversation?.inferring ?? false) + + 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() + } + } + + private func copy() { + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setString(message.content, forType: .string) + } + + private func regenerate() { + guard let conversation = message.conversation else { return } + + let conversationStore = ConversationStore.shared + + if conversation.messages.last != message { + conversationStore.deleteMessages(message.suffixMessages()) + } + + guard let model = ModelStore.shared.model(conversation.model) else { + return + } + + Task { + await conversationStore.send( + conversation: conversation, + model: model + ) + } + } +} diff --git a/ChatMLX/Features/Conversation/Detail/ConversationDetailView.swift b/ChatMLX/Features/Conversation/Detail/ConversationDetailView.swift new file mode 100644 index 0000000..a57bbe6 --- /dev/null +++ b/ChatMLX/Features/Conversation/Detail/ConversationDetailView.swift @@ -0,0 +1,74 @@ +// +// 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 { + let conversation: Conversation + + @State private var showRightSidebar = false + @State private var displayStyle: DisplayStyle = .markdown + @State private var isEditorFullScreen: Bool = false + + var body: some View { + ZStack(alignment: .trailing) { + VStack(spacing: 0) { + if !isEditorFullScreen { + MessageBoxView( + conversation: conversation, + displayStyle: displayStyle + ) + Divider() + } + + EditorToolbarView( + conversation: conversation, + displayStyle: $displayStyle, + isEditorFullScreen: $isEditorFullScreen + ) + + EditorView( + conversation: conversation, + displayStyle: displayStyle, + isEditorFullScreen: isEditorFullScreen + ) + } + + if showRightSidebar { + Color.black.opacity(0.00001) + .ignoresSafeArea() + .onTapGesture { + withAnimation { + showRightSidebar = false + } + } + + RightSidebarView(conversation: conversation) + } + } + .ultramanToolbar(alignment: .trailing) { + Button(action: showRightSidebarToggle) { + Image(systemName: "slider.horizontal.3") + } + .buttonStyle(.plain) + } + .ultramanNavigationTitle( + conversation.title + ) + } + + func showRightSidebarToggle() { + withAnimation { + showRightSidebar.toggle() + } + } +} diff --git a/ChatMLX/Features/Conversation/Detail/EditorToolbarView.swift b/ChatMLX/Features/Conversation/Detail/EditorToolbarView.swift new file mode 100644 index 0000000..56ad163 --- /dev/null +++ b/ChatMLX/Features/Conversation/Detail/EditorToolbarView.swift @@ -0,0 +1,119 @@ +// +// EditorToolbarView.swift +// ChatMLX +// +// Created by John Mai on 2024/11/10. +// + +import SwiftUI + +struct EditorToolbarView: View { + @ObservedObject var conversation: Conversation + @Binding var displayStyle: DisplayStyle + @Binding var isEditorFullScreen: Bool + @State var isPresented = false + + var body: some View { + HStack { + Button(action: switchDisplayStyle) { + Image(displayStyle == .markdown ? "plaintext" : "markdown") + } + + Button(action: clearMessages) { + Image("clear") + } + + Button(action: isEditorFullScreenToggle) { + 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(action: isPresentedToggle) { + // if conversation.gpuActiveMemory > 0 { + // HStack { + // Image(systemName: "info.circle") + // Text("\(conversation.gpuActiveMemory)M") + // } + // .padding(4) + // .background(Color.black.opacity(0.2)) + // .cornerRadius(20) + // } else { + // Image(systemName: "info.circle") + // .padding(4) + // } + } + .font(.subheadline) + .popover(isPresented: $isPresented) { + 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) + } + + ModelPicker( + selection: $conversation.model, + models: ModelStore.shared.models + ) + } + .buttonStyle(.borderless) + .foregroundStyle(.white) + .tint(.white) + .frame(height: 35) + .padding(.horizontal, 10) + .task { + await ModelStore.shared.fetchModels() + } + } + + private func clearMessages() { + conversation.messages = [] + } + + private func isPresentedToggle() { + isPresented.toggle() + } + + private func switchDisplayStyle() { + withAnimation { + displayStyle = (displayStyle == .markdown) ? .plain : .markdown + } + } + + private func isEditorFullScreenToggle() { + withAnimation { + isEditorFullScreen.toggle() + } + } +} diff --git a/ChatMLX/Features/Conversation/Detail/EditorView.swift b/ChatMLX/Features/Conversation/Detail/EditorView.swift new file mode 100644 index 0000000..101c510 --- /dev/null +++ b/ChatMLX/Features/Conversation/Detail/EditorView.swift @@ -0,0 +1,78 @@ +// +// EditorView.swift +// ChatMLX +// +// Created by John Mai on 2024/11/10. +// + +import Luminare +import SwiftUI + +struct EditorView: View { + let conversation: Conversation + let displayStyle: DisplayStyle + let isEditorFullScreen: Bool + + @State var message = "" + + var body: some View { + ZStack(alignment: .bottom) { + UltramanTextEditor( + text: $message, + placeholder: "Type your message…", + onSubmit: send + ) + .padding(.horizontal, 5) + + HStack(spacing: 16) { + Spacer() + Button("Clear") { + message = "" + } + .buttonStyle(.borderless) + .disabled(message.isEmpty) + + Button(action: send) { + if conversation.inferring { + Label { + Text("Send") + } icon: { + ProgressView() + .controlSize(.small) + .padding(.trailing, 2) + .colorInvert() + .brightness(1) + } + } else { + Label("Send", systemImage: "paperplane") + } + } + .buttonStyle(LuminareCompactButtonStyle()) + .fixedSize() + .disabled( + message.trimmingCharacters( + in: .whitespacesAndNewlines + ).isEmpty || conversation.inferring) + } + .padding() + } + .frame(maxHeight: isEditorFullScreen ? .infinity : 150) + } + + private func send() { + guard let model = ModelStore.shared.model(conversation.model) else { + return + } + + let message = message.trimmingCharacters(in: .whitespacesAndNewlines) + Message(context: PersistenceController.shared.viewContext).user(content: message, conversation: conversation) + self.message = "" + Task { + await ConversationStore.shared.send( + conversation: conversation, + model: model, + content: message + ) + } + } +} diff --git a/ChatMLX/Features/Conversation/Detail/EmptyConversation.swift b/ChatMLX/Features/Conversation/Detail/EmptyConversation.swift new file mode 100644 index 0000000..ef65d81 --- /dev/null +++ b/ChatMLX/Features/Conversation/Detail/EmptyConversation.swift @@ -0,0 +1,26 @@ +// +// EmptyConversation.swift +// ChatMLX +// +// Created by John Mai on 2024/8/3. +// + +import Luminare +import SwiftUI + +struct EmptyConversation: View { + var body: some View { + ContentUnavailableView { + Label("No Conversation", systemImage: "tray.fill") + } description: { + Text("Please select a new conversation") + + Button(action: ConversationStore.shared.createConversation) { + Label("New Conversation", systemImage: "plus") + } + .buttonStyle(LuminareCompactButtonStyle()) + .fixedSize() + } + .foregroundColor(.white) + } +} diff --git a/ChatMLX/Features/Conversation/Detail/MessageBoxView.swift b/ChatMLX/Features/Conversation/Detail/MessageBoxView.swift new file mode 100644 index 0000000..c606d9f --- /dev/null +++ b/ChatMLX/Features/Conversation/Detail/MessageBoxView.swift @@ -0,0 +1,50 @@ +// +// MessageBoxView.swift +// ChatMLX +// +// Created by John Mai on 2024/11/10. +// + +import SwiftUI + +struct MessageBoxView: View { + @ObservedObject var conversation: Conversation + @State var scrollViewProxy: ScrollViewProxy? + let displayStyle: DisplayStyle + + var body: some View { + ScrollViewReader { proxy in + ScrollView { + LazyVStack { + ForEach(conversation.messages) { message in + MessageBubbleView( + message: message, + displayStyle: displayStyle + ) + } + } + .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) + } + } +} diff --git a/ChatMLX/Features/Conversation/Detail/MessageBubbleView.swift b/ChatMLX/Features/Conversation/Detail/MessageBubbleView.swift new file mode 100644 index 0000000..b73345e --- /dev/null +++ b/ChatMLX/Features/Conversation/Detail/MessageBubbleView.swift @@ -0,0 +1,31 @@ +// +// MessageBubbleView.swift +// ChatMLX +// +// Created by John Mai on 2024/8/4. +// + +import AlertToast +import MarkdownUI +import SwiftUI + +struct MessageBubbleView: View { + @ObservedObject var message: Message + + let displayStyle: DisplayStyle + + var body: some View { + HStack { + if message.role == .assistant { + AssistantMessageView( + displayStyle: displayStyle, + message: message + ) + } else { + UserMessageView(message: message) + } + } + .textSelection(.enabled) + .padding(.vertical, 8) + } +} diff --git a/ChatMLX/Features/Conversation/RightSidebarView.swift b/ChatMLX/Features/Conversation/Detail/RightSidebarView.swift similarity index 82% rename from ChatMLX/Features/Conversation/RightSidebarView.swift rename to ChatMLX/Features/Conversation/Detail/RightSidebarView.swift index 0f0c09c..ca1b8ad 100644 --- a/ChatMLX/Features/Conversation/RightSidebarView.swift +++ b/ChatMLX/Features/Conversation/Detail/RightSidebarView.swift @@ -75,14 +75,7 @@ struct RightSidebarView: View { Text("Max Length") Spacer() CompactSlider( - value: Binding( - get: { - Double(conversation.maxLength) - }, - set: { - conversation.maxLength = Int64($0) - } - ), in: 0 ... 8192, step: 1 + value: $conversation.maxLength.asDouble(), in: 0 ... 8192, step: 1 ) { Text("\(Int(conversation.maxLength))") .foregroundStyle(.white) @@ -96,14 +89,8 @@ struct RightSidebarView: View { Text("Repetition Context Size") Spacer() CompactSlider( - value: Binding( - get: { - Double(conversation.repetitionContextSize) - }, - set: { - conversation.repetitionContextSize = Int($0) - } - ), in: 0 ... 100, step: 1 + value: $conversation.repetitionContextSize.asDouble(), in: 0 ... 100, + step: 1 ) { Text("\(conversation.repetitionContextSize)") .foregroundStyle(.white) @@ -157,14 +144,8 @@ struct RightSidebarView: View { Text("Max Messages Limit") Spacer() CompactSlider( - value: Binding( - get: { - Double(conversation.maxMessagesLimit) - }, - set: { - conversation.maxMessagesLimit = Int32($0) - } - ), in: 1 ... 50, step: 1 + value: $conversation.maxMessagesLimit.asDouble(), in: 1 ... 50, + step: 1 ) { Text("\(conversation.maxMessagesLimit)") .foregroundStyle(.white) @@ -188,11 +169,9 @@ struct RightSidebarView: View { if conversation.useSystemPrompt { UltramanTextEditor( - text: $conversation.systemPrompt, + text: $conversation.systemPrompt.toUnwrapped(defaultValue: ""), placeholder: "System prompt", - onSubmit: { - - } + onSubmit: {} ) .frame(height: 100) .padding(padding) @@ -214,5 +193,6 @@ struct RightSidebarView: View { emphasized: true ) ) + .zIndex(10) } } diff --git a/ChatMLX/Features/Conversation/Detail/UserMessageView.swift b/ChatMLX/Features/Conversation/Detail/UserMessageView.swift new file mode 100644 index 0000000..5beeb4e --- /dev/null +++ b/ChatMLX/Features/Conversation/Detail/UserMessageView.swift @@ -0,0 +1,51 @@ +// +// UserMessageView.swift +// ChatMLX +// +// Created by John Mai on 2024/11/10. +// + +import SwiftUI + +struct UserMessageView: View { + let message: Message + + var body: some View { + Spacer() + 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: copy) { + 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 copy() { + let pasteboard = NSPasteboard.general + pasteboard.clearContents() + pasteboard.setString(message.content, forType: .string) + } + + private func delete() { + ConversationStore.shared.deleteMessages(message.suffixMessages()) + } +} 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/Sidebar/ConversationSidebarItemView.swift b/ChatMLX/Features/Conversation/Sidebar/ConversationSidebarItemView.swift new file mode 100644 index 0000000..31f02c1 --- /dev/null +++ b/ChatMLX/Features/Conversation/Sidebar/ConversationSidebarItemView.swift @@ -0,0 +1,55 @@ +// +// ConversationSidebarItem.swift +// ChatMLX +// +// Created by John Mai on 2024/8/4. +// + +import SwiftUI + +struct ConversationSidebarItemView: View { + let conversation: Conversation + @Binding var selectedConversation: Conversation? + + var isActive: Bool { + selectedConversation == conversation + } + + var body: some View { + Button(action: selectConversation) { + 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: .constant(isActive))) + .contextMenu { + Button(role: .destructive, action: deleteConversation) { + Label("Delete", systemImage: "trash") + } + } + } + + private func selectConversation() { + selectedConversation = conversation + } + + private func deleteConversation() { + ConversationStore.shared.deleteConversation(conversation) + } +} diff --git a/ChatMLX/Features/Conversation/Sidebar/ConversationSidebarView.swift b/ChatMLX/Features/Conversation/Sidebar/ConversationSidebarView.swift new file mode 100644 index 0000000..bf5c146 --- /dev/null +++ b/ChatMLX/Features/Conversation/Sidebar/ConversationSidebarView.swift @@ -0,0 +1,102 @@ +// +// ConversationSidebarView.swift +// ChatMLX +// +// Created by John Mai on 2024/8/3. +// + +import Defaults +import Luminare +import SwiftUI + +struct ConversationSidebarView: View { + @FetchRequest( + sortDescriptors: [NSSortDescriptor(keyPath: \Conversation.updatedAt, ascending: false)], + animation: .default + ) + private var conversations: FetchedResults + + private let theme = Theme.shared + private let store = ConversationStore.shared + + @Binding var selectedConversation: Conversation? + @State var keyword = "" + + var body: some View { + VStack(spacing: 0) { + headerView() + logoView() + searchField() + conversationList() + } + .background(.black.opacity(0.4)) + } + + @ViewBuilder + private func headerView() -> some View { + HStack { + Spacer() + Button(action: store.createConversation) { + Image(systemName: "plus") + } + SettingsLink { + Image(systemName: "gear") + } + } + .frame(height: 50) + .padding(.horizontal, theme.padding) + .buttonStyle(.plain) + } + + @ViewBuilder + private func logoView() -> some View { + HStack { + Image("AppLogo") + .resizable() + .aspectRatio(contentMode: .fit) + .frame(width: 60, height: 60) + .shadow(radius: 5) + Text("ChatMLX") + .font(.title) + .fontWeight(.bold) + } + } + + @ViewBuilder + private func searchField() -> some View { + LuminareSection { + UltramanTextField( + $keyword, + placeholder: Text("Search Conversation..."), + onSubmit: search + ) + .frame(height: 25) + } + .padding(.horizontal, theme.padding) + } + + @ViewBuilder + private func conversationList() -> some View { + ScrollView { + LazyVStack(spacing: 0) { + ForEach(conversations) { conversation in + ConversationSidebarItemView( + conversation: conversation, + selectedConversation: $selectedConversation + ) + } + } + } + .padding(.top, 6) + } + + func search() { + conversations.nsPredicate = + keyword.isEmpty + ? nil + : NSPredicate( + format: "title CONTAINS [cd] %@ OR ANY messages.content CONTAINS [cd] %@", + keyword, keyword + ) + } +} diff --git a/ChatMLX/Features/Settings/AboutView.swift b/ChatMLX/Features/Settings/AboutView.swift index 0bffc12..bdb0dee 100644 --- a/ChatMLX/Features/Settings/AboutView.swift +++ b/ChatMLX/Features/Settings/AboutView.swift @@ -25,7 +25,7 @@ struct AboutView: View { .fontWeight(.bold) Text( - "Version \(Bundle.main.infoDictionary?["CFBundleShortVersionString"] as? String ?? "Unknown")" + "Version \(getVersion())" ) .font(.subheadline) .foregroundColor(.white) diff --git a/ChatMLX/Features/Settings/DefaultConversationView.swift b/ChatMLX/Features/Settings/DefaultConversationView.swift index c4b8449..47cd492 100644 --- a/ChatMLX/Features/Settings/DefaultConversationView.swift +++ b/ChatMLX/Features/Settings/DefaultConversationView.swift @@ -25,9 +25,9 @@ struct DefaultConversationView: View { @Default(.defaultUseSystemPrompt) var defaultUseSystemPrompt @Default(.defaultSystemPrompt) var defaultSystemPrompt - @State private var localModels: [LocalModel] = [] + @Default(.defaultProvider) var defaultProvider - @Environment(SettingsViewModel.self) var vm + @Environment(ModelStore.self) var modelStore private let padding: CGFloat = 6 @@ -39,34 +39,20 @@ struct DefaultConversationView: View { $defaultTitle, placeholder: Text("Default conversation title") ) - .frame(height: 25) + .frame(minHeight: 35) } LuminareSection("Model Settings") { - HStack { - Text("Model") - Spacer() - Picker( + LabeledContent("Model") { + ModelPicker( 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) - } - } + models: modelStore.models + ).task { + await modelStore.fetchModels() } - .labelsHidden() - .buttonStyle(.borderless) - .foregroundStyle(.white) - .tint(.white) } - .padding(padding) - HStack { - Text("Temperature") - Spacer() + LabeledContent("Temperature") { CompactSlider( value: $defaultTemperature, in: 0 ... 2, step: 0.01 ) { @@ -75,11 +61,8 @@ struct DefaultConversationView: View { } .frame(width: 200) } - .padding(padding) - HStack { - Text("Top P") - Spacer() + LabeledContent("Top P") { CompactSlider( value: $defaultTopP, in: 0 ... 1, step: 0.01 ) { @@ -88,62 +71,39 @@ struct DefaultConversationView: View { } .frame(width: 200) } - .padding(padding) - HStack { - Text("Use Max Length") - Spacer() + LabeledContent("Use Max Length") { Toggle("", isOn: $defaultUseMaxLength) - .toggleStyle(.switch) } - .padding(padding) if defaultUseMaxLength { - HStack { - Text("Max Length") - Spacer() + LabeledContent("Max Length") { CompactSlider( - value: Binding( - get: { Double(defaultMaxLength) }, - set: { defaultMaxLength = Int64($0) } - ), in: 0 ... 8192, step: 1 + value: $defaultMaxLength.asDouble(), in: 0 ... 8192, step: 1 ) { Text("\(defaultMaxLength)") .foregroundStyle(.white) } .frame(width: 200) } - .padding(padding) } - HStack { - Text("Repetition Context Size") - Spacer() + LabeledContent("Repetition Context Size") { CompactSlider( - value: Binding( - get: { Double(defaultRepetitionContextSize) }, - set: { defaultRepetitionContextSize = Int32($0) } - ), in: 0 ... 100, step: 1 + value: $defaultRepetitionContextSize.asDouble(), in: 0 ... 100, step: 1 ) { Text("\(defaultRepetitionContextSize)") .foregroundStyle(.white) } .frame(width: 200) } - .padding(padding) - HStack { - Text("Use Repetition Penalty") - Spacer() + LabeledContent("Use Repetition Penalty") { Toggle("", isOn: $defaultUseRepetitionPenalty) - .toggleStyle(.switch) } - .padding(padding) if defaultUseRepetitionPenalty { - HStack { - Text("Repetition Penalty") - Spacer() + LabeledContent("Repetition Penalty") { CompactSlider( value: $defaultRepetitionPenalty, in: 1 ... 2, step: 0.01 @@ -155,48 +115,31 @@ struct DefaultConversationView: View { } .frame(width: 200) } - .padding(padding) } } - .compactSliderSecondaryColor(.white) LuminareSection("Message Control") { - HStack { - Text("Use Max Messages Limit") - Spacer() + LabeledContent("Use Max Messages Limit") { Toggle("", isOn: $defaultUseMaxMessagesLimit) - .toggleStyle(.switch) } - .padding(padding) if defaultUseMaxMessagesLimit { - HStack { - Text("Max Messages Limit") - Spacer() + LabeledContent("Max Messages Limit") { CompactSlider( - value: Binding( - get: { Double(defaultMaxMessagesLimit) }, - set: { defaultMaxMessagesLimit = Int32($0) } - ), in: 1 ... 50, step: 1 + value: $defaultMaxMessagesLimit.asDouble(), 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() + LabeledContent("Use System Prompt") { Toggle("", isOn: $defaultUseSystemPrompt) - .toggleStyle(.switch) } - .padding(padding) if defaultUseSystemPrompt { UltramanTextEditor( @@ -205,7 +148,6 @@ struct DefaultConversationView: View { onSubmit: {} ) .frame(height: 100) - .padding(padding) } } @@ -213,56 +155,13 @@ struct DefaultConversationView: View { } .padding() } + .labeledContentStyle(.horizontal) + .compactSliderSecondaryColor(.white) .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") - } + .labelsHidden() + .buttonStyle(.borderless) + .foregroundStyle(.white) + .toggleStyle(.switch) } } diff --git a/ChatMLX/Features/Settings/DownloadManager/DownloadManagerView.swift b/ChatMLX/Features/Settings/Download Manager/DownloadManagerView.swift similarity index 88% rename from ChatMLX/Features/Settings/DownloadManager/DownloadManagerView.swift rename to ChatMLX/Features/Settings/Download Manager/DownloadManagerView.swift index 178866c..e1b8273 100644 --- a/ChatMLX/Features/Settings/DownloadManager/DownloadManagerView.swift +++ b/ChatMLX/Features/Settings/Download Manager/DownloadManagerView.swift @@ -8,7 +8,7 @@ import SwiftUI struct DownloadManagerView: View { - @Environment(SettingsViewModel.self) private var settingsViewModel + @Environment(SettingsViewModel.self) private var vm @State private var repoId: String = "" @State var showingAlert = false @@ -16,7 +16,7 @@ struct DownloadManagerView: View { var body: some View { List { - ForEach(settingsViewModel.tasks) { task in + ForEach(vm.tasks) { task in DownloadTaskView(task: task) } } @@ -54,6 +54,6 @@ struct DownloadManagerView: View { private func addTask() { let task = DownloadTask(repoId) task.start() - settingsViewModel.tasks.append(task) + vm.tasks.append(task) } } diff --git a/ChatMLX/Features/Settings/DownloadManager/DownloadTaskView.swift b/ChatMLX/Features/Settings/Download Manager/DownloadTaskView.swift similarity index 93% rename from ChatMLX/Features/Settings/DownloadManager/DownloadTaskView.swift rename to ChatMLX/Features/Settings/Download Manager/DownloadTaskView.swift index 40f230f..9dd56ee 100644 --- a/ChatMLX/Features/Settings/DownloadManager/DownloadTaskView.swift +++ b/ChatMLX/Features/Settings/Download Manager/DownloadTaskView.swift @@ -8,8 +8,9 @@ import SwiftUI struct DownloadTaskView: View { - @Bindable var task: DownloadTask - @Environment(SettingsViewModel.self) private var settingsViewModel + let task: DownloadTask + + @Environment(SettingsViewModel.self) private var vm var body: some View { HStack { @@ -64,7 +65,7 @@ struct DownloadTaskView: View { } Button(action: { - settingsViewModel.tasks.removeAll(where: { + vm.tasks.removeAll(where: { $0.id == task.id }) }) { diff --git a/ChatMLX/Features/Settings/ExperimentalFeaturesView.swift b/ChatMLX/Features/Settings/ExperimentalFeaturesView.swift index 1a97b86..c2ae9b0 100644 --- a/ChatMLX/Features/Settings/ExperimentalFeaturesView.swift +++ b/ChatMLX/Features/Settings/ExperimentalFeaturesView.swift @@ -18,18 +18,13 @@ struct ExperimentalFeaturesView: View { var body: some View { VStack(spacing: 18) { LuminareSection("Window Appearance") { - HStack { - Text("Apple Intelligence Effect") - Spacer() + LabeledContent("Apple Intelligence Effect") { Toggle("", isOn: $enableAppleIntelligenceEffect) .toggleStyle(.switch) } - .padding(6) if enableAppleIntelligenceEffect { - HStack { - Text("Display Mode") - Spacer() + LabeledContent("Display Mode") { Picker( "Display Mode", selection: $appleIntelligenceEffectDisplay @@ -43,9 +38,10 @@ struct ExperimentalFeaturesView: View { .foregroundStyle(.white) .tint(.white) } - .padding(8) } } + .labeledContentStyle(.horizontal) + Spacer() } .ultramanToolbar(alignment: .trailing) { diff --git a/ChatMLX/Features/Settings/GeneralView.swift b/ChatMLX/Features/Settings/GeneralView.swift index 83c1cdb..9ae9e46 100644 --- a/ChatMLX/Features/Settings/GeneralView.swift +++ b/ChatMLX/Features/Settings/GeneralView.swift @@ -17,21 +17,14 @@ struct GeneralView: View { @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 + @Environment(ConversationStore.self) private var conversationStore let maxRAM = ProcessInfo.processInfo.physicalMemory / (1024 * 1024) var body: some View { VStack(spacing: 18) { LuminareSection("Language") { - HStack { - Text("Language") - Spacer() + LabeledContent("Language") { Picker( "Language", selection: $language @@ -40,18 +33,14 @@ struct GeneralView: View { Text(language.displayName).tag(language) } } - .labelsHidden() .buttonStyle(.borderless) .foregroundStyle(.white) .tint(.white) } - .padding(8) } LuminareSection("Window Appearance") { - HStack { - Text("Blur") - Spacer() + LabeledContent("Blur") { CompactSlider(value: $blurRadius, in: 0 ... 100) { Text("\(Int(blurRadius))") .foregroundStyle(.white) @@ -59,41 +48,14 @@ struct GeneralView: View { .frame(width: 200) .compactSliderSecondaryColor(.white) } - .padding(5) - HStack { - Text("Color") - Spacer() + LabeledContent("Color") { 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) + Button("Clear All Conversations", action: conversationStore.clearConversations) .frame(height: 35) Button("Reset All Settings", action: resetAllSettings) .frame(height: 35) @@ -102,53 +64,14 @@ struct GeneralView: View { Spacer() } - + .labeledContentStyle(.horizontal) .ultramanNavigationTitle("General") .padding() + .labelsHidden() } 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") - } + Defaults.removeAll() } } diff --git a/ChatMLX/Features/Settings/HuggingFaceView.swift b/ChatMLX/Features/Settings/HuggingFaceView.swift index 8608868..3c657d3 100644 --- a/ChatMLX/Features/Settings/HuggingFaceView.swift +++ b/ChatMLX/Features/Settings/HuggingFaceView.swift @@ -23,27 +23,17 @@ struct HuggingFaceView: View { var body: some View { VStack(spacing: 18) { LuminareSection("Hugging Face Token") { - HStack { - Text("Token") - Spacer() + LabeledContent("Token") { UltramanSecureField( - Binding( - get: { token ?? "" }, - set: { token = $0.isEmpty ? nil : $0 } - ), + $token, placeholder: Text("Enter your Hugging Face token"), alignment: .trailing ) - .frame(height: 25) } - .padding(5) } LuminareSection("Hugging Face Endpoint") { - - HStack { - Text("Endpoint") - Spacer() + LabeledContent("Endpoint") { Picker("", selection: $endpoint) { if useCustomEndpoint { ForEach(customEndpoints, id: \.self) { @@ -56,26 +46,14 @@ struct HuggingFaceView: View { 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() + LabeledContent("Use Custom Endpoint") { Toggle("", isOn: $useCustomEndpoint) - .toggleStyle(.switch) - .labelsHidden() } - .padding(8) if useCustomEndpoint { - HStack { - Text("Custom Endpoint") - Spacer() + LabeledContent("Custom Endpoint") { UltramanTextField( $newCustomEndpoint, placeholder: Text("Custom Hugging Face Endpoint"), @@ -86,7 +64,6 @@ struct HuggingFaceView: View { addCustomEndpoint() } } - .padding(5) if !customEndpoints.isEmpty { List { @@ -115,6 +92,12 @@ struct HuggingFaceView: View { Spacer() } + .toggleStyle(.switch) + .labeledContentStyle(.horizontal) + .labelsHidden() + .buttonStyle(.borderless) + .foregroundStyle(.white) + .tint(.white) .ultramanNavigationTitle("Hugging Face") .padding() .alert(isPresented: $showingAlert) { diff --git a/ChatMLX/Features/Settings/LocalModels/LocalModelItemView.swift b/ChatMLX/Features/Settings/Local Models/LocalModelItemView.swift similarity index 87% rename from ChatMLX/Features/Settings/LocalModels/LocalModelItemView.swift rename to ChatMLX/Features/Settings/Local Models/LocalModelItemView.swift index e8fb1f6..8975958 100644 --- a/ChatMLX/Features/Settings/LocalModels/LocalModelItemView.swift +++ b/ChatMLX/Features/Settings/Local Models/LocalModelItemView.swift @@ -6,16 +6,18 @@ // import SwiftUI +import HuggingfaceHub struct LocalModelItemView: View { - @Binding var model: LocalModel + let name: String + var onDelete: () -> Void @State private var showingDeleteAlert = false var body: some View { VStack { HStack { - Text(model.name) + Text(name) Spacer() Button(action: { showingDeleteAlert = true }) { Image(systemName: "trash") @@ -33,7 +35,7 @@ struct LocalModelItemView: View { Button("Cancel", role: .cancel) {} Button("Delete", role: .destructive, action: onDelete) } message: { - Text("Are you sure you want to delete '\(model.origin)'?") + Text("Are you sure you want to delete '\(name)'?") } } } diff --git a/ChatMLX/Features/Settings/Local Models/LocalModelsView.swift b/ChatMLX/Features/Settings/Local Models/LocalModelsView.swift new file mode 100644 index 0000000..3fe6a93 --- /dev/null +++ b/ChatMLX/Features/Settings/Local Models/LocalModelsView.swift @@ -0,0 +1,75 @@ +// +// LocalModelsView.swift +// ChatMLX +// +// Created by John Mai on 2024/8/10. +// + +import HuggingfaceHub +import SwiftUI + +struct LocalModelsView: View { + + @State private var groupedModels: [String: [CachedRepoInfo]] = [:] + private var service: HuggingfaceHubService = .init() + + var body: some View { + List { + ForEach(groupedModels.keys.sorted(), id: \.self) { key in + Section( + header: Text(key).font( + .title2.bold()) + ) { + EmptyView() + ForEach(groupedModels[key]!, id: \.id) { model in + LocalModelItemView(name: model.repoId.deletingPrefix("\(key)/"),onDelete: { + }) + } + } + } + } + .scrollContentBackground(.hidden) + .listStyle(SidebarListStyle()) + .ultramanNavigationTitle("Models") + .ultramanToolbar { + Button(action: openModelsDirectory) { + Image(systemName: "folder") + } + .buttonStyle(.plain) + }.task { + do { + groupedModels = try service.scanMLXModels() + } catch { + print(error) + } + } + + } + + 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/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/MLX Community/MLXCommunityItemView.swift similarity index 57% rename from ChatMLX/Features/Settings/MLXCommunity/MLXCommunityItemView.swift rename to ChatMLX/Features/Settings/MLX Community/MLXCommunityItemView.swift index 3f0801f..b9329bf 100644 --- a/ChatMLX/Features/Settings/MLXCommunity/MLXCommunityItemView.swift +++ b/ChatMLX/Features/Settings/MLX Community/MLXCommunityItemView.swift @@ -6,9 +6,10 @@ // import SwiftUI +import HuggingfaceHub struct MLXCommunityItemView: View { - @Binding var model: RemoteModel + @Binding var model: CachedRepoInfo @Environment(SettingsViewModel.self) var settingsViewModel var body: some View { @@ -38,35 +39,35 @@ struct MLXCommunityItemView: View { } HStack { - Label("\(model.downloads)", systemImage: "arrow.down.circle") - .font(.subheadline) - - Label("\(model.likes)", systemImage: "heart.fill") - .font(.subheadline) - .foregroundColor(.red.opacity(0.6)) +// 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) - } +// 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) - } - } - } +// 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)) diff --git a/ChatMLX/Features/Settings/MLXCommunity/MLXCommunityView.swift b/ChatMLX/Features/Settings/MLX Community/MLXCommunityView.swift similarity index 51% rename from ChatMLX/Features/Settings/MLXCommunity/MLXCommunityView.swift rename to ChatMLX/Features/Settings/MLX Community/MLXCommunityView.swift index 5c4127c..d21b97a 100644 --- a/ChatMLX/Features/Settings/MLXCommunity/MLXCommunityView.swift +++ b/ChatMLX/Features/Settings/MLX Community/MLXCommunityView.swift @@ -8,6 +8,8 @@ import Alamofire import Luminare import SwiftUI +import os +import HuggingfaceHub struct MLXCommunityView: View { @Environment(SettingsViewModel.self) var settingsViewModel @@ -17,9 +19,13 @@ struct MLXCommunityView: View { @State var next: String? @State var status: Status = .isLoading + + @State var models: [CachedRepoInfo] = [] private let sessionManager: Session + private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "MLXCommunityView") + enum Status { case isLoading case idle @@ -44,16 +50,15 @@ struct MLXCommunityView: View { ) { Task { settingsViewModel.remoteModels = [] - await fetchModels(search: searchQuery) +// await fetchModels(search: searchQuery) } } - } .padding(.top) .padding(.horizontal) List { - ForEach($settingsViewModel.remoteModels) { model in + ForEach($models,id: \.repoId) { model in MLXCommunityItemView(model: model) } lastRowView @@ -62,7 +67,7 @@ struct MLXCommunityView: View { } .onAppear { Task { - await fetchModels() +// await fetchModels() } } .ultramanNavigationTitle("MLX Community") @@ -70,7 +75,7 @@ struct MLXCommunityView: View { Button(action: { Task { settingsViewModel.remoteModels = [] - await fetchModels() +// await fetchModels() } }) { Image(systemName: "arrow.clockwise") @@ -78,9 +83,11 @@ struct MLXCommunityView: View { .disabled(isFetching) .buttonStyle(.plain) } + .task { +// models = (try? HuggingfaceHubService().scanMLXModels()) ?? [] + } } - @MainActor @ViewBuilder var lastRowView: some View { ZStack(alignment: .center) { @@ -97,7 +104,7 @@ struct MLXCommunityView: View { .frame(maxWidth: .infinity) .onAppear { Task { - await loadMoreModelsIfNeeded() +// await loadMoreModelsIfNeeded() } } } @@ -128,71 +135,71 @@ struct MLXCommunityView: View { 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 - } - } +// 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/Model Manager/ModelManagerView.swift b/ChatMLX/Features/Settings/Model Manager/ModelManagerView.swift new file mode 100644 index 0000000..0cff9bb --- /dev/null +++ b/ChatMLX/Features/Settings/Model Manager/ModelManagerView.swift @@ -0,0 +1,22 @@ +// +// ModelManagerView.swift +// ChatMLX +// +// Created by John Mai on 2024/10/4. +// +import SwiftUI + +struct ModelManagerView: View { + @State var isMLXExpanded: Bool = true + + var body: some View { + ScrollView { + LazyVStack { + MLXProviderView() + OpenAIProviderView() + } + .padding() + } + .ultramanNavigationTitle("Model Manager") + } +} diff --git a/ChatMLX/Features/Settings/Model Manager/Providers/MLX/GPUMemorySettingsView.swift b/ChatMLX/Features/Settings/Model Manager/Providers/MLX/GPUMemorySettingsView.swift new file mode 100644 index 0000000..3d56142 --- /dev/null +++ b/ChatMLX/Features/Settings/Model Manager/Providers/MLX/GPUMemorySettingsView.swift @@ -0,0 +1,42 @@ +// +// GPUMemorySettingsView.swift +// ChatMLX +// +// Created by John Mai on 2024/10/26. +// + +import CompactSlider +import Defaults +import SwiftUI + +struct GPUMemorySettingsView: View { + @Default(.enableGPUMemorySettings) var enableGPUMemorySettings + @Default(.gpuCacheLimit) var gpuCacheLimit + @Default(.gpuMemoryLimit) var gpuMemoryLimit + + let maxRAM = ProcessInfo.processInfo.physicalMemory / (1024 * 1024) + + var body: some View { + if enableGPUMemorySettings { + LabeledContent("GPU Cache Limit") { + CompactSlider( + value: $gpuCacheLimit.asDouble(), in: 0 ... Double(maxRAM), step: 128 + ) { + Text("\(Int(gpuCacheLimit))MB") + .foregroundStyle(.white) + } + .frame(width: 200) + } + + LabeledContent("GPU Memory Limit") { + CompactSlider( + value: $gpuMemoryLimit.asDouble(), in: 0 ... Double(maxRAM), step: 128 + ) { + Text("\(Int(gpuMemoryLimit))MB") + .foregroundStyle(.white) + } + .frame(width: 200) + } + } + } +} diff --git a/ChatMLX/Features/Settings/Model Manager/Providers/MLX/MLXProviderContentView.swift b/ChatMLX/Features/Settings/Model Manager/Providers/MLX/MLXProviderContentView.swift new file mode 100644 index 0000000..74a27d1 --- /dev/null +++ b/ChatMLX/Features/Settings/Model Manager/Providers/MLX/MLXProviderContentView.swift @@ -0,0 +1,175 @@ +// +// MLXProviderContentView.swift +// ChatMLX +// +// Created by John Mai on 2024/10/26. +// + +import CompactSlider +import Defaults +import Luminare +import SwiftUI +import os + +struct MLXProviderContentView: View { + // MARK: - Properties + + let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "MLXProviderContentView") + let persistenceController = PersistenceController.shared + + // MARK: - State + + @State private var isPresentedImport = false + @State var providerModels: [ProviderModel] = [] + + // MARK: - Environment + + @Environment(\.managedObjectContext) private var viewContext + @Environment(\.appError) private var appError + + // MARK: - Fetch Request + + @FetchRequest( + sortDescriptors: [NSSortDescriptor(keyPath: \ModelInfo.name, ascending: true)], + predicate: NSPredicate(format: "providerRaw == %@", Provider.mlx.rawValue), + animation: .default + ) var models: FetchedResults + + // MARK: - User Defaults + + @Default(.enableGPUMemorySettings) var enableGPUMemorySettings + + var body: some View { + DividedVStack { + LabeledToggle(title: "Enable GPU Memory Settings", isOn: $enableGPUMemorySettings) + GPUMemorySettingsView() + modelListView() + } + .labeledContentStyle(.horizontal) + } +} + +// MARK: - Views + +extension MLXProviderContentView { + // MARK: - Model List View + + @ViewBuilder + private func modelListView() -> some View { + LabeledContent { + List { + // ForEach(providerModels, id: \.self) { + // MLXProviderModelItemView( + // model: $0, + // onDelete: onDelete + // ) + // } + // .padding(.horizontal, -8) + } + .listStyle(.plain) + .scrollIndicators(.hidden) + .frame(height: 200) + .scrollContentBackground(.hidden) + } label: { + HStack { + Text("Model List") + Spacer() + Button(action: { + isPresentedImport = true + }) { + Label("Import", systemImage: "plus.circle") + .padding(5) + } + .buttonStyle(LuminareCompactButtonStyle(extraCompact: true)) + .fileImporter( + isPresented: $isPresentedImport, + allowedContentTypes: [.folder], + allowsMultipleSelection: false + ) { result in + handleImport(result) + } + } + .frame(height: 35) + + } + .padding(.horizontal) + .labeledContentStyle(.vertical) + .compactSliderSecondaryColor(.white) + .task { + try! await loadProviderModels() + } + } +} + +// MARK: - Private Methods + +extension MLXProviderContentView { + private func handleImport(_ result: Result<[URL], Error>) { + Task { + do { + switch result { + case .success(let urls): + for url in urls { + let model = ModelInfo(context: viewContext) + model.id = url.absoluteString + model.name = url.lastPathComponent + model.path = url + model.provider = .mlx + } + try viewContext.save() + try await loadProviderModels() + case .failure(let error): + throw error + } + } catch { + appError(error) + } + } + } + + // MARK: - Delete + + private func onDelete(_ model: ProviderModel) { + Task { + // let documentsURL = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0] + // + // if let path = model.path { + // if path.standardized.path.hasPrefix(documentsURL.appendingPathComponent("huggingface/models").standardized.path) { + // try? FileManager.default.removeItem(at: path) + // } else { + // if let model = models.first(where: { $0.id == model.id }) { + // viewContext.delete(model) + // try? viewContext.save() + // } + // } + // try? await loadProviderModels() + // } + } + } + + // MARK: - Open Directory + + private func openDirectory(_ path: URL?) { + if let path { + NSWorkspace.shared.open(path) + } + } + + // MARK: - Load Provider Models + + private func loadProviderModels() async throws { + // var models = try MLXProvider.fetchModels() + // + // for model in self.models { + // if let index = models.firstIndex(where: { $0.id == model.id }) { + // models[index] = ProviderModel(from: model) + // } else { + // models.append(ProviderModel(from: model)) + // } + // } + // + // await MainActor.run { + // providerModels = models + // } + } +} diff --git a/ChatMLX/Features/Settings/Model Manager/Providers/MLX/MLXProviderLabelView.swift b/ChatMLX/Features/Settings/Model Manager/Providers/MLX/MLXProviderLabelView.swift new file mode 100644 index 0000000..0f92e75 --- /dev/null +++ b/ChatMLX/Features/Settings/Model Manager/Providers/MLX/MLXProviderLabelView.swift @@ -0,0 +1,20 @@ +// +// MLXProviderLabelView.swift +// ChatMLX +// +// Created by John Mai on 2024/10/26. +// + +import SwiftUI + +struct MLXProviderLabelView: View { + var body: some View { + HStack(spacing: 0) { + Text("ML") + .foregroundStyle(.black) + Text("X") + .foregroundStyle(Color(hex: "#D5D5D5")) + } + .font(.title2.weight(.medium)) + } +} diff --git a/ChatMLX/Features/Settings/Model Manager/Providers/MLX/MLXProviderModelItemView.swift b/ChatMLX/Features/Settings/Model Manager/Providers/MLX/MLXProviderModelItemView.swift new file mode 100644 index 0000000..d3e1322 --- /dev/null +++ b/ChatMLX/Features/Settings/Model Manager/Providers/MLX/MLXProviderModelItemView.swift @@ -0,0 +1,45 @@ +// +// MLXProviderModelItemView.swift +// ChatMLX +// +// Created by John Mai on 2024/10/26. +// +import SwiftUI + +struct MLXProviderModelItemView: View { + let model: ProviderModel + let onDelete: (ProviderModel) -> Void + + var body: some View { + EmptyView() + // HStack { + // Text(model.name ?? model.id) + // Spacer() + // Button(action: { + // openDirectory(model.path) + // }) { + // Image(systemName: "folder") + // } + // + // Button(action: { + // onDelete(model) + // }) { + // Image(systemName: "trash") + // .renderingMode(.original) + // } + // } + // .buttonStyle(.plain) + // .padding() + // .background(.black.opacity(0.3)) + // .listRowSeparator(.hidden) + // .clipShape(RoundedRectangle(cornerRadius: 10)) + // .shadow(color: .black, radius: 2) + // .listRowInsets(EdgeInsets(top: 0, leading: -5, bottom: 10, trailing: -5)) + } + + private func openDirectory(_ path: URL?) { + if let path { + NSWorkspace.shared.open(path) + } + } +} diff --git a/ChatMLX/Features/Settings/Model Manager/Providers/MLX/MLXProviderView.swift b/ChatMLX/Features/Settings/Model Manager/Providers/MLX/MLXProviderView.swift new file mode 100644 index 0000000..0fbc5c4 --- /dev/null +++ b/ChatMLX/Features/Settings/Model Manager/Providers/MLX/MLXProviderView.swift @@ -0,0 +1,18 @@ +// +// MLXProvider.swift +// ChatMLX +// +// Created by John Mai on 2024/10/4. +// + +import SwiftUI + +struct MLXProviderView: View { + var body: some View { + ProviderView(isExpanded: true, isEnabled: .constant(nil)) { + MLXProviderLabelView() + } content: { + MLXProviderContentView() + } + } +} diff --git a/ChatMLX/Features/Settings/Model Manager/Providers/OpenAI/OpenAIProviderView.swift b/ChatMLX/Features/Settings/Model Manager/Providers/OpenAI/OpenAIProviderView.swift new file mode 100644 index 0000000..e828d2f --- /dev/null +++ b/ChatMLX/Features/Settings/Model Manager/Providers/OpenAI/OpenAIProviderView.swift @@ -0,0 +1,66 @@ +// +// OpenAIProvider.swift +// ChatMLX +// +// Created by John Mai on 2024/10/4. +// + +import Defaults +import Luminare +import SwiftUI + +struct OpenAIProviderView: View { + @State var isExpanded: Bool = false + @State var isEnabled: Bool? = false + + @Default(.enableOpenAI) var enableOpenAI + @Default(.openAIApiKey) var openAIApiKey + @Default(.openAIBaseURL) var openAIBaseURL + + var body: some View { + ProviderView( + isEnabled: Binding( + get: { enableOpenAI }, + set: { enableOpenAI = $0 ?? false } + ) + ) { + Label() + } content: { + Content() + } + } + + @ViewBuilder + func Label() -> some View { + HStack { + Image("openai-logomark") + .resizable() + .scaledToFit() + .frame(height: 24) + Text("Other AI Provider") + } + .font(.title2.weight(.medium)) + } + + @ViewBuilder + func Content() -> some View { + DividedVStack { + LabeledContent("API Key") { + UltramanSecureField( + $openAIApiKey, + placeholder: Text("Enter your OpenAI API Key"), + alignment: .trailing + ) + } + + LabeledContent("API Proxy Address") { + UltramanTextField( + $openAIBaseURL, + placeholder: Text("Enter your OpenAI API Proxy Address"), + alignment: .trailing + ) + } + } + .labeledContentStyle(.horizontal) + } +} diff --git a/ChatMLX/Features/Settings/SettingsSidebarItemView.swift b/ChatMLX/Features/Settings/SettingsSidebarItemView.swift index 1c09dd1..d6a1009 100644 --- a/ChatMLX/Features/Settings/SettingsSidebarItemView.swift +++ b/ChatMLX/Features/Settings/SettingsSidebarItemView.swift @@ -8,7 +8,7 @@ import SwiftUI struct SettingsSidebarItemView: View { - @Environment(SettingsViewModel.self) var settingsViewModel + @Environment(SettingsStore.self) var store let tab: SettingsTab @@ -16,10 +16,6 @@ struct SettingsSidebarItemView: View { @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() @@ -56,12 +52,12 @@ struct SettingsSidebarItemView: View { .onHover { isHovering = $0 } .onAppear { checkIfSelfIsActiveTab() - showIndicator = tab.showIndicator?(settingsViewModel) ?? false + showIndicator = tab.showIndicator?() ?? false } - .onChange(of: settingsViewModel.activeTabID) { _, _ in + .onChange(of: store.activeTabID) { _, _ in checkIfSelfIsActiveTab() } - .onChange(of: tab.showIndicator?(settingsViewModel) ?? false) { + .onChange(of: tab.showIndicator?() ?? false) { _, newValue in withAnimation { showIndicator = newValue @@ -72,7 +68,7 @@ struct SettingsSidebarItemView: View { func checkIfSelfIsActiveTab() { withAnimation(.easeOut(duration: 0.1)) { - isActive = settingsViewModel.activeTabID == tab.id + isActive = store.activeTabID == tab.id } } } diff --git a/ChatMLX/Features/Settings/SettingsSidebarView.swift b/ChatMLX/Features/Settings/SettingsSidebarView.swift index ccbc8de..e7eba55 100644 --- a/ChatMLX/Features/Settings/SettingsSidebarView.swift +++ b/ChatMLX/Features/Settings/SettingsSidebarView.swift @@ -8,7 +8,7 @@ import SwiftUI struct SettingsSidebarView: View { - @Environment(SettingsViewModel.self) var settingsViewModel + @Environment(SettingsStore.self) var store let titlebarHeight: CGFloat = 50 let groupSpacing: CGFloat = 4 @@ -21,17 +21,18 @@ struct SettingsSidebarView: View { .init(.defaultConversation, Image(systemName: "person.bubble")), .init(.huggingFace, Image("huggingface")), .init(.models, Image(systemName: "brain")), + .init(.providers, Image(systemName: "brain")), .init(.mlxCommunity, Image("MLX")), .init( .downloadManager, Image(systemName: "arrow.down.circle"), - showIndicator: { $0.tasks.contains { $0.isDownloading } } + showIndicator: { DownloadStore.shared.tasks.contains { $0.isDownloading } } ), .init(.experimentalFeatures, Image(systemName: "flask")), .init(.about, Image(systemName: "info.circle")), ] var body: some View { - @Bindable var settingsViewModel = settingsViewModel + @Bindable var store = store VStack(alignment: .leading) { Group { Text("Settings") @@ -43,9 +44,10 @@ struct SettingsSidebarView: View { } .padding(.horizontal, itemPadding) - List(selection: $settingsViewModel.activeTabID) { + List(selection: $store.activeTabID) { ForEach(Self.tabs) { tab in - SettingsSidebarItemView(tab) + SettingsSidebarItemView(tab: tab) + .tag(tab.id) } } .scrollContentBackground(.hidden) diff --git a/ChatMLX/Features/Settings/SettingsView.swift b/ChatMLX/Features/Settings/SettingsView.swift index 76603d0..2c40a3f 100644 --- a/ChatMLX/Features/Settings/SettingsView.swift +++ b/ChatMLX/Features/Settings/SettingsView.swift @@ -8,16 +8,14 @@ import SwiftUI struct SettingsView: View { - @Environment(SettingsViewModel.self) var vm + @Environment(SettingsStore.self) var store var body: some View { - @Bindable var vm = vm - UltramanNavigationSplitView(sidebarWidth: 220) { SettingsSidebarView() } detail: { Group { - switch vm.activeTabID { + switch store.activeTabID { case .general: GeneralView() case .defaultConversation: @@ -26,6 +24,8 @@ struct SettingsView: View { HuggingFaceView() case .models: LocalModelsView() + case .providers: + ModelManagerView() case .downloadManager: DownloadManagerView() case .mlxCommunity: diff --git a/ChatMLX/Features/Theme/Theme.swift b/ChatMLX/Features/Theme/Theme.swift new file mode 100644 index 0000000..6d58b0c --- /dev/null +++ b/ChatMLX/Features/Theme/Theme.swift @@ -0,0 +1,16 @@ +// +// Theme.swift +// ChatMLX +// +// Created by John Mai on 2024/11/3. +// +import SwiftUI + +@MainActor +@Observable +final class Theme { + static let shared = Theme() + + let conversationDetailWidth: CGFloat = 550 + let padding: CGFloat = 8 +} diff --git a/ChatMLX/Features/View Models/ConversationViewModelOld.swift b/ChatMLX/Features/View Models/ConversationViewModelOld.swift new file mode 100644 index 0000000..cc1291c --- /dev/null +++ b/ChatMLX/Features/View Models/ConversationViewModelOld.swift @@ -0,0 +1,38 @@ +//// +//// ConversationViewModel.swift +//// ChatMLX +//// +//// Created by John Mai on 2024/10/3. +//// +// +//import SwiftUI +//import os +// +//@Observable +//class ConversationViewModelOld { +// var detailWidth: CGFloat = 550 +// var selectedConversation: Conversation? +// var error: Error? +// var errorTitle: String? +// var showErrorAlert = false +// +// private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "ConversationViewModel") +// +// 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/View Models/ModelManagerViewModel.swift b/ChatMLX/Features/View Models/ModelManagerViewModel.swift new file mode 100644 index 0000000..c6fee12 --- /dev/null +++ b/ChatMLX/Features/View Models/ModelManagerViewModel.swift @@ -0,0 +1,36 @@ +//// +//// ModelManagerViewModel.swift +//// ChatMLX +//// +//// Created by John Mai on 2024/10/10. +//// +// +//import SwiftUI +// +//@Observable +//class ModelManagerViewModel { +// var models: [ModelInfo] = [] +// +// init() { +// try? loadModels() +// } +// +// func loadModels() throws { +// +// } +// +// func deleteModel(_ model: ModelInfo) throws { +// guard let path = model.path else { +// return +// } +// +//// if !model.isExternal { +//// let fileManager = FileManager.default +//// try fileManager.removeItem(at: path) +//// } +// +// if let index = models.firstIndex(of: model) { +// models.remove(at: index) +// } +// } +//} diff --git a/ChatMLX/Features/Settings/SettingsViewModel.swift b/ChatMLX/Features/View Models/SettingsViewModel.swift similarity index 77% rename from ChatMLX/Features/Settings/SettingsViewModel.swift rename to ChatMLX/Features/View Models/SettingsViewModel.swift index ce26612..f91cfe8 100644 --- a/ChatMLX/Features/Settings/SettingsViewModel.swift +++ b/ChatMLX/Features/View Models/SettingsViewModel.swift @@ -5,9 +5,12 @@ // Created by John Mai on 2024/10/3. // import SwiftUI +import os @Observable -class SettingsViewModel { +class SettingsViewModel:@unchecked Sendable { + private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "SettingsViewModel") + var tasks: [DownloadTask] = [] var sidebarWidth: CGFloat = 250 var activeTabID: SettingsTab.ID = .general 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/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/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/Resources/Assets.xcassets/AccentColor.colorset/Contents.json b/ChatMLX/Resources/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000..274babb --- /dev/null +++ b/ChatMLX/Resources/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,20 @@ +{ + "colors" : [ + { + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ChatMLX/Assets.xcassets/AppIcon.appiconset/1024.png b/ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/1024.png similarity index 100% rename from ChatMLX/Assets.xcassets/AppIcon.appiconset/1024.png rename to ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/1024.png diff --git a/ChatMLX/Assets.xcassets/AppIcon.appiconset/128.png b/ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/128.png similarity index 100% rename from ChatMLX/Assets.xcassets/AppIcon.appiconset/128.png rename to ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/128.png diff --git a/ChatMLX/Assets.xcassets/AppIcon.appiconset/16.png b/ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/16.png similarity index 100% rename from ChatMLX/Assets.xcassets/AppIcon.appiconset/16.png rename to ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/16.png diff --git a/ChatMLX/Assets.xcassets/AppIcon.appiconset/256 1.png b/ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/256 1.png similarity index 100% rename from ChatMLX/Assets.xcassets/AppIcon.appiconset/256 1.png rename to ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/256 1.png diff --git a/ChatMLX/Assets.xcassets/AppIcon.appiconset/256.png b/ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/256.png similarity index 100% rename from ChatMLX/Assets.xcassets/AppIcon.appiconset/256.png rename to ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/256.png diff --git a/ChatMLX/Assets.xcassets/AppIcon.appiconset/32 1.png b/ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/32 1.png similarity index 100% rename from ChatMLX/Assets.xcassets/AppIcon.appiconset/32 1.png rename to ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/32 1.png diff --git a/ChatMLX/Assets.xcassets/AppIcon.appiconset/32.png b/ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/32.png similarity index 100% rename from ChatMLX/Assets.xcassets/AppIcon.appiconset/32.png rename to ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/32.png diff --git a/ChatMLX/Assets.xcassets/AppIcon.appiconset/512 1.png b/ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/512 1.png similarity index 100% rename from ChatMLX/Assets.xcassets/AppIcon.appiconset/512 1.png rename to ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/512 1.png diff --git a/ChatMLX/Assets.xcassets/AppIcon.appiconset/512.png b/ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/512.png similarity index 100% rename from ChatMLX/Assets.xcassets/AppIcon.appiconset/512.png rename to ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/512.png diff --git a/ChatMLX/Assets.xcassets/AppIcon.appiconset/64.png b/ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/64.png similarity index 100% rename from ChatMLX/Assets.xcassets/AppIcon.appiconset/64.png rename to ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/64.png diff --git a/ChatMLX/Assets.xcassets/AppIcon.appiconset/Contents.json b/ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json similarity index 100% rename from ChatMLX/Assets.xcassets/AppIcon.appiconset/Contents.json rename to ChatMLX/Resources/Assets.xcassets/AppIcon.appiconset/Contents.json diff --git a/ChatMLX/Assets.xcassets/AppLogo.imageset/Contents.json b/ChatMLX/Resources/Assets.xcassets/AppLogo.imageset/Contents.json similarity index 100% rename from ChatMLX/Assets.xcassets/AppLogo.imageset/Contents.json rename to ChatMLX/Resources/Assets.xcassets/AppLogo.imageset/Contents.json diff --git a/ChatMLX/Assets.xcassets/AppLogo.imageset/logo.png b/ChatMLX/Resources/Assets.xcassets/AppLogo.imageset/logo.png similarity index 100% rename from ChatMLX/Assets.xcassets/AppLogo.imageset/logo.png rename to ChatMLX/Resources/Assets.xcassets/AppLogo.imageset/logo.png diff --git a/ChatMLX/Assets.xcassets/Contents.json b/ChatMLX/Resources/Assets.xcassets/Contents.json similarity index 100% rename from ChatMLX/Assets.xcassets/Contents.json rename to ChatMLX/Resources/Assets.xcassets/Contents.json diff --git a/ChatMLX/Assets.xcassets/MLX.imageset/1028322432.png b/ChatMLX/Resources/Assets.xcassets/MLX.imageset/1028322432.png similarity index 100% rename from ChatMLX/Assets.xcassets/MLX.imageset/1028322432.png rename to ChatMLX/Resources/Assets.xcassets/MLX.imageset/1028322432.png diff --git a/ChatMLX/Assets.xcassets/MLX.imageset/Contents.json b/ChatMLX/Resources/Assets.xcassets/MLX.imageset/Contents.json similarity index 100% rename from ChatMLX/Assets.xcassets/MLX.imageset/Contents.json rename to ChatMLX/Resources/Assets.xcassets/MLX.imageset/Contents.json diff --git a/ChatMLX/Resources/Assets.xcassets/MLX2.imageset/Contents.json b/ChatMLX/Resources/Assets.xcassets/MLX2.imageset/Contents.json new file mode 100644 index 0000000..d036be4 --- /dev/null +++ b/ChatMLX/Resources/Assets.xcassets/MLX2.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "MLX2.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ChatMLX/Resources/Assets.xcassets/MLX2.imageset/MLX2.png b/ChatMLX/Resources/Assets.xcassets/MLX2.imageset/MLX2.png new file mode 100644 index 0000000..22a8980 Binary files /dev/null and b/ChatMLX/Resources/Assets.xcassets/MLX2.imageset/MLX2.png differ diff --git a/ChatMLX/Assets.xcassets/clear.imageset/Contents.json b/ChatMLX/Resources/Assets.xcassets/clear.imageset/Contents.json similarity index 100% rename from ChatMLX/Assets.xcassets/clear.imageset/Contents.json rename to ChatMLX/Resources/Assets.xcassets/clear.imageset/Contents.json diff --git a/ChatMLX/Assets.xcassets/clear.imageset/clear-l.svg b/ChatMLX/Resources/Assets.xcassets/clear.imageset/clear-l.svg similarity index 100% rename from ChatMLX/Assets.xcassets/clear.imageset/clear-l.svg rename to ChatMLX/Resources/Assets.xcassets/clear.imageset/clear-l.svg diff --git a/ChatMLX/Assets.xcassets/huggingface.imageset/Contents.json b/ChatMLX/Resources/Assets.xcassets/huggingface.imageset/Contents.json similarity index 100% rename from ChatMLX/Assets.xcassets/huggingface.imageset/Contents.json rename to ChatMLX/Resources/Assets.xcassets/huggingface.imageset/Contents.json diff --git a/ChatMLX/Assets.xcassets/huggingface.imageset/hf-logo-pirate.svg b/ChatMLX/Resources/Assets.xcassets/huggingface.imageset/hf-logo-pirate.svg similarity index 100% rename from ChatMLX/Assets.xcassets/huggingface.imageset/hf-logo-pirate.svg rename to ChatMLX/Resources/Assets.xcassets/huggingface.imageset/hf-logo-pirate.svg diff --git a/ChatMLX/Assets.xcassets/markdown.imageset/Contents.json b/ChatMLX/Resources/Assets.xcassets/markdown.imageset/Contents.json similarity index 100% rename from ChatMLX/Assets.xcassets/markdown.imageset/Contents.json rename to ChatMLX/Resources/Assets.xcassets/markdown.imageset/Contents.json diff --git a/ChatMLX/Assets.xcassets/markdown.imageset/markdown (2).svg b/ChatMLX/Resources/Assets.xcassets/markdown.imageset/markdown (2).svg similarity index 100% rename from ChatMLX/Assets.xcassets/markdown.imageset/markdown (2).svg rename to ChatMLX/Resources/Assets.xcassets/markdown.imageset/markdown (2).svg diff --git a/ChatMLX/Resources/Assets.xcassets/menubarIcon.imageset/Contents.json b/ChatMLX/Resources/Assets.xcassets/menubarIcon.imageset/Contents.json new file mode 100644 index 0000000..38d5dd1 --- /dev/null +++ b/ChatMLX/Resources/Assets.xcassets/menubarIcon.imageset/Contents.json @@ -0,0 +1,23 @@ +{ + "images" : [ + { + "filename" : "menubarIcon@1x.png", + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "menubarIcon@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "menubarIcon@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ChatMLX/Resources/Assets.xcassets/menubarIcon.imageset/menubarIcon@1x.png b/ChatMLX/Resources/Assets.xcassets/menubarIcon.imageset/menubarIcon@1x.png new file mode 100644 index 0000000..7a7fbe8 Binary files /dev/null and b/ChatMLX/Resources/Assets.xcassets/menubarIcon.imageset/menubarIcon@1x.png differ diff --git a/ChatMLX/Resources/Assets.xcassets/menubarIcon.imageset/menubarIcon@2x.png b/ChatMLX/Resources/Assets.xcassets/menubarIcon.imageset/menubarIcon@2x.png new file mode 100644 index 0000000..8fe13c4 Binary files /dev/null and b/ChatMLX/Resources/Assets.xcassets/menubarIcon.imageset/menubarIcon@2x.png differ diff --git a/ChatMLX/Resources/Assets.xcassets/menubarIcon.imageset/menubarIcon@3x.png b/ChatMLX/Resources/Assets.xcassets/menubarIcon.imageset/menubarIcon@3x.png new file mode 100644 index 0000000..dc3d9a6 Binary files /dev/null and b/ChatMLX/Resources/Assets.xcassets/menubarIcon.imageset/menubarIcon@3x.png differ diff --git a/ChatMLX/Resources/Assets.xcassets/openai-lockup.imageset/Contents.json b/ChatMLX/Resources/Assets.xcassets/openai-lockup.imageset/Contents.json new file mode 100644 index 0000000..3bba569 --- /dev/null +++ b/ChatMLX/Resources/Assets.xcassets/openai-lockup.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "openai-lockup.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ChatMLX/Resources/Assets.xcassets/openai-lockup.imageset/openai-lockup.svg b/ChatMLX/Resources/Assets.xcassets/openai-lockup.imageset/openai-lockup.svg new file mode 100644 index 0000000..859d7af --- /dev/null +++ b/ChatMLX/Resources/Assets.xcassets/openai-lockup.imageset/openai-lockup.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ChatMLX/Resources/Assets.xcassets/openai-logomark.imageset/Contents.json b/ChatMLX/Resources/Assets.xcassets/openai-logomark.imageset/Contents.json new file mode 100644 index 0000000..6d16a8f --- /dev/null +++ b/ChatMLX/Resources/Assets.xcassets/openai-logomark.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "openai-logomark.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ChatMLX/Resources/Assets.xcassets/openai-logomark.imageset/openai-logomark.svg b/ChatMLX/Resources/Assets.xcassets/openai-logomark.imageset/openai-logomark.svg new file mode 100644 index 0000000..e04db75 --- /dev/null +++ b/ChatMLX/Resources/Assets.xcassets/openai-logomark.imageset/openai-logomark.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/ChatMLX/Resources/Assets.xcassets/openai-white-lockup.imageset/Contents.json b/ChatMLX/Resources/Assets.xcassets/openai-white-lockup.imageset/Contents.json new file mode 100644 index 0000000..53b2311 --- /dev/null +++ b/ChatMLX/Resources/Assets.xcassets/openai-white-lockup.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "openai-white-lockup.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ChatMLX/Resources/Assets.xcassets/openai-white-lockup.imageset/openai-white-lockup.svg b/ChatMLX/Resources/Assets.xcassets/openai-white-lockup.imageset/openai-white-lockup.svg new file mode 100644 index 0000000..610e5aa --- /dev/null +++ b/ChatMLX/Resources/Assets.xcassets/openai-white-lockup.imageset/openai-white-lockup.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/ChatMLX/Resources/Assets.xcassets/openai-white-logomark.imageset/Contents.json b/ChatMLX/Resources/Assets.xcassets/openai-white-logomark.imageset/Contents.json new file mode 100644 index 0000000..5a327fe --- /dev/null +++ b/ChatMLX/Resources/Assets.xcassets/openai-white-logomark.imageset/Contents.json @@ -0,0 +1,21 @@ +{ + "images" : [ + { + "filename" : "openai-white-logomark.svg", + "idiom" : "universal", + "scale" : "1x" + }, + { + "idiom" : "universal", + "scale" : "2x" + }, + { + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/ChatMLX/Resources/Assets.xcassets/openai-white-logomark.imageset/openai-white-logomark.svg b/ChatMLX/Resources/Assets.xcassets/openai-white-logomark.imageset/openai-white-logomark.svg new file mode 100644 index 0000000..b3df81f --- /dev/null +++ b/ChatMLX/Resources/Assets.xcassets/openai-white-logomark.imageset/openai-white-logomark.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/ChatMLX/Assets.xcassets/plaintext.imageset/Contents.json b/ChatMLX/Resources/Assets.xcassets/plaintext.imageset/Contents.json similarity index 100% rename from ChatMLX/Assets.xcassets/plaintext.imageset/Contents.json rename to ChatMLX/Resources/Assets.xcassets/plaintext.imageset/Contents.json diff --git a/ChatMLX/Assets.xcassets/plaintext.imageset/doc-plaintext (1).svg b/ChatMLX/Resources/Assets.xcassets/plaintext.imageset/doc-plaintext (1).svg similarity index 100% rename from ChatMLX/Assets.xcassets/plaintext.imageset/doc-plaintext (1).svg rename to ChatMLX/Resources/Assets.xcassets/plaintext.imageset/doc-plaintext (1).svg diff --git a/ChatMLX/Localizable.xcstrings b/ChatMLX/Resources/Localizable.xcstrings similarity index 94% rename from ChatMLX/Localizable.xcstrings rename to ChatMLX/Resources/Localizable.xcstrings index 201d43e..84b0f4f 100644 --- a/ChatMLX/Localizable.xcstrings +++ b/ChatMLX/Resources/Localizable.xcstrings @@ -219,19 +219,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" : "" @@ -457,19 +457,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" @@ -695,19 +695,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%%" @@ -933,19 +933,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" @@ -1171,19 +1171,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" @@ -1415,12 +1415,6 @@ "value" : "%1$lld / %2$lld" } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "%1$lld / %2$lld" - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -1432,243 +1426,11 @@ "state" : "translated", "value" : "%1$lld / %2$lld" } - } - } - }, - "%lldM" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "‏%lld مليون" - } - }, - "ca" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld M" - } - }, - "cs" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld M" - } - }, - "el" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "en-AU" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "en-GB" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "en-IN" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "es-419" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld M" - } - }, - "fi" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld Mt" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "fr-CA" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "hr" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldJuta" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldJ" - } - }, - "nb" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld M" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld M" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lld МБ" - } - }, - "sk" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldМ" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } }, "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "%lldM" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "%lldM" + "value" : "%1$lld / %2$lld" } } } @@ -1891,19 +1653,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,25 +1892,37 @@ "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" : "關於" } } } + }, + "An error has occurred!" : { + + }, + "Any to Any" : { + + }, + "API Key" : { + + }, + "API Proxy Address" : { + }, "Apple Intelligence Effect" : { "localizations" : { @@ -2368,22 +2142,22 @@ "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 智能效果" } } } @@ -2606,19 +2380,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" : "您確定要刪除「%@」嗎?" @@ -2844,19 +2618,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" : "模糊" @@ -3082,19 +2856,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" : "取消" @@ -3320,19 +3094,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 +3333,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 +3572,6 @@ "value" : "Đang kiểm tra..." } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "正在檢查..." - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -3815,6 +3583,12 @@ "state" : "translated", "value" : "正在檢查" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "正在檢查..." + } } } }, @@ -4037,19 +3811,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" : "清除" @@ -4275,19 +4049,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" : "清除所有對話" @@ -4513,19 +4287,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" : "顏色" @@ -4751,19 +4525,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" : "確認刪除" @@ -4989,19 +4763,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" : "對話標題" @@ -5227,12 +5001,6 @@ "value" : "Sao chép" } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "複製" - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -5244,6 +5012,12 @@ "state" : "translated", "value" : "拷貝" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "複製" + } } } }, @@ -5465,12 +5239,6 @@ "value" : "Điểm Kết Thúc Tùy Chỉnh" } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "自訂端點" - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -5482,6 +5250,12 @@ "state" : "translated", "value" : "自定義端點" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "自訂端點" + } } } }, @@ -5703,12 +5477,6 @@ "value" : "Điểm cuối Custom Hugging Face" } }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "自定 Hugging Face 端點" - } - }, "zh-Hans" : { "stringUnit" : { "state" : "translated", @@ -5720,6436 +5488,6460 @@ "state" : "translated", "value" : "自訂 Hugging Face 端點" } + }, + "zh-HK" : { + "stringUnit" : { + "state" : "translated", + "value" : "自定 Hugging Face 端點" + } } } }, - "Default Conversation" : { + "Default conversation title" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "المحادثة الافتراضية" + "value" : "عنوان المحادثة الافتراضي" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Conversa predeterminada" + "value" : "Títol de la conversa per defecte" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Výchozí konverzace" + "value" : "Výchozí název konverzace" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Standard samtale" + "value" : "Standard samtaletitel" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standardunterhaltung" + "value" : "Standard-Konversationstitel" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Προεπιλεγμένη Συνομιλία" + "value" : "Προεπιλεγμένος τίτλος συνομιλίας" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Default Conversation" + "value" : "Default conversation title" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Default Conversation" + "value" : "Default conversation title" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Default Conversation" + "value" : "Default conversation title" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Conversación predeterminada" + "value" : "Título de conversación predeterminado" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Conversación predeterminada" + "value" : "Título predeterminado de la conversación" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Oletuskeskustelu" + "value" : "Keskustelun oletusnimeke" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Conversation par défaut" + "value" : "Titre de conversation par défaut" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Conversation par défaut" + "value" : "Titre de la conversation par défaut" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "שיחה ברירת מחדל" + "value" : "כותרת שיחה ברירת מחדל" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "डिफ़ॉल्ट बातचीत" + "value" : "डिफ़ॉल्ट बातचीत शीर्षक" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Zadani razgovor" + "value" : "Zadani naslov razgovora" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Alapértelmezett beszélgetés" + "value" : "Alapértelmezett beszélgetés címe" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Percakapan Default" + "value" : "Judul percakapan default" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Conversazione predefinita" + "value" : "Titolo conversazione predefinito" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "デフォルトの会話" + "value" : "デフォルトの会話タイトル" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "기본 대화" + "value" : "기본 대화 제목" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Perbualan Lalai" + "value" : "Tajuk perbualan lalai" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Standardkonversasjon" + "value" : "Standard samtaletittel" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Standaardgesprek" + "value" : "Standaard gesprekstitel" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Domyślna konwersacja" + "value" : "Domyślny tytuł rozmowy" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Conversa Padrão" + "value" : "Título da conversa padrão" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Conversa Predefinida" + "value" : "Título de conversa padrão" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Conversație implicită" + "value" : "Titlu conversație implicită" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Разговор по умолчанию" + "value" : "Название беседы по умолчанию" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Predvolená konverzácia" + "value" : "Predvolený názov konverzácie" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Standardkonversation" + "value" : "Standardkonversationstitel" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "การสนทนาเริ่มต้น" + "value" : "ชื่อการสนทนาเริ่มต้น" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Varsayılan Konuşma" + "value" : "Varsayılan sohbet başlığı" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Розмова за замовчуванням" + "value" : "Типова назва розмови" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Đoạn hội thoại mặc định" + "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" : "預設對話" + "value" : "預設對話標題" } } } }, - "Default conversation title" : { + "Delete" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "عنوان المحادثة الافتراضي" + "value" : "مسح" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Títol de la conversa per defecte" + "value" : "Esborrar" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Výchozí název konverzace" + "value" : "Smazat" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Standard samtaletitel" + "value" : "Slet" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Standard-Konversationstitel" + "value" : "Löschen" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Προεπιλεγμένος τίτλος συνομιλίας" + "value" : "Διαγραφή" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Default conversation title" + "value" : "Delete" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Default conversation title" + "value" : "Delete" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Default conversation title" + "value" : "Delete" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Título de conversación predeterminado" + "value" : "Eliminar" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Título predeterminado de la conversación" + "value" : "Eliminar" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Keskustelun oletusnimeke" + "value" : "Poista" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Titre de conversation par défaut" + "value" : "Supprimer" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Titre de la conversation par défaut" + "value" : "Supprimer" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "כותרת שיחה ברירת מחדל" + "value" : "מחק" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "डिफ़ॉल्ट बातचीत शीर्षक" + "value" : "हटाएं" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Zadani naslov razgovora" + "value" : "Izbriši" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Alapértelmezett beszélgetés címe" + "value" : "Törlés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Judul percakapan default" + "value" : "Hapus" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Titolo conversazione predefinito" + "value" : "Elimina" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "デフォルトの会話タイトル" + "value" : "削除" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "기본 대화 제목" + "value" : "삭제" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Tajuk perbualan lalai" + "value" : "Padam" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Standard samtaletittel" + "value" : "Slett" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Standaard gesprekstitel" + "value" : "Verwijderen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Domyślny tytuł rozmowy" + "value" : "Usuń" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Título da conversa padrão" + "value" : "Apagar" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Título de conversa padrão" + "value" : "Eliminar" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Titlu conversație implicită" + "value" : "Ștergeți" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Название беседы по умолчанию" + "value" : "Удалить" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Predvolený názov konverzácie" + "value" : "Odstrániť" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Standardkonversationstitel" + "value" : "Radera" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ชื่อการสนทนาเริ่มต้น" + "value" : "ลบ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Varsayılan sohbet başlığı" + "value" : "Sil" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Типова назва розмови" + "value" : "Видалити" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Tiêu đề cuộc trò chuyện mặc định" + "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" : "預設對話標題" + "value" : "刪除" } } } }, - "Delete" : { + "Display Mode" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "مسح" + "value" : "طريقة العرض" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Esborrar" + "value" : "Mode de visualització" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Smazat" + "value" : "Režim zobrazení" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Slet" + "value" : "Visningstilstand" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Löschen" + "value" : "Anzeige-Modus" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Διαγραφή" + "value" : "Λειτουργία εμφάνισης" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Delete" + "value" : "Display Mode" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Delete" + "value" : "Display Mode" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Delete" + "value" : "Display Mode" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar" + "value" : "Modo de visualización" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar" + "value" : "Modo de visualización" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Poista" + "value" : "Näyttötila" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimer" + "value" : "Mode dʼaffichage" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Supprimer" + "value" : "Mode d'affichage" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "מחק" + "value" : "מצב תצוגה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "हटाएं" + "value" : "डिस्प्ले मोड" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Izbriši" + "value" : "Način prikaza" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Törlés" + "value" : "Megjelenítési mód" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Hapus" + "value" : "Mode Tampilan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Elimina" + "value" : "Modalità di visualizzazione" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "削除" + "value" : "表示モード" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "삭제" + "value" : "화면 모드" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Padam" + "value" : "Mod Paparan" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Slett" + "value" : "Visningsmodus" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verwijderen" + "value" : "Weergavemodus" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Usuń" + "value" : "Tryb wyświetlania" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Apagar" + "value" : "Modo de Exibição" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Eliminar" + "value" : "Modo de Exibição" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Ștergeți" + "value" : "Mod de afișare" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Удалить" + "value" : "Режим отображения" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Odstrániť" + "value" : "Režim zobrazenia" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Radera" + "value" : "Visningsläget" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ลบ" + "value" : "โหมดการแสดงผล" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Sil" + "value" : "Görüntüleme Modu" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Видалити" + "value" : "Режим відображення" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Xóa" + "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" : "刪除" + "value" : "顯示模式" } } } }, - "Display Mode" : { + "Done" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "طريقة العرض" + "value" : "تم" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Mode de visualització" + "value" : "Fet" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Režim zobrazení" + "value" : "Hotovo" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Visningstilstand" + "value" : "OK" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Anzeige-Modus" + "value" : "Fertig" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Λειτουργία εμφάνισης" + "value" : "Τέλος" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Display Mode" + "value" : "Done" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Display Mode" + "value" : "Done" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Display Mode" + "value" : "Done" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Modo de visualización" + "value" : "OK" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Modo de visualización" + "value" : "OK" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Näyttötila" + "value" : "Valmis" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Mode dʼaffichage" + "value" : "Terminé" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Mode d'affichage" + "value" : "OK" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "מצב תצוגה" + "value" : "סיום" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "डिस्प्ले मोड" + "value" : "पूर्ण करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Način prikaza" + "value" : "Završi" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Megjelenítési mód" + "value" : "Kész" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Mode Tampilan" + "value" : "Selesai" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Modalità di visualizzazione" + "value" : "Fine" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "表示モード" + "value" : "完了" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "화면 모드" + "value" : "완료" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Mod Paparan" + "value" : "Selesai" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Visningsmodus" + "value" : "Ferdig" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Weergavemodus" + "value" : "Gereed" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Tryb wyświetlania" + "value" : "Gotowe" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Modo de Exibição" + "value" : "OK" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Modo de Exibição" + "value" : "Concluído" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Mod de afișare" + "value" : "OK" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Режим отображения" + "value" : "Готово" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Režim zobrazenia" + "value" : "Hotovo" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Visningsläget" + "value" : "Klar" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "โหมดการแสดงผล" + "value" : "เสร็จสิ้น" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Görüntüleme Modu" + "value" : "Bitti" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Режим відображення" + "value" : "Готово" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Chế độ hiển thị" + "value" : "Xong" } }, - "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" : "完成" } } } }, - "Done" : { + "Download Manager" : { "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "تم" + "value" : "إدارة التنزيلات" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Fet" + "value" : "Gestor de descàrregues" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Hotovo" + "value" : "Správce stahování" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Overførselsstyring" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Fertig" + "value" : "Download-Manager" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Τέλος" + "value" : "Διαχειριστής Λήψεων" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Done" + "value" : "Download Manager" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Done" + "value" : "Download Manager" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Done" + "value" : "Download Manager" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Gestor de Descargas" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Gestor de Descargas" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Valmis" + "value" : "Latausten Hallinta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Terminé" + "value" : "Gestionnaire de téléchargements" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Gestionnaire de téléchargement" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "סיום" + "value" : "מנהל הורדות" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "पूर्ण करें" + "value" : "डाउनलोड मैनेजर" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Završi" + "value" : "Upravitelj preuzimanja" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Kész" + "value" : "Letöltéskezelő" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Selesai" + "value" : "Pengelola Unduhan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Fine" + "value" : "Gestore dei download" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "完了" + "value" : "ダウンロードマネージャー" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "완료" + "value" : "다운로드 관리자" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Selesai" + "value" : "Pengurus Muat Turun" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ferdig" + "value" : "Nedlastingsbehandling" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Gereed" + "value" : "Downloadbeheerder" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Gotowe" + "value" : "Menedżer pobierania" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Gerenciador de Downloads" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Concluído" + "value" : "Gestor de Transferências" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "OK" + "value" : "Manager de descărcări" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Готово" + "value" : "Менеджер загрузок" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Hotovo" + "value" : "Manažér sťahovania" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Klar" + "value" : "Nedladdningshanterare" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "เสร็จสิ้น" + "value" : "ตัวจัดการดาวน์โหลด" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Bitti" + "value" : "İndirme Yöneticisi" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Готово" + "value" : "Менеджер завантажень" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Xong" + "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" : "完成" + "value" : "下載管理器" } } } }, - "Download Manager" : { - "extractionState" : "manual", + "Endpoint" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "إدارة التنزيلات" + "value" : "نقطة النهاية" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Gestor de descàrregues" + "value" : "Punt final" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Správce stahování" + "value" : "Koncový bod" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Overførselsstyring" + "value" : "Slutpunkt" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Download-Manager" + "value" : "Endpunkt" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Διαχειριστής Λήψεων" + "value" : "Σημείο Τερματισμού" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Download Manager" + "value" : "Endpoint" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Download Manager" + "value" : "Endpoint" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Download Manager" + "value" : "Endpoint" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Gestor de Descargas" + "value" : "Punto de conexión" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Gestor de Descargas" + "value" : "Punto de conexión" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Latausten Hallinta" + "value" : "Loppupiste" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Gestionnaire de téléchargements" + "value" : "Point de terminaison" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Gestionnaire de téléchargement" + "value" : "Point de terminaison" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "מנהל הורדות" + "value" : "נקודת קצה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "डाउनलोड मैनेजर" + "value" : "एंडपॉइंट" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Upravitelj preuzimanja" + "value" : "Krajnja točka" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Letöltéskezelő" + "value" : "Végpont" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Pengelola Unduhan" + "value" : "Endpoint" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Gestore dei download" + "value" : "Endpoint" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "ダウンロードマネージャー" + "value" : "エンドポイント" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "다운로드 관리자" + "value" : "엔드포인트" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Pengurus Muat Turun" + "value" : "Titik akhir" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Nedlastingsbehandling" + "value" : "Endepunkt" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Downloadbeheerder" + "value" : "Eindpunt" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Menedżer pobierania" + "value" : "Punkt końcowy" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Gerenciador de Downloads" + "value" : "Endpoint" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Gestor de Transferências" + "value" : "Endpoint" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Manager de descărcări" + "value" : "Punct final" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Менеджер загрузок" + "value" : "Конечная точка" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Manažér sťahovania" + "value" : "Koncový bod" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Nedladdningshanterare" + "value" : "Slutpunkt" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ตัวจัดการดาวน์โหลด" + "value" : "เอนด์พอยต์" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "İndirme Yöneticisi" + "value" : "Uç Nokta" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Менеджер завантажень" + "value" : "Кінцева точка" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Trình quản lý Tải xuống" + "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" : "下載管理器" + "value" : "端點" } } } }, - "Endpoint" : { + "Enter Full Screen" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "نقطة النهاية" + "value" : "دخول وضع ملء الشاشة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Punt final" + "value" : "Pantalla completa" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Koncový bod" + "value" : "Zobrazit na celou obrazovku" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Slutpunkt" + "value" : "Fuld skærm" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Endpunkt" + "value" : "Vollbildmodus aktivieren" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Σημείο Τερματισμού" + "value" : "Πλήρης Οθόνη" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint" + "value" : "Enter Full Screen" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint" + "value" : "Enter Full Screen" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint" + "value" : "Enter Full Screen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Punto de conexión" + "value" : "Entrar en pantalla completa" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Punto de conexión" + "value" : "Pantalla completa" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Loppupiste" + "value" : "Siirry koko näytön tilaan" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Point de terminaison" + "value" : "Passer en plein écran" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Point de terminaison" + "value" : "Mode Plein Écran" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "נקודת קצה" + "value" : "כניסה למסך מלא" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "एंडपॉइंट" + "value" : "पूर्ण स्क्रीन दर्ज करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Krajnja točka" + "value" : "Prijeđi na cijeli zaslon" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Végpont" + "value" : "Teljes képernyőre lépés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint" + "value" : "Masuk Layar Penuh" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint" + "value" : "Entra in Full Screen" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "エンドポイント" + "value" : "フルスクリーンにする" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "엔드포인트" + "value" : "전체 화면으로 전환" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Titik akhir" + "value" : "Masuk Skrin Penuh" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Endepunkt" + "value" : "Gå til fullskjerm" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Eindpunt" + "value" : "Volledig scherm openen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Punkt końcowy" + "value" : "Pełny ekran" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint" + "value" : "Tela Cheia" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint" + "value" : "Entrar em Ecrã Completo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Punct final" + "value" : "Intră în Ecran Complet" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Конечная точка" + "value" : "Перейти в полноэкранный режим" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Koncový bod" + "value" : "Režim celej obrazovky" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Slutpunkt" + "value" : "Ange fullskärm" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "เอนด์พอยต์" + "value" : "เข้าสู่โหมดเต็มจอ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Uç Nokta" + "value" : "Tam Ekrana Geç" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Кінцева точка" + "value" : "На весь екран" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Điểm cuối" + "value" : "Toàn Màn 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" : "全螢幕模式" } } } }, - "Enter Full Screen" : { + "Enter your Hugging Face token" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "دخول وضع ملء الشاشة" + "value" : "أدخل رمز Hugging Face الخاص بك" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Pantalla completa" + "value" : "Introdueix el teu token de Hugging Face" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Zobrazit na celou obrazovku" + "value" : "Zadejte váš token Hugging Face" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Fuld skærm" + "value" : "Indtast din Hugging Face-token" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vollbildmodus aktivieren" + "value" : "Gib dein Hugging Face-Token ein" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Πλήρης Οθόνη" + "value" : "Εισαγάγετε το Hugging Face token σας" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Enter Full Screen" + "value" : "Enter your Hugging Face token" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Enter Full Screen" + "value" : "Enter your Hugging Face token" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Enter Full Screen" + "value" : "Enter your Hugging Face token" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Entrar en pantalla completa" + "value" : "Introduce tu token de Hugging Face" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Pantalla completa" + "value" : "Introduce tu token de Hugging Face" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Siirry koko näytön tilaan" + "value" : "Anna Hugging Face -tunnuksesi" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Passer en plein écran" + "value" : "Entrez votre jeton Hugging Face" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Mode Plein Écran" + "value" : "Entrez votre jeton Hugging Face" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "כניסה למסך מלא" + "value" : "הזן את הטוקן שלך ל-Hugging Face" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "पूर्ण स्क्रीन दर्ज करें" + "value" : "अपना Hugging Face टोकन दर्ज करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Prijeđi na cijeli zaslon" + "value" : "Unesite svoj Hugging Face token" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Teljes képernyőre lépés" + "value" : "Adja meg a Hugging Face tokenjét" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Masuk Layar Penuh" + "value" : "Masukkan token Hugging Face Anda" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Entra in Full Screen" + "value" : "Inserisci il tuo token Hugging Face" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "フルスクリーンにする" + "value" : "Hugging Faceトークンを入力" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "전체 화면으로 전환" + "value" : "Hugging Face 토큰을 입력하세요" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Masuk Skrin Penuh" + "value" : "Masukkan token Hugging Face anda" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Gå til fullskjerm" + "value" : "Skriv inn Hugging Face-tokenet ditt" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Volledig scherm openen" + "value" : "Voer je Hugging Face-token in" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Pełny ekran" + "value" : "Wprowadź swój token Hugging Face" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Tela Cheia" + "value" : "Insira seu token do Hugging Face" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Entrar em Ecrã Completo" + "value" : "Introduza o seu token do Hugging Face" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Intră în Ecran Complet" + "value" : "Introduceți tokenul dvs. Hugging Face" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Перейти в полноэкранный режим" + "value" : "Введите ваш токен Hugging Face" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Režim celej obrazovky" + "value" : "Zadajte svoj Hugging Face token" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Ange fullskärm" + "value" : "Ange din Hugging Face-token" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "เข้าสู่โหมดเต็มจอ" + "value" : "ป้อนโทเค็น Hugging Face ของคุณ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Tam Ekrana Geç" + "value" : "Hugging Face tokenınızı girin" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "На весь екран" + "value" : "Введіть ваш токен Hugging Face" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Toàn Màn Hình" + "value" : "Nhập mã thông báo Hugging Face của bạn" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "全螢幕模式" + "value" : "输入您的 Hugging Face 令牌" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "进入全屏模式" + "value" : "輸入您的 Hugging Face 令牌" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "進入全螢幕模式" + "value" : "輸入你的 Hugging Face 令牌" } } } }, - "Enter your Hugging Face token" : { + "Enter your OpenAI API Key" : { + + }, + "Enter your OpenAI API Proxy Address" : { + + }, + "Exit Full Screen" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "أدخل رمز Hugging Face الخاص بك" + "value" : "إنهاء ملء الشاشة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Introdueix el teu token de Hugging Face" + "value" : "Sortir de la pantalla completa" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Zadejte váš token Hugging Face" + "value" : "Ukončit režim celé obrazovky" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Indtast din Hugging Face-token" + "value" : "Afslut fuld skærm" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Gib dein Hugging Face-Token ein" + "value" : "Vollbildmodus beenden" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Εισαγάγετε το Hugging Face token σας" + "value" : "Έξοδος από την Πλήρη Οθόνη" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Enter your Hugging Face token" + "value" : "Exit Full Screen" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Enter your Hugging Face token" + "value" : "Exit Full Screen" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Enter your Hugging Face token" + "value" : "Exit Full Screen" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Introduce tu token de Hugging Face" + "value" : "Salir de pantalla completa" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Introduce tu token de Hugging Face" + "value" : "Salir de pantalla completa" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Anna Hugging Face -tunnuksesi" + "value" : "Poistu koko näytöstä" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Entrez votre jeton Hugging Face" + "value" : "Quitter le mode plein écran" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Entrez votre jeton Hugging Face" + "value" : "Quitter le plein écran" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "הזן את הטוקן שלך ל-Hugging Face" + "value" : "יציאה ממסך מלא" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "अपना Hugging Face टोकन दर्ज करें" + "value" : "पूर्ण स्क्रीन से बाहर निकलें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Unesite svoj Hugging Face token" + "value" : "Izađi iz punog zaslona" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Adja meg a Hugging Face tokenjét" + "value" : "Kilépés a teljes képernyőről" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Masukkan token Hugging Face Anda" + "value" : "Keluar Layar Penuh" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Inserisci il tuo token Hugging Face" + "value" : "Esci da schermo intero" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Faceトークンを入力" + "value" : "フルスクリーンを終了" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 토큰을 입력하세요" + "value" : "전체 화면 종료" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Masukkan token Hugging Face anda" + "value" : "Keluar Skrin Penuh" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Skriv inn Hugging Face-tokenet ditt" + "value" : "Avslutt fullskjerm" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Voer je Hugging Face-token in" + "value" : "Verlaat Volledig Scherm" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Wprowadź swój token Hugging Face" + "value" : "Zakończ Pełny Ekran" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Insira seu token do Hugging Face" + "value" : "Sair da Tela Cheia" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Introduza o seu token do Hugging Face" + "value" : "Sair do Ecrã Completo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Introduceți tokenul dvs. Hugging Face" + "value" : "Ieşire Din Ecran Complet" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Введите ваш токен Hugging Face" + "value" : "Выйти из полноэкранного режима" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Zadajte svoj Hugging Face token" + "value" : "Ukončiť celú obrazovku" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Ange din Hugging Face-token" + "value" : "Avsluta helskärm" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ป้อนโทเค็น Hugging Face ของคุณ" + "value" : "ออกจากเต็มจอ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face tokenınızı girin" + "value" : "Tam Ekrandan Çık" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Введіть ваш токен Hugging Face" + "value" : "Вийти з повноекранного режиму" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Nhập mã thông báo Hugging Face của bạn" + "value" : "Thoát Chế Độ Toàn Màn Hình" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "輸入你的 Hugging Face 令牌" + "value" : "退出全屏" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "输入您的 Hugging Face 令牌" + "value" : "退出全螢幕" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "輸入您的 Hugging Face 令牌" + "value" : "退出全屏幕" } } } }, - "Exit Full Screen" : { + "Experimental features may have performance limitations. Features and programmatic interfaces are subject to change at any time." : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "إنهاء ملء الشاشة" + "value" : "قد تكون الميزات التجريبية محدودة الأداء. الميزات والواجهات البرمجية قابلة للتغيير في أي وقت." } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Sortir de la pantalla completa" + "value" : "Les funcions experimentals poden tenir limitacions de rendiment. Les funcions i interfícies programàtiques poden canviar en qualsevol moment." } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Ukončit režim celé obrazovky" + "value" : "Experimentální funkce mohou mít omezení výkonu. Funkce a programové rozhraní mohou být kdykoli změněny." } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Afslut fuld skærm" + "value" : "Eksperimentelle funktioner kan have ydeevnebegrænsninger. Funktioner og programmeringsgrænseflader kan ændres når som helst." } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Vollbildmodus beenden" + "value" : "Experimentelle Funktionen können Leistungseinschränkungen haben. Funktionen und programmgesteuerte Schnittstellen können jederzeit geändert werden." } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Έξοδος από την Πλήρη Οθόνη" + "value" : "Πειραματικές δυνατότητες μπορεί να έχουν περιορισμούς απόδοσης. Οι δυνατότητες και οι προγραμματικές διεπαφές ενδέχεται να αλλάξουν ανά πάσα στιγμή." } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Exit Full Screen" + "value" : "Experimental features may have performance limitations. Features and programmatic interfaces are subject to change at any time." } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Exit Full Screen" + "value" : "Experimental features may have performance limitations. Features and programmatic interfaces are subject to change at any time" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Exit Full Screen" + "value" : "Experimental features may have performance limitations. Features and programmatic interfaces are subject to change at any time." } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Salir de pantalla completa" + "value" : "Las funciones experimentales pueden tener limitaciones de rendimiento. Las funciones e interfaces programáticas están sujetas a cambios en cualquier momento." } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Salir de pantalla completa" + "value" : "Las funciones experimentales pueden tener limitaciones de rendimiento. Las características e interfaces programáticas están sujetas a cambios en cualquier momento." } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Poistu koko näytöstä" + "value" : "Kokeelliset ominaisuudet voivat sisältää suorituskykyrajoituksia. Ominaisuudet ja ohjelmointirajapinnat voivat muuttua milloin tahansa." } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Quitter le mode plein écran" + "value" : "Les fonctionnalités expérimentales peuvent avoir des limitations de performance. Les fonctionnalités et les interfaces programmatiques peuvent être modifiées à tout moment." } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Quitter le plein écran" + "value" : "Les fonctionnalités expérimentales peuvent avoir des limitations de performance. Les fonctionnalités et les interfaces programmatiques peuvent changer à tout moment." } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "יציאה ממסך מלא" + "value" : "ייתכן שלמאפיינים ניסיוניים יהיו מגבלות ביצועים. מאפיינים ממשקיים ותכנותיים נתונים לשינוי בכל עת." } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "पूर्ण स्क्रीन से बाहर निकलें" + "value" : "प्रयोगात्मक विशेषताएं प्रदर्शन सीमाएँ हो सकती हैं। विशेषताएं और कार्यक्रमात्मक इंटरफेस कभी भी परिवर्तन के अधीन हो सकते हैं।" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Izađi iz punog zaslona" + "value" : "Eksperimentalne značajke mogu imati ograničenja u performansama. Značajke i programska sučelja podložni su promjenama u bilo kojem trenutku." } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Kilépés a teljes képernyőről" + "value" : "Kísérleti funkciók teljesítménybeli korlátozásokkal járhatnak. A funkciók és a programozási felületek bármikor változhatnak." } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Keluar Layar Penuh" + "value" : "Fitur eksperimental mungkin memiliki keterbatasan kinerja. Fitur dan antarmuka program dapat berubah sewaktu-waktu." } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Esci da schermo intero" + "value" : "Le funzionalità sperimentali potrebbero avere limitazioni di prestazioni. Le funzionalità e le interfacce programmatiche sono soggette a modifiche in qualsiasi momento." } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "フルスクリーンを終了" + "value" : "実験的な機能にはパフォーマンスの制限がある場合があります。機能やプログラムインターフェースはいつでも変更される可能性があります。" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "전체 화면 종료" + "value" : "실험적인 기능은 성능 제한이 있을 수 있습니다. 기능 및 프로그래밍 인터페이스는 언제든지 변경될 수 있습니다." } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Keluar Skrin Penuh" + "value" : "Ciri eksperimen mungkin mempunyai had prestasi. Ciri dan antara muka berasaskan program boleh berubah pada bila-bila masa." } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Avslutt fullskjerm" + "value" : "Eksperimentelle funksjoner kan ha ytelsesbegrensninger. Funksjoner og programmeringsgrensesnitt kan endres når som helst." } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Verlaat Volledig Scherm" + "value" : "Experimentele functies kunnen prestatiebeperkingen hebben. Functies en programmatische interfaces kunnen op elk moment worden gewijzigd." } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Zakończ Pełny Ekran" + "value" : "Funkcje eksperymentalne mogą mieć ograniczenia wydajności. Funkcje i interfejsy programistyczne mogą ulec zmianie w dowolnym momencie." } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Sair da Tela Cheia" + "value" : "Recursos experimentais podem ter limitações de desempenho. Recursos e interfaces programáticas estão sujeitos a alterações a qualquer momento." } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Sair do Ecrã Completo" + "value" : "Funcionalidades experimentais podem ter limitações de desempenho. Funcionalidades e interfaces programáticas estão sujeitas a alterações a qualquer momento." } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Ieşire Din Ecran Complet" + "value" : "Funcționalitățile experimentale pot avea limitări de performanță. Funcționalitățile și interfețele programatice pot fi modificate oricând." } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Выйти из полноэкранного режима" + "value" : "Экспериментальные функции могут иметь ограничения производительности. Функции и программные интерфейсы могут изменяться в любое время." } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Ukončiť celú obrazovku" + "value" : "Experimentálne funkcie môžu mať obmedzenia výkonu. Funkcie a programovateľné rozhrania sa môžu kedykoľvek zmeniť." } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Avsluta helskärm" + "value" : "Experimentella funktioner kan ha prestandabegränsningar. Funktioner och programmatiska gränssnitt kan ändras när som helst." } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ออกจากเต็มจอ" + "value" : "คุณลักษณะทดลองอาจมีข้อจำกัดด้านประสิทธิภาพ คุณลักษณะและอินเทอร์เฟซโปรแกรมอาจเปลี่ยนแปลงได้ตลอดเวลา" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Tam Ekrandan Çık" + "value" : "Deneysel özellikler performans sınırlamalarına sahip olabilir. Özellikler ve programatik arabirimler herhangi bir zamanda değiştirilebilir." } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Вийти з повноекранного режиму" + "value" : "Експериментальні функції можуть мати обмеження продуктивності. Функції та програмні інтерфейси можуть змінюватися в будь-який час." } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Thoát Chế Độ Toàn Màn Hình" + "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" : { + "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" : "實驗功能可能有性能限制。功能和編程接口可能會隨時更改。" } } } }, - "Experimental Features" : { + "Feedback" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الميزات التجريبية" + "value" : "التعليقات" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Funcions experimentals" + "value" : "Comentari" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Experimentální funkce" + "value" : "Zpětná vazba" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Eksperimentelle funktioner" + "value" : "Feedback" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Experimentelle Funktionen" + "value" : "Feedback" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Πειραματικές Δυνατότητες" + "value" : "Σχόλια" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Experimental Features" + "value" : "Feedback" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Experimental Features" + "value" : "Feedback" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Experimental Features" + "value" : "Feedback" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Funciones experimentales" + "value" : "Comentarios" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Funciones experimentales" + "value" : "Comentarios" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Kokeelliset ominaisuudet" + "value" : "Palaute" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Fonctionnalités expérimentales" + "value" : "Retour d’information" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Fonctionnalités expérimentales" + "value" : "Commentaires" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "פיצ'רים ניסיוניים" + "value" : "משוב" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "प्रायोगिक फीचर्स" + "value" : "प्रतिक्रिया" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Eksperimentalne značajke" + "value" : "Povratne informacije" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Kísérleti funkciók" + "value" : "Visszajelzés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Fitur Eksperimental" + "value" : "Masukan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Funzionalità Sperimentali" + "value" : "Feedback" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "実験的な機能" + "value" : "フィードバック" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "실험적 기능" + "value" : "피드백" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Ciri Eksperimen" + "value" : "Maklum Balas" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Eksperimentelle funksjoner" + "value" : "Tilbakemelding" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Experimentele functies" + "value" : "Feedback" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Funkcje eksperymentalne" + "value" : "Opinie" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Recursos experimentais" + "value" : "Feedback" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Funcionalidades Experimentais" + "value" : "Feedback" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Funcții Experimentale" + "value" : "Feedback" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Экспериментальные функции" + "value" : "Отзыв" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Experimentálne funkcie" + "value" : "Spätná väzba" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Experimentella funktioner" + "value" : "Feedback" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "คุณสมบัติการทดลอง" + "value" : "คำติชม" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Deneysel Özellikler" + "value" : "Geri Bildirim" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Експериментальні функції" + "value" : "Відгук" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Tính Năng Thử Nghiệm" + "value" : "Phản hồ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" : "意見反饋" } } } }, - "Experimental features may have performance limitations. Features and programmatic interfaces are subject to change at any time." : { + "General" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "قد تكون الميزات التجريبية محدودة الأداء. الميزات والواجهات البرمجية قابلة للتغيير في أي وقت." + "value" : "عام" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Les funcions experimentals poden tenir limitacions de rendiment. Les funcions i interfícies programàtiques poden canviar en qualsevol moment." + "value" : "General" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Experimentální funkce mohou mít omezení výkonu. Funkce a programové rozhraní mohou být kdykoli změněny." + "value" : "Obecné" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Eksperimentelle funktioner kan have ydeevnebegrænsninger. Funktioner og programmeringsgrænseflader kan ændres når som helst." + "value" : "Generelt" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Experimentelle Funktionen können Leistungseinschränkungen haben. Funktionen und programmgesteuerte Schnittstellen können jederzeit geändert werden." + "value" : "Allgemein" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Πειραματικές δυνατότητες μπορεί να έχουν περιορισμούς απόδοσης. Οι δυνατότητες και οι προγραμματικές διεπαφές ενδέχεται να αλλάξουν ανά πάσα στιγμή." + "value" : "Γενικά" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Experimental features may have performance limitations. Features and programmatic interfaces are subject to change at any time." + "value" : "General" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Experimental features may have performance limitations. Features and programmatic interfaces are subject to change at any time" + "value" : "General" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Experimental features may have performance limitations. Features and programmatic interfaces are subject to change at any time." + "value" : "General" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Las funciones experimentales pueden tener limitaciones de rendimiento. Las funciones e interfaces programáticas están sujetas a cambios en cualquier momento." + "value" : "General" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Las funciones experimentales pueden tener limitaciones de rendimiento. Las características e interfaces programáticas están sujetas a cambios en cualquier momento." + "value" : "General" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Kokeelliset ominaisuudet voivat sisältää suorituskykyrajoituksia. Ominaisuudet ja ohjelmointirajapinnat voivat muuttua milloin tahansa." + "value" : "Yleiset" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Les fonctionnalités expérimentales peuvent avoir des limitations de performance. Les fonctionnalités et les interfaces programmatiques peuvent être modifiées à tout moment." + "value" : "Général" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Les fonctionnalités expérimentales peuvent avoir des limitations de performance. Les fonctionnalités et les interfaces programmatiques peuvent changer à tout moment." + "value" : "Général" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "ייתכן שלמאפיינים ניסיוניים יהיו מגבלות ביצועים. מאפיינים ממשקיים ותכנותיים נתונים לשינוי בכל עת." + "value" : "כללי" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "प्रयोगात्मक विशेषताएं प्रदर्शन सीमाएँ हो सकती हैं। विशेषताएं और कार्यक्रमात्मक इंटरफेस कभी भी परिवर्तन के अधीन हो सकते हैं।" + "value" : "सामान्य" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Eksperimentalne značajke mogu imati ograničenja u performansama. Značajke i programska sučelja podložni su promjenama u bilo kojem trenutku." + "value" : "Općenito" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Kísérleti funkciók teljesítménybeli korlátozásokkal járhatnak. A funkciók és a programozási felületek bármikor változhatnak." + "value" : "Általános" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Fitur eksperimental mungkin memiliki keterbatasan kinerja. Fitur dan antarmuka program dapat berubah sewaktu-waktu." + "value" : "Umum" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Le funzionalità sperimentali potrebbero avere limitazioni di prestazioni. Le funzionalità e le interfacce programmatiche sono soggette a modifiche in qualsiasi momento." + "value" : "Generali" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "実験的な機能にはパフォーマンスの制限がある場合があります。機能やプログラムインターフェースはいつでも変更される可能性があります。" + "value" : "一般" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "실험적인 기능은 성능 제한이 있을 수 있습니다. 기능 및 프로그래밍 인터페이스는 언제든지 변경될 수 있습니다." + "value" : "일반" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Ciri eksperimen mungkin mempunyai had prestasi. Ciri dan antara muka berasaskan program boleh berubah pada bila-bila masa." + "value" : "Umum" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Eksperimentelle funksjoner kan ha ytelsesbegrensninger. Funksjoner og programmeringsgrensesnitt kan endres når som helst." + "value" : "Generelt" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Experimentele functies kunnen prestatiebeperkingen hebben. Functies en programmatische interfaces kunnen op elk moment worden gewijzigd." + "value" : "Algemeen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Funkcje eksperymentalne mogą mieć ograniczenia wydajności. Funkcje i interfejsy programistyczne mogą ulec zmianie w dowolnym momencie." + "value" : "Ogólne" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Recursos experimentais podem ter limitações de desempenho. Recursos e interfaces programáticas estão sujeitos a alterações a qualquer momento." + "value" : "Geral" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Funcionalidades experimentais podem ter limitações de desempenho. Funcionalidades e interfaces programáticas estão sujeitas a alterações a qualquer momento." + "value" : "Geral" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Funcționalitățile experimentale pot avea limitări de performanță. Funcționalitățile și interfețele programatice pot fi modificate oricând." + "value" : "General" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Экспериментальные функции могут иметь ограничения производительности. Функции и программные интерфейсы могут изменяться в любое время." + "value" : "Основные настройки" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Experimentálne funkcie môžu mať obmedzenia výkonu. Funkcie a programovateľné rozhrania sa môžu kedykoľvek zmeniť." + "value" : "Všeobecné" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Experimentella funktioner kan ha prestandabegränsningar. Funktioner och programmatiska gränssnitt kan ändras när som helst." + "value" : "Allmänt" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "คุณลักษณะทดลองอาจมีข้อจำกัดด้านประสิทธิภาพ คุณลักษณะและอินเทอร์เฟซโปรแกรมอาจเปลี่ยนแปลงได้ตลอดเวลา" + "value" : "ทั่วไป" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Deneysel özellikler performans sınırlamalarına sahip olabilir. Özellikler ve programatik arabirimler herhangi bir zamanda değiştirilebilir." + "value" : "Genel" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Експериментальні функції можуть мати обмеження продуктивності. Функції та програмні інтерфейси можуть змінюватися в будь-який час." + "value" : "Основні" } }, "vi" : { "stringUnit" : { "state" : "translated", - "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." + "value" : "Chung" } }, - "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" : "一般" } } } }, - "Feedback" : { + "Generate Time" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "التعليقات" + "value" : "توليد الوقت" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Comentari" + "value" : "Genera hora" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Zpětná vazba" + "value" : "Vygenerovat čas" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback" + "value" : "Generer tid" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback" + "value" : "Zeit generieren" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Σχόλια" + "value" : "Δημιουργία Χρόνου" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback" + "value" : "Generate Time" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback" + "value" : "Generate Time" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback" + "value" : "Generate Time" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Comentarios" + "value" : "Generar hora" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Comentarios" + "value" : "Generar hora" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Palaute" + "value" : "Luo aika" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Retour d’information" + "value" : "Générer l'heure" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Commentaires" + "value" : "Générer l'heure" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "משוב" + "value" : "צור זמן" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "प्रतिक्रिया" + "value" : "समय उत्पन्न करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Povratne informacije" + "value" : "Generiraj vrijeme" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Visszajelzés" + "value" : "Idő létrehozása" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Masukan" + "value" : "Hasilkan Waktu" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback" + "value" : "Genera ora" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "フィードバック" + "value" : "時間を生成" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "피드백" + "value" : "시간 생성" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Maklum Balas" + "value" : "Jana Masa" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Tilbakemelding" + "value" : "Generer tid" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback" + "value" : "Tijd genereren" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Opinie" + "value" : "Generuj czas" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback" + "value" : "Gerar Horário" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback" + "value" : "Gerar Tempo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback" + "value" : "Generează timp" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Отзыв" + "value" : "Сгенерировать время" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Spätná väzba" + "value" : "Generovať čas" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Feedback" + "value" : "Generera tid" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "คำติชม" + "value" : "สร้างเวลา" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Geri Bildirim" + "value" : "Süre Oluştur" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Відгук" + "value" : "Згенерувати час" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Phản hồi" + "value" : "Tạo thời gian" } }, - "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" : "生成時間" } } } }, - "GPU Cache Limit" : { + "Generate Tokens/second" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "حد ذاكرة التخزين المؤقت لوحدة معالجة الرسوميات" + "value" : "توليد الرموز/الثانية" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Límit de memòria cau de la GPU" + "value" : "Genera tokens/segon" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Limit pro mezipaměť GPU" + "value" : "Generovat tokeny/sekundu" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Begrænsning for GPU-cache" + "value" : "Generer Tokens/sekund" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "GPU-Cache-Grenze" + "value" : "Token pro Sekunde generieren" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Όριο προσωρινής μνήμης GPU" + "value" : "Δημιουργία διακριτικών/δευτερόλεπτο" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "GPU Cache Limit" + "value" : "Generate Tokens/second" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "GPU Cache Limit" + "value" : "Generate Tokens/second" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "GPU Cache Limit" + "value" : "Generate Tokens per second" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Límite de caché de GPU" + "value" : "Generar tokens/segundo" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Límite de Caché de GPU" + "value" : "Generar tokens/segundo" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "GPU-välimuistin raja" + "value" : "Luo merkkejä/sekunti" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Limite de cache GPU" + "value" : "Générer des jetons/seconde" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Limite de cache GPU" + "value" : "Générer des jetons/seconde" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "מגבלת המטמון של ה-GPU" + "value" : "יצירת סמלים/שנייה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "GPU कैश सीमा" + "value" : "प्रती/सेकंड टोकन जनरेट करें" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Ograničenje GPU predmemorije" + "value" : "Generiraj toka/sekundi" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "GPU gyorsítótár korlátja" + "value" : "Tokenek létrehozása/másodperc" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Batas Cache GPU" + "value" : "Hasilkan Token/detik" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Limite cache GPU" + "value" : "Genera token/secondo" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "GPUキャッシュ制限" + "value" : "トークン/秒を生成" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "GPU 캐시 제한" + "value" : "초당 토큰 생성" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Had Cache GPU" + "value" : "Jana Token/saat" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "GPU-hurtigbuffergrense" + "value" : "Generer token/sekund" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "GPU-cachelimiet" + "value" : "Genereer Tokens/seconde" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Limit bufora GPU" + "value" : "Generuj Tokeny/sekundę" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Limite de Cache da GPU" + "value" : "Gerar Tokens/segundo" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Limite da Cache da GPU" + "value" : "Gerar Tokens/segundo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Limită cache GPU" + "value" : "Generează jetoane/secundă" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Ограничение кэша GPU" + "value" : "Создать токены/секунда" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Limit vyrovnávacej pamäte GPU" + "value" : "Generovať tokeny/sekundu" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Gräns för GPU-cache" + "value" : "Generera tokens/sekund" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ขีดจำกัดแคช GPU" + "value" : "สร้างโทเคน/วินาที" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "GPU Önbellek Limiti" + "value" : "Saniye Başı Jeton Üret" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Граничення кешу GPU" + "value" : "Генерувати токени/секунда" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Giới hạn bộ nhớ đệm GPU" + "value" : "Tạo mã mỗi giây" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "GPU 緩存限制" + "value" : "生成令牌/秒" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "GPU缓存限制" + "value" : "每秒生成 Token" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "GPU 快取上限" + "value" : "每秒產生代幣" } } } }, - "General" : { - "extractionState" : "manual", + "GitHub" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "عام" + "value" : "GitHub" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "GitHub" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Obecné" + "value" : "GitHub" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Generelt" + "value" : "GitHub" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Allgemein" + "value" : "GitHub" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Γενικά" + "value" : "GitHub" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "GitHub" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "GitHub" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "GitHub" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "GitHub" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "GitHub" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Yleiset" + "value" : "GitHub" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Général" + "value" : "GitHub" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Général" + "value" : "GitHub" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "כללי" + "value" : "GitHub" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "सामान्य" + "value" : "GitHub" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Općenito" + "value" : "GitHub" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Általános" + "value" : "GitHub" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Umum" + "value" : "GitHub" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Generali" + "value" : "GitHub" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "一般" + "value" : "GitHub" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "일반" + "value" : "GitHub" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Umum" + "value" : "GitHub" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Generelt" + "value" : "GitHub" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Algemeen" + "value" : "GitHub" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Ogólne" + "value" : "GitHub" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Geral" + "value" : "GitHub" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Geral" + "value" : "GitHub" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "General" + "value" : "GitHub" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Основные настройки" + "value" : "GitHub" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Všeobecné" + "value" : "GitHub" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Allmänt" + "value" : "GitHub" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ทั่วไป" + "value" : "GitHub" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Genel" + "value" : "GitHub" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Основні" + "value" : "GitHub" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Chung" + "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" : "概述" + "value" : "GitHub" } } } }, - "Generate Time" : { + "GPU Cache Limit" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "توليد الوقت" + "value" : "حد ذاكرة التخزين المؤقت لوحدة معالجة الرسوميات" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Genera hora" + "value" : "Límit de memòria cau de la GPU" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Vygenerovat čas" + "value" : "Limit pro mezipaměť GPU" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Generer tid" + "value" : "Begrænsning for GPU-cache" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Zeit generieren" + "value" : "GPU-Cache-Grenze" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Δημιουργία Χρόνου" + "value" : "Όριο προσωρινής μνήμης GPU" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Generate Time" + "value" : "GPU Cache Limit" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Generate Time" + "value" : "GPU Cache Limit" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Generate Time" + "value" : "GPU Cache Limit" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Generar hora" + "value" : "Límite de caché de GPU" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Generar hora" + "value" : "Límite de Caché de GPU" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Luo aika" + "value" : "GPU-välimuistin raja" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Générer l'heure" + "value" : "Limite de cache GPU" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Générer l'heure" + "value" : "Limite de cache GPU" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "צור זמן" + "value" : "מגבלת המטמון של ה-GPU" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "समय उत्पन्न करें" + "value" : "GPU कैश सीमा" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Generiraj vrijeme" + "value" : "Ograničenje GPU predmemorije" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Idő létrehozása" + "value" : "GPU gyorsítótár korlátja" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Hasilkan Waktu" + "value" : "Batas Cache GPU" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Genera ora" + "value" : "Limite cache GPU" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "時間を生成" + "value" : "GPUキャッシュ制限" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "시간 생성" + "value" : "GPU 캐시 제한" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Jana Masa" + "value" : "Had Cache GPU" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Generer tid" + "value" : "GPU-hurtigbuffergrense" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Tijd genereren" + "value" : "GPU-cachelimiet" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Generuj czas" + "value" : "Limit bufora GPU" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Gerar Horário" + "value" : "Limite de Cache da GPU" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Gerar Tempo" + "value" : "Limite da Cache da GPU" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Generează timp" + "value" : "Limită cache GPU" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Сгенерировать время" + "value" : "Ограничение кэша GPU" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Generovať čas" + "value" : "Limit vyrovnávacej pamäte GPU" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Generera tid" + "value" : "Gräns för GPU-cache" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "สร้างเวลา" + "value" : "ขีดจำกัดแคช GPU" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Süre Oluştur" + "value" : "GPU Önbellek Limiti" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Згенерувати час" + "value" : "Граничення кешу GPU" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Tạo thời gian" + "value" : "Giới hạn bộ nhớ đệm GPU" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "生成時間" + "value" : "GPU缓存限制" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "生成时间" + "value" : "GPU 快取上限" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "生成時間" + "value" : "GPU 緩存限制" } } } }, - "Generate Tokens/second" : { + "GPU Memory Limit" : { + + }, + "https://hf-mirror.com" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "توليد الرموز/الثانية" + "value" : "https://hf-mirror.com" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Genera tokens/segon" + "value" : "https://hf-mirror.com" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Generovat tokeny/sekundu" + "value" : "https://hf-mirror.com" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Generer Tokens/sekund" + "value" : "https://hf-mirror.com" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Token pro Sekunde generieren" + "value" : "https://hf-mirror.com" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Δημιουργία διακριτικών/δευτερόλεπτο" + "value" : "https://hf-mirror.com" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Generate Tokens/second" + "value" : "https://hf-mirror.com" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Generate Tokens/second" + "value" : "https://hf-mirror.com" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Generate Tokens per second" + "value" : "https://hf-mirror.com" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Generar tokens/segundo" + "value" : "https://hf-mirror.com" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Generar tokens/segundo" + "value" : "https://hf-mirror.com" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Luo merkkejä/sekunti" + "value" : "https://hf-mirror.com" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Générer des jetons/seconde" + "value" : "https://hf-mirror.com" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Générer des jetons/seconde" + "value" : "https://hf-mirror.com" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "יצירת סמלים/שנייה" + "value" : "https://hf-mirror.com" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "प्रती/सेकंड टोकन जनरेट करें" + "value" : "https://hf-mirror.com" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Generiraj toka/sekundi" + "value" : "https://hf-mirror.com" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Tokenek létrehozása/másodperc" + "value" : "https://hf-mirror.com" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Hasilkan Token/detik" + "value" : "https://hf-mirror.com" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Genera token/secondo" + "value" : "https://hf-mirror.com" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "トークン/秒を生成" + "value" : "https://hf-mirror.com" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "초당 토큰 생성" + "value" : "https://hf-mirror.com" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Jana Token/saat" + "value" : "https://hf-mirror.com" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Generer token/sekund" + "value" : "https://hf-mirror.com" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Genereer Tokens/seconde" + "value" : "https://hf-mirror.com" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Generuj Tokeny/sekundę" + "value" : "https://hf-mirror.com" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Gerar Tokens/segundo" + "value" : "https://hf-mirror.com" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Gerar Tokens/segundo" + "value" : "https://hf-mirror.com" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Generează jetoane/secundă" + "value" : "https://hf-mirror.com" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Создать токены/секунда" + "value" : "https://hf-mirror.com" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Generovať tokeny/sekundu" + "value" : "https://hf-mirror.com" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Generera tokens/sekund" + "value" : "https://hf-mirror.com" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "สร้างโทเคน/วินาที" + "value" : "https://hf-mirror.com" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Saniye Başı Jeton Üret" + "value" : "https://hf-mirror.com" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Генерувати токени/секунда" + "value" : "https://hf-mirror.com" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Tạo mã mỗi giây" + "value" : "https://hf-mirror.com" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "每秒產生代幣" + "value" : "https://hf-mirror.com" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "生成令牌/秒" + "value" : "https://hf-mirror.com" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "每秒生成 Token" + "value" : "https://hf-mirror.com" } } } }, - "GitHub" : { + "https://huggingface.co" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "GitHub" + "value" : "https://huggingface.co" } } } }, - "Hugging Face" : { + "Hugging Face Endpoint" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "نقطة النهاية Hugging Face" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Punt final de Hugging Face" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face koncový bod" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face-endepunkt" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face-Endpunkt" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Endpoint Hugging Face" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face Endpoint" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face Endpoint" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face Endpoint" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Punto Final de Hugging Face" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Punto de conexión de Hugging Face" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face -päätepiste" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Point de terminaison Hugging Face" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "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" + "value" : "Hugging Face završna točka" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face végpont" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face Endpoint" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Endpoint di Hugging Face" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face エンドポイント" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "허깅 페이스 엔드포인트" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Titik Akhir Hugging Face" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face-endepunkt" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face-eindpunt" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face Endpoint" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Endpoint do Hugging Face" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Ponto Final Hugging Face" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Endpoint Hugging Face" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Конечная точка Hugging Face" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face endpoint" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face-slutpunkt" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face Endpoint" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Hugging Face Uç Noktası" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Кінцева точка Hugging Face" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "value" : "Điểm cuối Hugging Face" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face" + "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" + "value" : "Hugging Face 端點" } } } }, - "Hugging Face Endpoint" : { + "Hugging Face Repo Id" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "نقطة النهاية Hugging Face" + "value" : "معرّف مستودع Hugging Face" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Punt final de Hugging Face" + "value" : "Id de Repositori de Hugging Face" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face koncový bod" + "value" : "ID úložiště Hugging Face" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-endepunkt" + "value" : "Hugging Face Repo-id" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-Endpunkt" + "value" : "Hugging Face Repo-ID" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint Hugging Face" + "value" : "Αναγνωριστικό Αποθετηρίου Hugging Face" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Endpoint" + "value" : "Hugging Face Repo ID" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Endpoint" + "value" : "Hugging Face Repo ID" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Endpoint" + "value" : "Hugging Face Repo Id" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Punto Final de Hugging Face" + "value" : "ID del repositorio de Hugging Face" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Punto de conexión de Hugging Face" + "value" : "ID del repo de Hugging Face" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face -päätepiste" + "value" : "Hugging Face -repojen tunnus" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Point de terminaison Hugging Face" + "value" : "ID du dépôt Hugging Face" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Point de terminaison Hugging Face" + "value" : "ID du dépôt 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 završna točka" + "value" : "ID spremišta Hugging Face" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face végpont" + "value" : "Hugging Face tárolóazonosító" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Endpoint" + "value" : "ID Repo Hugging Face" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint di Hugging Face" + "value" : "ID Repository Hugging Face" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face エンドポイント" + "value" : "Hugging Face リポジトリID" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "허깅 페이스 엔드포인트" + "value" : "Hugging Face 저장소 ID" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Titik Akhir Hugging Face" + "value" : "ID Repo Hugging Face" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-endepunkt" + "value" : "Hugging Face Repo-ID" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-eindpunt" + "value" : "Hugging Face-repo-ID" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Endpoint" + "value" : "Identyfikator repozytorium Hugging Face" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint do Hugging Face" + "value" : "ID do Repositório Hugging Face" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Ponto Final Hugging Face" + "value" : "ID do Repositório Hugging Face" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Endpoint Hugging Face" + "value" : "ID-ul depozitului Hugging Face" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Конечная точка Hugging Face" + "value" : "Идентификатор репозитория Hugging Face" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face endpoint" + "value" : "Hugging Face Repo ID" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-slutpunkt" + "value" : "Hugging Face Repo-ID" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Endpoint" + "value" : "Hugging Face Repo Id" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Uç Noktası" + "value" : "Hugging Face Repo Kimliği" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Кінцева точка Hugging Face" + "value" : "Ідентифікатор репозиторію Hugging Face" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Điểm cuối Hugging Face" + "value" : "Hugging Face Repo Id" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 端點" + "value" : "Hugging Face 仓库 ID" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 节点" + "value" : "Hugging Face 儲存庫 ID" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 端點" + "value" : "Hugging Face Repo ID" } } } }, - "Hugging Face Repo Id" : { - "extractionState" : "manual", + "Hugging Face Token" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "معرّف مستودع Hugging Face" + "value" : "كود Hugging Face" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Id de Repositori de Hugging Face" + "value" : "Token de Hugging Face" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "ID úložiště Hugging Face" + "value" : "Token Hugging Face" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo-id" + "value" : "Hugging Face-token" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo-ID" + "value" : "Hugging-Face-Token" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Αναγνωριστικό Αποθετηρίου Hugging Face" + "value" : "Διακριτικό Hugging Face" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo ID" + "value" : "Hugging Face Token" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo ID" + "value" : "Hugging Face Token" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo Id" + "value" : "Hugging Face Token" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "ID del repositorio de Hugging Face" + "value" : "Token de Hugging Face" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "ID del repo de Hugging Face" + "value" : "Token de Hugging Face" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face -repojen tunnus" + "value" : "Hugging Face -avain" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "ID du dépôt Hugging Face" + "value" : "Jeton Hugging Face" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "ID du dépôt Hugging Face" + "value" : "Jeton 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 token" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face tárolóazonosító" + "value" : "Hugging Face token" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "ID Repo Hugging Face" + "value" : "Hugging Face Token" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "ID Repository Hugging Face" + "value" : "Token di Hugging Face" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face リポジトリID" + "value" : "Hugging Face トークン" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 저장소 ID" + "value" : "허깅 페이스 토큰" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "ID Repo Hugging Face" + "value" : "Token Hugging Face" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo-ID" + "value" : "Hugging Face-token" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-repo-ID" + "value" : "Hugging Face-token" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Identyfikator repozytorium Hugging Face" + "value" : "Token Hugging Face" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "ID do Repositório Hugging Face" + "value" : "Token do Hugging Face" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "ID do Repositório Hugging Face" + "value" : "Token do Hugging Face" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "ID-ul depozitului Hugging Face" + "value" : "Token Hugging Face" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Идентификатор репозитория Hugging Face" + "value" : "Токен Hugging Face" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo ID" + "value" : "Hugging Face Token" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo-ID" + "value" : "Hugging Face-token" } }, "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 Token" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Ідентифікатор репозиторію Hugging Face" + "value" : "Токен Hugging Face" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Repo Id" + "value" : "Mã 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 Token" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 儲存庫 ID" + "value" : "Hugging Face Token" } } } }, - "Hugging Face Token" : { + "Image Text to Text (Vision)" : { + + }, + "Import" : { + + }, + "Language" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "كود Hugging Face" + "value" : "اللغة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Token de Hugging Face" + "value" : "Idioma" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Token Hugging Face" + "value" : "Jazyk" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-token" + "value" : "Sprog" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging-Face-Token" + "value" : "Sprache" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Διακριτικό Hugging Face" + "value" : "Γλώσσα" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Language" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Language" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Language" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Token de Hugging Face" + "value" : "Idioma" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Token de Hugging Face" + "value" : "Idioma" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face -avain" + "value" : "Kieli" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Jeton Hugging Face" + "value" : "Langue" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Jeton Hugging Face" + "value" : "Langue" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אסימון Hugging Face" + "value" : "שפה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face टोकन" + "value" : "भाषा" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face token" + "value" : "Jezik" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face token" + "value" : "Nyelv" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Bahasa" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Token di Hugging Face" + "value" : "Lingua" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face トークン" + "value" : "言語" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "허깅 페이스 토큰" + "value" : "언어" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Token Hugging Face" + "value" : "Bahasa" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-token" + "value" : "Språk" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-token" + "value" : "Taal" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Token Hugging Face" + "value" : "Język" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Token do Hugging Face" + "value" : "Idioma" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Token do Hugging Face" + "value" : "Idioma" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Token Hugging Face" + "value" : "Limbă" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Токен Hugging Face" + "value" : "Язык" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Jazyk" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face-token" + "value" : "Språk" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "โทเค็น Hugging Face" + "value" : "ภาษา" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "Dil" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Токен Hugging Face" + "value" : "Мова" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Mã Hugging Face" + "value" : "Ngôn ngữ" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "语言" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face 令牌" + "value" : "語言" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "Hugging Face Token" + "value" : "語言" } } } }, - "Language" : { + "Max Length" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "اللغة" + "value" : "الحد الأقصى لطول" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Idioma" + "value" : "Longitud màxima" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Jazyk" + "value" : "Maximální délka" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Sprog" + "value" : "Maks. længde" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Sprache" + "value" : "Maximale Länge" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Γλώσσα" + "value" : "Μέγιστο μήκος" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Language" + "value" : "Max Length" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Language" + "value" : "Max Length" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Language" + "value" : "Max Length" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Idioma" + "value" : "Longitud máxima" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Idioma" + "value" : "Longitud Máxima" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Kieli" + "value" : "Enimmäispituus" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Langue" + "value" : "Longueur maximale" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Langue" + "value" : "Longueur maximale" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "שפה" + "value" : "אורך מרבי" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "भाषा" + "value" : "अधिकतम लंबाई" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Jezik" + "value" : "Maksimalna duljina" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Nyelv" + "value" : "Maximális hosszasság" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Bahasa" + "value" : "Panjang Maksimum" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Lingua" + "value" : "Lunghezza massima" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "言語" + "value" : "最大長" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "언어" + "value" : "최대 길이" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Bahasa" + "value" : "Panjang Maksimum" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Språk" + "value" : "Maks lengde" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Taal" + "value" : "Maximale lengte" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Język" + "value" : "Maksymalna długość" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Idioma" + "value" : "Comprimento Máximo" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Idioma" + "value" : "Comprimento Máximo" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Limbă" + "value" : "Lungime maximă" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Язык" + "value" : "Макс. длина" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Jazyk" + "value" : "Maximálna dĺžka" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Språk" + "value" : "Maxlängd" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ภาษา" + "value" : "ความยาวสูงสุด" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Dil" + "value" : "Maksimum Uzunluk" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Мова" + "value" : "Максимальна довжина" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Ngôn ngữ" + "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" : "最大長度" } } } }, - "MLX Community" : { - "extractionState" : "manual", + "Max Messages Limit" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "مجتمع MLX" + "value" : "الحد الأقصى للرسائل" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Comunitat MLX" + "value" : "Límit màxim de missatges" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Komunita MLX" + "value" : "Maximální limit zpráv" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "MLX-fællesskab" + "value" : "Maksimumgrænse for beskeder" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Community" + "value" : "Maximale Nachrichtenanzahl" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Κοινότητα MLX" + "value" : "Μέγιστο Όριο Μηνυμάτων" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Community" + "value" : "Maximum Messages Limit" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Community" + "value" : "Maximum Messages Limit" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Community" + "value" : "Max Messages Limit" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Comunidad MLX" + "value" : "Límite Máximo de Mensajes" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Comunidad MLX" + "value" : "Límite Máximo de Mensajes" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "MLX-yhteisö" + "value" : "Viestiin enimmäisraja" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Communauté MLX" + "value" : "Limite maximale de messages" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Communauté MLX" + "value" : "Limite maximale de messages" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "קהילת MLX" + "value" : "מגבלת הודעות מרבית" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "MLX समुदाय" + "value" : "अधिकतम संदेश सीमा" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Zajednica MLX" + "value" : "Maksimalni broj poruka" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Közösség" + "value" : "Üzenetek maximális száma" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Komunitas MLX" + "value" : "Batas Pesan Maksimal" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Community MLX" + "value" : "Limite massimo messaggi" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "MLXコミュニティ" + "value" : "メッセージ上限" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "MLX 커뮤니티" + "value" : "최대 메시지 제한" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Komuniti MLX" + "value" : "Had Maksimum Mesej" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "MLX-fellesskap" + "value" : "Maks grense for meldinger" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "MLX-community" + "value" : "Maximaal aantal berichten" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Społeczność MLX" + "value" : "Limit maksymalnej liczby wiadomości" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Comunidade MLX" + "value" : "Limite Máximo de Mensagens" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Comunidade" + "value" : "Limite Máximo de Mensagens" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Comunitatea MLX" + "value" : "Limită maximă mesaje" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Сообщество" + "value" : "Максимальный лимит сообщений" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Komunita MLX" + "value" : "Maximálny limit správ" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "MLX-community" + "value" : "Maximalt antal meddelanden" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ชุมชน MLX" + "value" : "จำกัดจำนวนข้อความสูงสุด" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "MLX Topluluğu" + "value" : "Maksimum Mesaj Sınırı" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Спільнота MLX" + "value" : "Максимальна кількість повідомлень" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Cộng đồng MLX" + "value" : "Giới hạn tin nhắn tối đa" } }, - "zh-HK" : { + "zh-Hans" : { "stringUnit" : { "state" : "translated", - "value" : "MLX 社群" + "value" : "最大消息限制" } }, - "zh-Hans" : { + "zh-Hant" : { "stringUnit" : { "state" : "translated", - "value" : "MLX 社区" + "value" : "訊息數量上限" } }, - "zh-Hant" : { + "zh-HK" : { "stringUnit" : { "state" : "translated", - "value" : "MLX 社群" + "value" : "訊息數量上限" } } } }, - "Max Length" : { + "Message Control" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الحد الأقصى لطول" + "value" : "التحكم في الرسائل" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Longitud màxima" + "value" : "Control de missatges" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Maximální délka" + "value" : "Řízení zpráv" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Maks. længde" + "value" : "Beskedkontrol" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Maximale Länge" + "value" : "Nachrichtensteuerung" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Μέγιστο μήκος" + "value" : "Έλεγχος Μηνυμάτων" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Max Length" + "value" : "Message Control" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Max Length" + "value" : "Message Control" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Max Length" + "value" : "Message Control" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Longitud máxima" + "value" : "Control de mensajes" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Longitud Máxima" + "value" : "Control de Mensajes" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Enimmäispituus" + "value" : "Viestien hallinta" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Longueur maximale" + "value" : "Contrôle des messages" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Longueur maximale" + "value" : "Contrôle des messages" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אורך מרבי" + "value" : "בקרת הודעות" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "अधिकतम लंबाई" + "value" : "संदेश नियंत्रण" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Maksimalna duljina" + "value" : "Kontrola poruka" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Maximális hosszasság" + "value" : "Üzenetvezérlés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Panjang Maksimum" + "value" : "Kontrol Pesan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Lunghezza massima" + "value" : "Controllo messaggi" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "最大長" + "value" : "メッセージコントロール" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "최대 길이" + "value" : "메시지 제어" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Panjang Maksimum" + "value" : "Kawalan Mesej" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Maks lengde" + "value" : "Meldingskontroll" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Maximale lengte" + "value" : "Berichtbeheer" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Maksymalna długość" + "value" : "Sterowanie wiadomością" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Comprimento Máximo" + "value" : "Controle de Mensagens" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Comprimento Máximo" + "value" : "Controlo de Mensagens" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Lungime maximă" + "value" : "Control Mesaje" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Макс. длина" + "value" : "Управление сообщениями" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Maximálna dĺžka" + "value" : "Ovládanie správ" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Maxlängd" + "value" : "Meddelandekontroll" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "ความยาวสูงสุด" + "value" : "การควบคุมข้อความ" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Maksimum Uzunluk" + "value" : "Mesaj Kontrolü" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Максимальна довжина" + "value" : "Керування повідомленнями" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Chiều dài tối đa" + "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" : "訊息控制" } } } }, - "Max Messages Limit" : { + "ML" : { + + }, + "MLX Community" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "الحد الأقصى للرسائل" + "value" : "مجتمع MLX" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Límit màxim de missatges" + "value" : "Comunitat MLX" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Maximální limit zpráv" + "value" : "Komunita MLX" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Maksimumgrænse for beskeder" + "value" : "MLX-fællesskab" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Maximale Nachrichtenanzahl" + "value" : "MLX Community" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Μέγιστο Όριο Μηνυμάτων" + "value" : "Κοινότητα MLX" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Maximum Messages Limit" + "value" : "MLX Community" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Maximum Messages Limit" + "value" : "MLX Community" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Max Messages Limit" + "value" : "MLX Community" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Límite Máximo de Mensajes" + "value" : "Comunidad MLX" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Límite Máximo de Mensajes" + "value" : "Comunidad MLX" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Viestiin enimmäisraja" + "value" : "MLX-yhteisö" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Limite maximale de messages" + "value" : "Communauté MLX" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Limite maximale de messages" + "value" : "Communauté MLX" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "מגבלת הודעות מרבית" + "value" : "קהילת MLX" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "अधिकतम संदेश सीमा" + "value" : "MLX समुदाय" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Maksimalni broj poruka" + "value" : "Zajednica MLX" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Üzenetek maximális száma" + "value" : "MLX Közösség" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Batas Pesan Maksimal" + "value" : "Komunitas MLX" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Limite massimo messaggi" + "value" : "Community MLX" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "メッセージ上限" + "value" : "MLXコミュニティ" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "최대 메시지 제한" + "value" : "MLX 커뮤니티" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Had Maksimum Mesej" + "value" : "Komuniti MLX" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Maks grense for meldinger" + "value" : "MLX-fellesskap" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Maximaal aantal berichten" + "value" : "MLX-community" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Limit maksymalnej liczby wiadomości" + "value" : "Społeczność MLX" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Limite Máximo de Mensagens" + "value" : "Comunidade MLX" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Limite Máximo de Mensagens" + "value" : "MLX Comunidade" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Limită maximă mesaje" + "value" : "Comunitatea MLX" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Максимальный лимит сообщений" + "value" : "MLX Сообщество" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Maximálny limit správ" + "value" : "Komunita MLX" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Maximalt antal meddelanden" + "value" : "MLX-community" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "จำกัดจำนวนข้อความสูงสุด" + "value" : "ชุมชน MLX" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Maksimum Mesaj Sınırı" + "value" : "MLX Topluluğu" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Максимальна кількість повідомлень" + "value" : "Спільнота MLX" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Giới hạn tin nhắn tối đa" + "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 社群" } } } }, - "Message Control" : { + "mlx-community/OpenELM-3B" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "التحكم في الرسائل" + "value" : "mlx-community/OpenELM-3B" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Control de missatges" + "value" : "mlx-community/OpenELM-3B" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Řízení zpráv" + "value" : "mlx-community/OpenELM-3B" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Beskedkontrol" + "value" : "mlx-community/OpenELM-3B" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Nachrichtensteuerung" + "value" : "mlx-community/OpenELM-3B" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Έλεγχος Μηνυμάτων" + "value" : "mlx-community/OpenELM-3B" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Message Control" + "value" : "mlx-community/OpenELM-3B" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Message Control" + "value" : "mlx-community/OpenELM-3B" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Message Control" + "value" : "mlx-community/OpenELM-3B" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Control de mensajes" + "value" : "mlx-community/OpenELM-3B" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Control de Mensajes" + "value" : "mlx-community/OpenELM-3B" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Viestien hallinta" + "value" : "mlx-community/OpenELM-3B" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Contrôle des messages" + "value" : "mlx-community/OpenELM-3B" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Contrôle des messages" + "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" : "Kontrola poruka" + "value" : "mlx-community/OpenELM-3B" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Üzenetvezérlés" + "value" : "mlx-community/OpenELM-3B" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Kontrol Pesan" + "value" : "mlx-community/OpenELM-3B" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Controllo messaggi" + "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" : "Kawalan Mesej" + "value" : "mlx-community/OpenELM-3B" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Meldingskontroll" + "value" : "mlx-community/OpenELM-3B" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Berichtbeheer" + "value" : "mlx-community/OpenELM-3B" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Sterowanie wiadomością" + "value" : "mlx-community/OpenELM-3B" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Controle de Mensagens" + "value" : "mlx-community/OpenELM-3B" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Controlo de Mensagens" + "value" : "mlx-community/OpenELM-3B" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Control Mesaje" + "value" : "mlx-community/OpenELM-3B" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Управление сообщениями" + "value" : "mlx-community/OpenELM-3B" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Ovládanie správ" + "value" : "mlx-community/OpenELM-3B" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Meddelandekontroll" + "value" : "mlx-community/OpenELM-3B" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "การควบคุมข้อความ" + "value" : "mlx-community/OpenELM-3B" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Mesaj Kontrolü" + "value" : "mlx-community/OpenELM-3B" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Керування повідомленнями" + "value" : "mlx-community/OpenELM-3B" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Kiểm soát Tin nhắn" + "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" } } } @@ -12372,25 +12164,28 @@ "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" : "模型" } } } + }, + "Model List" : { + }, "Model Settings" : { "localizations" : { @@ -12610,19 +12405,19 @@ "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" : "模型設定" @@ -12630,520 +12425,520 @@ } } }, - "Model State" : { + "Models" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "حالة النموذج" + "value" : "النماذج" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Estat del model" + "value" : "Models" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Stav modelu" + "value" : "Modely" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Modeltilstand" + "value" : "Modeller" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Modellstatus" + "value" : "Modelle" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Κατάσταση Μοντέλου" + "value" : "Μοντέλα" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Model State" + "value" : "Models" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Model Status" + "value" : "Models" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Model State" + "value" : "Models" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Estado del Modelo" + "value" : "Modelos" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Estado del modelo" + "value" : "Modelos" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Mallin tila" + "value" : "Mallit" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "État du modèle" + "value" : "Modèles" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "État du modèle" + "value" : "Modèles" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "מצב דגם" + "value" : "דגמים" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "मॉडल स्थिति" + "value" : "मॉडल्स" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Stanje modela" + "value" : "Modeli" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Modellállapot" + "value" : "Modellek" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Keadaan Model" + "value" : "Model" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Stato modello" + "value" : "Modelli" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "モデルの状態" + "value" : "モデル" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "모델 상태" + "value" : "모델" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Keadaan Model" + "value" : "Model" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Modelltilstand" + "value" : "Modeller" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Modelstatus" + "value" : "Modellen" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Stan modelu" + "value" : "Modele" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Estado do Modelo" + "value" : "Modelos" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Estado do Modelo" + "value" : "Modelos" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Model de stare" + "value" : "Modele" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Состояние модели" + "value" : "Модели" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Stav modelu" + "value" : "Modely" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Modelläge" + "value" : "Modeller" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "สถานะโมเดล" + "value" : "โมเดล" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Model Durumu" + "value" : "Modeller" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Стан моделі" + "value" : "Моделі" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Trạng thái mô hình" + "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" : "模型" } } } }, - "Models" : { + "New Chat" : { "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "النماذج" + "value" : "دردشة جديدة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Models" + "value" : "Xat nou" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Modely" + "value" : "Nový chat" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Modeller" + "value" : "Ny chat" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Modelle" + "value" : "Neuer Chat" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Μοντέλα" + "value" : "Νέα συνομιλία" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "Models" + "value" : "New Chat" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "Models" + "value" : "New Chat" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "Models" + "value" : "New Chat" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Modelos" + "value" : "Nuevo Chat" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Modelos" + "value" : "Nuevo chat" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Mallit" + "value" : "Uusi keskustelu" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Modèles" + "value" : "Nouvelle discussion" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Modèles" + "value" : "Nouvelle Discussion" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "דגמים" + "value" : "צ'אט חדש" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "मॉडल्स" + "value" : "नयी चैट" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Modeli" + "value" : "Nova poruka" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Modellek" + "value" : "Új csevegés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Obrolan Baru" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Modelli" + "value" : "Nuova chat" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "モデル" + "value" : "新規チャット" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "모델" + "value" : "새로운 채팅" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Model" + "value" : "Sembang Baru" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Modeller" + "value" : "Ny chat" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Modellen" + "value" : "Nieuwe chat" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Modele" + "value" : "Nowy czat" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Modelos" + "value" : "Nova Conversa" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Modelos" + "value" : "Nova Conversa" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Modele" + "value" : "Chat nou" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Модели" + "value" : "Новый чат" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Modely" + "value" : "Nový chat" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Modeller" + "value" : "Ny Chatt" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "โมเดล" + "value" : "เริ่มแชทใหม่" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Modeller" + "value" : "Yeni Sohbet" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Моделі" + "value" : "Нова розмова" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Mô hình" + "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" : "新聊天" } } } }, - "New Chat" : { - "extractionState" : "manual", + "New Conversation" : { "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "دردشة جديدة" + "value" : "محادثة جديدة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Xat nou" + "value" : "Nova conversa" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Nový chat" + "value" : "Nová konverzace" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Ny chat" + "value" : "Ny samtale" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neuer Chat" + "value" : "Neue Konversation" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Νέα συνομιλία" + "value" : "Νέα Συνομιλία" } }, "en-AU" : { @@ -13155,7 +12950,7 @@ "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "New Chat" + "value" : "New Conversation" } }, "en-IN" : { @@ -13167,13 +12962,13 @@ "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nuevo Chat" + "value" : "Nueva conversación" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Nuevo chat" + "value" : "Nueva conversación" } }, "fi" : { @@ -13185,85 +12980,85 @@ "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle discussion" + "value" : "Nouvelle conversation" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle Discussion" + "value" : "Nouvelle conversation" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "צ'אט חדש" + "value" : "שיחה חדשה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "नयी चैट" + "value" : "नई बातचीत" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Nova poruka" + "value" : "Novi razgovor" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Új csevegés" + "value" : "Új beszélgetés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Obrolan Baru" + "value" : "Percakapan Baru" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuova chat" + "value" : "Nuova conversazione" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "新規チャット" + "value" : "新規メッセージ" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "새로운 채팅" + "value" : "새 대화" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Sembang Baru" + "value" : "Perbualan Baru" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ny chat" + "value" : "Ny samtale" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuwe chat" + "value" : "Nieuw gesprek" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nowy czat" + "value" : "Nowa rozmowa" } }, "pt-BR" : { @@ -13281,7 +13076,7 @@ "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Chat nou" + "value" : "Conversație Nouă" } }, "ru" : { @@ -13293,25 +13088,25 @@ "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Nový chat" + "value" : "Nový rozhovor" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Ny Chatt" + "value" : "Ny konversation" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "เริ่มแชทใหม่" + "value" : "เริ่มการสนทนาใหม่" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Yeni Sohbet" + "value" : "Yeni Konuşma" } }, "uk" : { @@ -13323,508 +13118,508 @@ "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Trò chuyện mớ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" : "新對話" } } } }, - "New Conversation" : { + "New Task" : { + "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "محادثة جديدة" + "value" : "مهمة جديدة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Nova conversa" + "value" : "Nova tasca" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Nová konverzace" + "value" : "Nový úkol" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Ny samtale" + "value" : "Ny Opgave" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neue Konversation" + "value" : "Neue Aufgabe" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Νέα Συνομιλία" + "value" : "Νέα Εργασία" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "New Chat" + "value" : "New Task" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "New Conversation" + "value" : "New Task" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "New Chat" + "value" : "New Task" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva conversación" + "value" : "Nueva tarea" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva conversación" + "value" : "Nueva Tarea" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Uusi keskustelu" + "value" : "Uusi tehtävä" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle conversation" + "value" : "Nouvelle tâche" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle conversation" + "value" : "Nouvelle tâche" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "שיחה חדשה" + "value" : "משימה חדשה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "नई बातचीत" + "value" : "नई कार्य" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Novi razgovor" + "value" : "Novi zadatak" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Új beszélgetés" + "value" : "Új feladat" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Percakapan Baru" + "value" : "Tugas Baru" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuova conversazione" + "value" : "Nuova attività" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "新規メッセージ" + "value" : "新規タスク" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "새 대화" + "value" : "새 작업" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Perbualan Baru" + "value" : "Tugasan Baharu" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ny samtale" + "value" : "Ny oppgave" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuw gesprek" + "value" : "Nieuwe taak" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nowa rozmowa" + "value" : "Nowe zadanie" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Nova Conversa" + "value" : "Nova Tarefa" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Nova Conversa" + "value" : "Nova Tarefa" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Conversație Nouă" + "value" : "Activitate nouă" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Новый чат" + "value" : "Новая задача" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Nový rozhovor" + "value" : "Nová úloha" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Ny konversation" + "value" : "Ny uppgift" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "เริ่มการสนทนาใหม่" + "value" : "งานใหม่" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Yeni Konuşma" + "value" : "Yeni Görev" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Нова розмова" + "value" : "Нове завдання" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Cuộc trò chuyện mới" + "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" : "新任務" } } } }, - "New Task" : { + "No Chat" : { "extractionState" : "manual", "localizations" : { "ar" : { "stringUnit" : { "state" : "translated", - "value" : "مهمة جديدة" + "value" : "لا توجد محادثة" } }, "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Nova tasca" + "value" : "Sense xat" } }, "cs" : { "stringUnit" : { "state" : "translated", - "value" : "Nový úkol" + "value" : "Žádný chat" } }, "da" : { "stringUnit" : { "state" : "translated", - "value" : "Ny Opgave" + "value" : "Ingen chat" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Neue Aufgabe" + "value" : "Kein Chat" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Νέα Εργασία" + "value" : "Χωρίς Συνομιλία" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "New Task" + "value" : "No Chat" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "New Task" + "value" : "No Chat" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "New Task" + "value" : "No Chat" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva tarea" + "value" : "Sin chat" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Nueva Tarea" + "value" : "Sin chat" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Uusi tehtävä" + "value" : "Ei keskustelua" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle tâche" + "value" : "Pas de discussion" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Nouvelle tâche" + "value" : "Pas de discussion" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "משימה חדשה" + "value" : "אין צ'אט" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "नई कार्य" + "value" : "कोई चैट नहीं" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Novi zadatak" + "value" : "Nema chata" } }, "hu" : { "stringUnit" : { "state" : "translated", - "value" : "Új feladat" + "value" : "Nincs csevegés" } }, "id" : { "stringUnit" : { "state" : "translated", - "value" : "Tugas Baru" + "value" : "Tidak Ada Obrolan" } }, "it" : { "stringUnit" : { "state" : "translated", - "value" : "Nuova attività" + "value" : "Nessuna chat" } }, "ja" : { "stringUnit" : { "state" : "translated", - "value" : "新規タスク" + "value" : "チャットなし" } }, "ko" : { "stringUnit" : { "state" : "translated", - "value" : "새 작업" + "value" : "채팅 없음" } }, "ms" : { "stringUnit" : { "state" : "translated", - "value" : "Tugasan Baharu" + "value" : "Tiada Sembang" } }, "nb" : { "stringUnit" : { "state" : "translated", - "value" : "Ny oppgave" + "value" : "Ingen Chat" } }, "nl" : { "stringUnit" : { "state" : "translated", - "value" : "Nieuwe taak" + "value" : "Geen chat" } }, "pl" : { "stringUnit" : { "state" : "translated", - "value" : "Nowe zadanie" + "value" : "Brak czatu" } }, "pt-BR" : { "stringUnit" : { "state" : "translated", - "value" : "Nova Tarefa" + "value" : "Sem bate-papo" } }, "pt-PT" : { "stringUnit" : { "state" : "translated", - "value" : "Nova Tarefa" + "value" : "Sem Chat" } }, "ro" : { "stringUnit" : { "state" : "translated", - "value" : "Activitate nouă" + "value" : "Fără chat" } }, "ru" : { "stringUnit" : { "state" : "translated", - "value" : "Новая задача" + "value" : "Нет чата" } }, "sk" : { "stringUnit" : { "state" : "translated", - "value" : "Nová úloha" + "value" : "Žiadny chat" } }, "sv" : { "stringUnit" : { "state" : "translated", - "value" : "Ny uppgift" + "value" : "Ingen chatt" } }, "th" : { "stringUnit" : { "state" : "translated", - "value" : "งานใหม่" + "value" : "ไม่มีการแชท" } }, "tr" : { "stringUnit" : { "state" : "translated", - "value" : "Yeni Görev" + "value" : "Sohbet Yok" } }, "uk" : { "stringUnit" : { "state" : "translated", - "value" : "Нове завдання" + "value" : "Немає чату" } }, "vi" : { "stringUnit" : { "state" : "translated", - "value" : "Tác vụ Mới" + "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" : "無聊天" } } } }, - "No Chat" : { - "extractionState" : "manual", + "No Conversation" : { "localizations" : { "ar" : { "stringUnit" : { @@ -13835,7 +13630,7 @@ "ca" : { "stringUnit" : { "state" : "translated", - "value" : "Sense xat" + "value" : "Cap Conversa" } }, "cs" : { @@ -13847,323 +13642,85 @@ "da" : { "stringUnit" : { "state" : "translated", - "value" : "Ingen chat" + "value" : "Ingen samtale" } }, "de" : { "stringUnit" : { "state" : "translated", - "value" : "Kein Chat" + "value" : "Kein Gespräch" } }, "el" : { "stringUnit" : { "state" : "translated", - "value" : "Χωρίς Συνομιλία" + "value" : "Καμία συνομιλία" } }, "en-AU" : { "stringUnit" : { "state" : "translated", - "value" : "No Chat" + "value" : "No Conversation" } }, "en-GB" : { "stringUnit" : { "state" : "translated", - "value" : "No Chat" + "value" : "No Conversation" } }, "en-IN" : { "stringUnit" : { "state" : "translated", - "value" : "No Chat" + "value" : "No Conversation" } }, "es" : { "stringUnit" : { "state" : "translated", - "value" : "Sin chat" + "value" : "Sin conversación" } }, "es-419" : { "stringUnit" : { "state" : "translated", - "value" : "Sin chat" + "value" : "Sin conversación" } }, "fi" : { "stringUnit" : { "state" : "translated", - "value" : "Ei keskustelua" + "value" : "Ei keskusteluja" } }, "fr" : { "stringUnit" : { "state" : "translated", - "value" : "Pas de discussion" + "value" : "Aucune conversation" } }, "fr-CA" : { "stringUnit" : { "state" : "translated", - "value" : "Pas de discussion" + "value" : "Aucune conversation" } }, "he" : { "stringUnit" : { "state" : "translated", - "value" : "אין צ'אט" + "value" : "אין שיחה" } }, "hi" : { "stringUnit" : { "state" : "translated", - "value" : "कोई चैट नहीं" + "value" : "कोई बातचीत नहीं" } }, "hr" : { "stringUnit" : { "state" : "translated", - "value" : "Nema chata" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nincs csevegés" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tidak Ada Obrolan" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nessuna chat" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "チャットなし" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "채팅 없음" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tiada Sembang" - } - }, - "nb" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ingen Chat" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Geen chat" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Brak czatu" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sem bate-papo" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sem Chat" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Fără chat" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Нет чата" - } - }, - "sk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Žiadny chat" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ingen chatt" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ไม่มีการแชท" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sohbet Yok" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Немає чату" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không có Trò chuyện" - } - }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "無聊天" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "无对话" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "沒有聊天" - } - } - } - }, - "No Conversation" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "لا توجد محادثة" - } - }, - "ca" : { - "stringUnit" : { - "state" : "translated", - "value" : "Cap Conversa" - } - }, - "cs" : { - "stringUnit" : { - "state" : "translated", - "value" : "Žádný chat" - } - }, - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ingen samtale" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Kein Gespräch" - } - }, - "el" : { - "stringUnit" : { - "state" : "translated", - "value" : "Καμία συνομιλία" - } - }, - "en-AU" : { - "stringUnit" : { - "state" : "translated", - "value" : "No Conversation" - } - }, - "en-GB" : { - "stringUnit" : { - "state" : "translated", - "value" : "No Conversation" - } - }, - "en-IN" : { - "stringUnit" : { - "state" : "translated", - "value" : "No Conversation" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sin conversación" - } - }, - "es-419" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sin conversación" - } - }, - "fi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ei keskusteluja" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aucune conversation" - } - }, - "fr-CA" : { - "stringUnit" : { - "state" : "translated", - "value" : "Aucune conversation" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "אין שיחה" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "कोई बातचीत नहीं" - } - }, - "hr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nema razgovora" + "value" : "Nema razgovora" } }, "hu" : { @@ -14175,8468 +13732,7769 @@ "id" : { "stringUnit" : { "state" : "translated", - "value" : "Tidak Ada Percakapan" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nessuna conversazione" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "会話なし" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "대화 없음" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tiada Perbualan" - } - }, - "nb" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ingen samtaler" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Geen gesprek" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Brak rozmowy" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sem conversa" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sem Conversa" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nicio conversație" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Нет беседы" - } - }, - "sk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Žiadna konverzácia" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ingen konversation" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ไม่มีการสนทนา" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Sohbet Yok" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Немає розмов" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không có hội thoại" - } - }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "沒有對話" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "无对话" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "尚未有對話" - } - } - } - }, - "Not selected" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "غير محدد" - } - }, - "ca" : { - "stringUnit" : { - "state" : "translated", - "value" : "No seleccionat" - } - }, - "cs" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nevybráno" - } - }, - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ikke valgt" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nicht ausgewählt" - } - }, - "el" : { - "stringUnit" : { - "state" : "translated", - "value" : "Δεν έχει επιλεγεί" - } - }, - "en-AU" : { - "stringUnit" : { - "state" : "translated", - "value" : "Not selected" - } - }, - "en-GB" : { - "stringUnit" : { - "state" : "translated", - "value" : "Not selected" - } - }, - "en-IN" : { - "stringUnit" : { - "state" : "translated", - "value" : "Not selected" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "No seleccionado" - } - }, - "es-419" : { - "stringUnit" : { - "state" : "translated", - "value" : "No seleccionado" - } - }, - "fi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ei valittu" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Non sélectionné" - } - }, - "fr-CA" : { - "stringUnit" : { - "state" : "translated", - "value" : "Non sélectionné" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "לא נבחר" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "चयनित नहीं" - } - }, - "hr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nije odabrano" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nincs kiválasztva" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "Belum dipilih" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "Non selezionato" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "未選択" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "선택 안 됨" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tidak dipilih" - } - }, - "nb" : { - "stringUnit" : { - "state" : "translated", - "value" : "Ikke valgt" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Niet geselecteerd" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nie wybrano" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "Não selecionado" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "Não selecionado" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "Neselectat" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "Не выбрано" - } - }, - "sk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Nevybraté" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "Inte valt" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ไม่ได้เลือก" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Seçilmedi" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Не вибрано" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Không được chọn" - } - }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "未選擇" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "无选择" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "未選取" - } - } - } - }, - "OK" : { - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "حسنًا" - } - }, - "ca" : { - "stringUnit" : { - "state" : "translated", - "value" : "D'acord" - } - }, - "cs" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "el" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "en-AU" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "en-GB" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "en-IN" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "es-419" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "fi" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "fr-CA" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "אישור" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "ठीक है" - } - }, - "hr" : { - "stringUnit" : { - "state" : "translated", - "value" : "U redu" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "id" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "it" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "ja" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "ko" : { - "stringUnit" : { - "state" : "translated", - "value" : "확인" - } - }, - "ms" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "nb" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "nl" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "pl" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "pt-BR" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "pt-PT" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "ro" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "ru" : { - "stringUnit" : { - "state" : "translated", - "value" : "ОК" - } - }, - "sk" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "sv" : { - "stringUnit" : { - "state" : "translated", - "value" : "OK" - } - }, - "th" : { - "stringUnit" : { - "state" : "translated", - "value" : "ตกลง" - } - }, - "tr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Tamam" - } - }, - "uk" : { - "stringUnit" : { - "state" : "translated", - "value" : "Гаразд" - } - }, - "vi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Đồng ý" - } - }, - "zh-HK" : { - "stringUnit" : { - "state" : "translated", - "value" : "確定" - } - }, - "zh-Hans" : { - "stringUnit" : { - "state" : "translated", - "value" : "确定" - } - }, - "zh-Hant" : { - "stringUnit" : { - "state" : "translated", - "value" : "好的" - } - } - } - }, - "Please enter Hugging Face Repo ID" : { - "extractionState" : "manual", - "localizations" : { - "ar" : { - "stringUnit" : { - "state" : "translated", - "value" : "يرجى إدخال معرف مستودع Hugging Face" - } - }, - "ca" : { - "stringUnit" : { - "state" : "translated", - "value" : "Introduïu l'ID del repositori de Hugging Face" - } - }, - "cs" : { - "stringUnit" : { - "state" : "translated", - "value" : "Zadejte ID repozitáře Hugging Face" - } - }, - "da" : { - "stringUnit" : { - "state" : "translated", - "value" : "Indtast venligst Hugging Face Repo ID" - } - }, - "de" : { - "stringUnit" : { - "state" : "translated", - "value" : "Bitte Hugging Face Repo-ID eingeben" - } - }, - "el" : { - "stringUnit" : { - "state" : "translated", - "value" : "Καταχωρίστε το ID του αποθετηρίου Hugging Face" - } - }, - "en-AU" : { - "stringUnit" : { - "state" : "translated", - "value" : "Please enter Hugging Face Repo ID" - } - }, - "en-GB" : { - "stringUnit" : { - "state" : "translated", - "value" : "Please enter Hugging Face Repository ID" - } - }, - "en-IN" : { - "stringUnit" : { - "state" : "translated", - "value" : "Please enter Hugging Face Repo ID" - } - }, - "es" : { - "stringUnit" : { - "state" : "translated", - "value" : "Introduce el ID del repositorio de Hugging Face" - } - }, - "es-419" : { - "stringUnit" : { - "state" : "translated", - "value" : "Por favor, ingresa el ID del repositorio Hugging Face" - } - }, - "fi" : { - "stringUnit" : { - "state" : "translated", - "value" : "Anna Hugging Face -repo ID" - } - }, - "fr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Veuillez entrer l'ID du dépôt Hugging Face" - } - }, - "fr-CA" : { - "stringUnit" : { - "state" : "translated", - "value" : "Veuillez entrer l'ID du dépôt Hugging Face" - } - }, - "he" : { - "stringUnit" : { - "state" : "translated", - "value" : "אנא הזן את מזהה המאגר של Hugging Face" - } - }, - "hi" : { - "stringUnit" : { - "state" : "translated", - "value" : "कृपया Hugging Face Repo ID दर्ज करें" - } - }, - "hr" : { - "stringUnit" : { - "state" : "translated", - "value" : "Unesite Hugging Face ID spremišta" - } - }, - "hu" : { - "stringUnit" : { - "state" : "translated", - "value" : "Adja meg a Hugging Face tárház azonosítóját" - } - }, - "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" : { "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" : { "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" : { + "Open %@" : { + + }, + "Other AI Provider" : { + + }, + "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" : { "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" : { "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" : { "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" : { + "Quit" : { + + }, + "Regenerate" : { "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" : { "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" : { "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" : { "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..." : { "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..." : { "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" : { "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" : { "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" : { "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", + "Text Generation" : { + + }, + "Title" : { "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" : { "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" : { "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" : { "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" : { "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" : { "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" : { "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" : { "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 %@" : { "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" : { "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" : { "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" : "視窗外觀" } } } + }, + "X" : { + } }, "version" : "1.0" -} +} \ No newline at end of file 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/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/ChatMLXCore/.gitignore b/ChatMLXCore/.gitignore new file mode 100644 index 0000000..0023a53 --- /dev/null +++ b/ChatMLXCore/.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/ChatMLXCore/Package.resolved b/ChatMLXCore/Package.resolved new file mode 100644 index 0000000..48f7f7c --- /dev/null +++ b/ChatMLXCore/Package.resolved @@ -0,0 +1,158 @@ +{ + "pins" : [ + { + "identity" : "alamofire", + "kind" : "remoteSourceControl", + "location" : "https://github.com/Alamofire/Alamofire.git", + "state" : { + "revision" : "f455c2975872ccd2d9c81594c658af65716e9b9a", + "version" : "5.9.1" + } + }, + { + "identity" : "alerttoast", + "kind" : "remoteSourceControl", + "location" : "https://github.com/elai950/AlertToast.git", + "state" : { + "revision" : "b39252eacd159904afd7d718bba0afabb87f2f2f", + "version" : "1.3.9" + } + }, + { + "identity" : "compactslider", + "kind" : "remoteSourceControl", + "location" : "https://github.com/buh/CompactSlider.git", + "state" : { + "revision" : "abe4d1df6f0c85dcb133266cc07c2a5d08295726", + "version" : "1.1.6" + } + }, + { + "identity" : "defaults", + "kind" : "remoteSourceControl", + "location" : "https://github.com/sindresorhus/Defaults.git", + "state" : { + "branch" : "main", + "revision" : "a89f799930c92a85aa04b72131849d46c0833ab3" + } + }, + { + "identity" : "gzipswift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/1024jp/GzipSwift", + "state" : { + "revision" : "731037f6cc2be2ec01562f6597c1d0aa3fe6fd05", + "version" : "6.0.1" + } + }, + { + "identity" : "jinja", + "kind" : "remoteSourceControl", + "location" : "https://github.com/maiqingqiang/Jinja", + "state" : { + "revision" : "b435eb62b0d3d5f34167ec70a128355486981712", + "version" : "1.0.5" + } + }, + { + "identity" : "luminare", + "kind" : "remoteSourceControl", + "location" : "https://github.com/MrKai77/Luminare.git", + "state" : { + "branch" : "main", + "revision" : "5300c9888823feccfb824ac313965536686b4352" + } + }, + { + "identity" : "mlx-swift", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ml-explore/mlx-swift", + "state" : { + "revision" : "78a7cfe6701d6e9c88e9d4a0d1f7990af84b2146", + "version" : "0.18.0" + } + }, + { + "identity" : "mlx-swift-examples", + "kind" : "remoteSourceControl", + "location" : "https://github.com/ml-explore/mlx-swift-examples.git", + "state" : { + "branch" : "main", + "revision" : "caa5caf4ca64e79c3ad8f64e2a49f9b85ef1bc19" + } + }, + { + "identity" : "networkimage", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gonzalezreal/NetworkImage", + "state" : { + "revision" : "7aff8d1b31148d32c5933d75557d42f6323ee3d1", + "version" : "6.0.0" + } + }, + { + "identity" : "splash", + "kind" : "remoteSourceControl", + "location" : "https://github.com/johnsundell/splash.git", + "state" : { + "revision" : "7f4df436eb78fe64fe2c32c58006e9949fa28ad8", + "version" : "0.16.0" + } + }, + { + "identity" : "swift-argument-parser", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-argument-parser.git", + "state" : { + "revision" : "41982a3656a71c768319979febd796c6fd111d5c", + "version" : "1.5.0" + } + }, + { + "identity" : "swift-log", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-log.git", + "state" : { + "revision" : "9cb486020ebf03bfa5b5df985387a14a98744537", + "version" : "1.6.1" + } + }, + { + "identity" : "swift-markdown-ui", + "kind" : "remoteSourceControl", + "location" : "https://github.com/gonzalezreal/swift-markdown-ui.git", + "state" : { + "branch" : "main", + "revision" : "55441810c0f678c78ed7e2ebd46dde89228e02fc" + } + }, + { + "identity" : "swift-numerics", + "kind" : "remoteSourceControl", + "location" : "https://github.com/apple/swift-numerics", + "state" : { + "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b", + "version" : "1.0.2" + } + }, + { + "identity" : "swift-transformers", + "kind" : "remoteSourceControl", + "location" : "https://github.com/huggingface/swift-transformers", + "state" : { + "revision" : "4d25d20e49d2269aec1556231f8e278db7b2a4f0", + "version" : "0.1.13" + } + }, + { + "identity" : "swiftui-introspect", + "kind" : "remoteSourceControl", + "location" : "https://github.com/siteline/swiftui-introspect.git", + "state" : { + "revision" : "807f73ce09a9b9723f12385e592b4e0aaebd3336", + "version" : "1.3.0" + } + } + ], + "version" : 2 +} diff --git a/ChatMLXCore/Package.swift b/ChatMLXCore/Package.swift new file mode 100644 index 0000000..2e7d3c6 --- /dev/null +++ b/ChatMLXCore/Package.swift @@ -0,0 +1,56 @@ +// swift-tools-version: 5.9 +// The swift-tools-version declares the minimum version of Swift required to build this package. + +import PackageDescription + +let package = Package( + name: "ChatMLXCore", + platforms: [.macOS(.v14)], + products: [ + // Products define the executables and libraries a package produces, making them visible to other packages. + .library( + name: "ChatMLXCore", + targets: ["ChatMLXCore"] + ), + .library( + name: "Utilities", + targets: ["Utilities"] + ), + ], + dependencies: [ + .package(url: "https://github.com/Alamofire/Alamofire.git", from: "5.9.1"), + .package(url: "https://github.com/elai950/AlertToast.git", from: "1.3.9"), + .package(url: "https://github.com/buh/CompactSlider.git", from: "1.1.6"), + .package(url: "https://github.com/sindresorhus/Defaults.git", branch: "main"), + .package(url: "https://github.com/MrKai77/Luminare.git", branch: "main"), + .package(url: "https://github.com/ml-explore/mlx-swift-examples.git", branch: "main"), + .package(url: "https://github.com/johnsundell/splash.git", from: "0.16.0"), + .package(url: "https://github.com/apple/swift-log.git", from: "1.6.1"), + .package(url: "https://github.com/gonzalezreal/swift-markdown-ui.git", branch: "main"), + .package(url: "https://github.com/siteline/swiftui-introspect.git", from: "1.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: "ChatMLXCore", + dependencies: [ + .product(name: "Alamofire", package: "Alamofire"), + .product(name: "AlertToast", package: "AlertToast"), + .product(name: "CompactSlider", package: "CompactSlider"), + .product(name: "Defaults", package: "Defaults"), + .product(name: "Luminare", package: "Luminare"), + .product(name: "LLM", package: "mlx-swift-examples"), + .product(name: "Logging", package: "swift-log"), + .product(name: "MarkdownUI", package: "swift-markdown-ui"), + .product(name: "SwiftUIIntrospect", package: "swiftui-introspect"), + ] + ), + .target( + name: "Utilities", + dependencies: [ + .product(name: "Logging", package: "swift-log") + ] + ), + ] +) diff --git a/ChatMLXCore/Sources/ChatMLXCore/ChatMLXApp.swift b/ChatMLXCore/Sources/ChatMLXCore/ChatMLXApp.swift new file mode 100644 index 0000000..33f508c --- /dev/null +++ b/ChatMLXCore/Sources/ChatMLXCore/ChatMLXApp.swift @@ -0,0 +1,19 @@ +// +// ChatMLXApp.swift +// ChatMLXCore +// +// Created by John Mai on 2024/8/3. +// + +import Defaults +import SwiftUI + +@main +struct ChatMLXApp: App { + + var body: some Scene { + WindowGroup { + Text("66") + } + } +} diff --git a/ChatMLXCore/Sources/ChatMLXCore/ChatMLXCore.swift b/ChatMLXCore/Sources/ChatMLXCore/ChatMLXCore.swift new file mode 100644 index 0000000..08b22b8 --- /dev/null +++ b/ChatMLXCore/Sources/ChatMLXCore/ChatMLXCore.swift @@ -0,0 +1,2 @@ +// The Swift Programming Language +// https://docs.swift.org/swift-book diff --git a/ChatMLXTests/ChatMLXTests.swift b/ChatMLXTests/ChatMLXTests.swift new file mode 100644 index 0000000..86ed88b --- /dev/null +++ b/ChatMLXTests/ChatMLXTests.swift @@ -0,0 +1,16 @@ +// +// ChatMLXTests.swift +// ChatMLXTests +// +// Created by John Mai on 2024/10/10. +// + +import Testing + +struct ChatMLXTests { + + @Test func example() async throws { + // Write your test here and use APIs like `#expect(...)` to check expected conditions. + } + +} diff --git a/ChatMLXTests/MarkdownMetadataTests.swift b/ChatMLXTests/MarkdownMetadataTests.swift new file mode 100644 index 0000000..1fb3305 --- /dev/null +++ b/ChatMLXTests/MarkdownMetadataTests.swift @@ -0,0 +1,45 @@ +// +// MarkdownMetadataTests.swift +// ChatMLXTests +// +// Created by John Mai on 2024/10/10. +// + +import Testing + +@testable import ChatMLX + +struct MarkdownMetadataTests { + @Test func testBasicMetadataParsing() async throws { + let markdown = """ + --- + license: other + license_name: qwen + license_link: https://huggingface.co/Qwen/Qwen2.5-VL-72B-Instruct/blob/main/LICENSE + language: + - en + pipeline_tag: image-text-to-text + tags: + - multimodal + - mlx + library_name: transformers + base_model: + - Qwen/Qwen2.5-VL-72B-Instruct + --- + + # Content + This is the content. + """ + + let metadata = MarkdownMetadata(from: markdown) + + #expect(metadata.string(for: "license") == "other") + #expect(metadata.string(for: "license_name") == "qwen") + #expect(metadata.string(for: "license_link") == "https://huggingface.co/Qwen/Qwen2.5-VL-72B-Instruct/blob/main/LICENSE") + #expect(metadata.array(for: "language") == ["en"]) + #expect(metadata.string(for: "pipeline_tag") == "image-text-to-text") + #expect(metadata.array(for: "tags") == ["multimodal", "mlx"]) + #expect(metadata.string(for: "library_name") == "transformers") + #expect(metadata.array(for: "base_model") == ["Qwen/Qwen2.5-VL-72B-Instruct"]) + } +} diff --git a/ChatMLXTests/OpenAIServiceTests.swift b/ChatMLXTests/OpenAIServiceTests.swift new file mode 100644 index 0000000..d91a4e8 --- /dev/null +++ b/ChatMLXTests/OpenAIServiceTests.swift @@ -0,0 +1,17 @@ +// +// OpenAIServiceTests.swift +// ChatMLX +// +// Created by John Mai on 2024/11/9. +// + +import Testing + +@testable import ChatMLX + +struct OpenAIServiceTests { + @Test + func fetchModels() async throws { + + } +} diff --git a/ChatMLXTests/ProviderFactoryTests.swift b/ChatMLXTests/ProviderFactoryTests.swift new file mode 100644 index 0000000..066b218 --- /dev/null +++ b/ChatMLXTests/ProviderFactoryTests.swift @@ -0,0 +1,17 @@ +// +// ProviderFactoryTests.swift +// ChatMLXTests +// +// Created by John Mai on 2024/10/13. +// + +import Testing + +@testable import ChatMLX + +struct ProviderFactoryTests { + @Test func mlx() async throws { + // let models = await ProviderFactory.shared.provider(.mlx).listModels() + // print(models) + } +} diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..82f0545 --- /dev/null +++ b/Makefile @@ -0,0 +1,2 @@ +format: + swift-format . -i -r \ No newline at end of file diff --git a/README-zh_CN.md b/README-zh_CN.md index e5138f5..40b947c 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -2,7 +2,6 @@ [English](./README.md) | 简体中文 - [![贡献者][contributors-shield]][contributors-url] [![分支数][forks-shield]][forks-url] [![星标数][stars-shield]][stars-url] @@ -96,19 +95,52 @@ xattr -c /Applications/ChatMLX.app/* sudo xattr -d com.apple.quarantine /Applications/ChatMLX.app/ ``` +## 参与贡献 🤝 + +我们非常欢迎各种形式的贡献。如果你对贡献 ChatMLX 感兴趣,可以大展身手,向我们展示你的奇思妙想。 + +[![][github-contrib-shield]][github-contrib-link] + +## Links 🌐 + +- [MLX Swift](https://github.com/ml-explore/mlx-swift) - [MLX](https://github.com/ml-explore/mlx)的Swift API +- [Transformers Swift](https://github.com/huggingface/swift-transformers) - + 在Swift中实现类似于[transformers](https://github.com/huggingface/transformers)的API的Swift包 +- [Jinja Swift](https://github.com/maiqingqiang/Jinja) - 一个简约的 Swift + 实现 [Jinja](https://jinja.palletsprojects.com/en/3.1.x/) 模板引擎,专门用于解析和渲染 ML 聊天模板。 + ## Star 历史 🌟 [![星标历史图表](https://api.star-history.com/svg?repos=maiqingqiang/ChatMLX&type=Date)](https://star-history.com/#maiqingqiang/ChatMLX&Date) +
+ +[![][back-to-top]](#readme-top) + +
+ [contributors-shield]: https://img.shields.io/github/contributors/maiqingqiang/ChatMLX.svg?style=for-the-badge + [contributors-url]: https://github.com/maiqingqiang/ChatMLX/graphs/contributors + [forks-shield]: https://img.shields.io/github/forks/maiqingqiang/ChatMLX.svg?style=for-the-badge + [forks-url]: https://github.com/maiqingqiang/ChatMLX/network/members + [stars-shield]: https://img.shields.io/github/stars/maiqingqiang/ChatMLX.svg?style=for-the-badge + [stars-url]: https://github.com/maiqingqiang/ChatMLX/stargazers + [issues-shield]: https://img.shields.io/github/issues/maiqingqiang/ChatMLX.svg?style=for-the-badge + [issues-url]: https://github.com/maiqingqiang/ChatMLX/issues + [license-shield]: https://img.shields.io/github/license/maiqingqiang/ChatMLX.svg?style=for-the-badge + [license-url]: https://github.com/maiqingqiang/ChatMLX/blob/main/LICENSE -

( < a href="#readme-top ">返回顶部< / a > )< p > \ No newline at end of file +[github-contrib-link]: https://github.com/maiqingqiang/ChatMLX/graphs/contributors + +[github-contrib-shield]: https://contrib.rocks/image?repo=maiqingqiang/ChatMLX + +[back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square diff --git a/README.md b/README.md index 551e8fe..79f937c 100644 --- a/README.md +++ b/README.md @@ -95,10 +95,32 @@ If that doesn’t work, try running this command: sudo xattr -d com.apple.quarantine /Applications/ChatMLX.app/ ``` +## 参与贡献 🤝 + +Contributions of all types are more than welcome, if you are interested in contributing ChatMLX, feel free to show us +what you’re made of. + +[![][github-contrib-shield]][github-contrib-link] + +## Links 🌐 + +- [MLX Swift](https://github.com/ml-explore/mlx-swift) - Swift API for [MLX](https://github.com/ml-explore/mlx) +- [Transformers Swift](https://github.com/huggingface/swift-transformers) - Swift Package to implement + a [transformers](https://github.com/huggingface/transformers)-like API in Swift +- [Jinja Swift](https://github.com/maiqingqiang/Jinja) - A minimalistic Swift implementation of + the [Jinja](https://jinja.palletsprojects.com/en/3.1.x/) templating engine, specifically designed for parsing and + rendering ML chat templates. + ## Star History 🌟 [![Star History Chart](https://api.star-history.com/svg?repos=maiqingqiang/ChatMLX&type=Date)](https://star-history.com/#maiqingqiang/ChatMLX&Date) +

+ +[![][back-to-top]](#readme-top) + +
+ [contributors-shield]: https://img.shields.io/github/contributors/maiqingqiang/ChatMLX.svg?style=for-the-badge [contributors-url]: https://github.com/maiqingqiang/ChatMLX/graphs/contributors @@ -119,4 +141,8 @@ sudo xattr -d com.apple.quarantine /Applications/ChatMLX.app/ [license-url]: https://github.com/maiqingqiang/ChatMLX/blob/main/LICENSE -

(back to top)

+[github-contrib-link]: https://github.com/maiqingqiang/ChatMLX/graphs/contributors + +[github-contrib-shield]: https://contrib.rocks/image?repo=maiqingqiang/ChatMLX + +[back-to-top]: https://img.shields.io/badge/-BACK_TO_TOP-151515?style=flat-square diff --git a/images/Logo.psd b/images/Logo.psd new file mode 100644 index 0000000..7e3b10a Binary files /dev/null and b/images/Logo.psd differ