mirror of
https://github.com/MustangYM/OSXChatGpt.git
synced 2025-11-25 03:15:08 +08:00
plugin
This commit is contained in:
@@ -67,11 +67,11 @@
|
||||
CB28A52829C1569900F0286A /* ThinkingAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB28A52729C1569900F0286A /* ThinkingAnimationView.swift */; };
|
||||
CB298CE52A0A97390022EE6B /* ExportView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB298CE42A0A97390022EE6B /* ExportView.swift */; };
|
||||
CB2A943229F828E500D3A048 /* SwiftSoup in Frameworks */ = {isa = PBXBuildFile; productRef = CB2A943129F828E500D3A048 /* SwiftSoup */; };
|
||||
CB2A943929F830E500D3A048 /* Conversation+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A943729F830E500D3A048 /* Conversation+CoreDataClass.swift */; };
|
||||
CB2A943A29F830E500D3A048 /* Conversation+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A943829F830E500D3A048 /* Conversation+CoreDataProperties.swift */; };
|
||||
CB2A944029F973F800D3A048 /* GoogleSearchPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A943F29F973F800D3A048 /* GoogleSearchPopView.swift */; };
|
||||
CB2A944229F993A200D3A048 /* GoogleSearchSettingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2A944129F993A200D3A048 /* GoogleSearchSettingView.swift */; };
|
||||
CB2AADE42A41AFC100747BEB /* SimpleToast in Frameworks */ = {isa = PBXBuildFile; productRef = CB2AADE32A41AFC100747BEB /* SimpleToast */; };
|
||||
CB2AADE62A41E15600747BEB /* PluginContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2AADE52A41E15600747BEB /* PluginContentView.swift */; };
|
||||
CB2AADE82A42786F00747BEB /* PluginViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2AADE72A42786F00747BEB /* PluginViewModel.swift */; };
|
||||
CB2D438929F0183A007742AE /* ChatGPT+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2D438729F0183A007742AE /* ChatGPT+CoreDataClass.swift */; };
|
||||
CB2D438A29F0183A007742AE /* ChatGPT+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB2D438829F0183A007742AE /* ChatGPT+CoreDataProperties.swift */; };
|
||||
CB2F971F29CE12B6004EBD96 /* MarkdownUI in Frameworks */ = {isa = PBXBuildFile; productRef = CB2F971E29CE12B6004EBD96 /* MarkdownUI */; };
|
||||
@@ -82,11 +82,23 @@
|
||||
CB4D1FC429F195E60010D063 /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = CB4D1FC629F195E60010D063 /* Localizable.strings */; };
|
||||
CB53A3BE29D48C8F00A5B8FC /* Prompt+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB53A3BC29D48C8F00A5B8FC /* Prompt+CoreDataClass.swift */; };
|
||||
CB53A3BF29D48C8F00A5B8FC /* Prompt+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB53A3BD29D48C8F00A5B8FC /* Prompt+CoreDataProperties.swift */; };
|
||||
CB55E0EA2A3AAEA2006EBE2D /* PluginManifest.swift in Sources */ = {isa = PBXBuildFile; fileRef = CB55E0E92A3AAEA2006EBE2D /* PluginManifest.swift */; };
|
||||
CBD5AB6429E6DE9A007B6625 /* ProjectSettingManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD5AB6329E6DE9A007B6625 /* ProjectSettingManager.swift */; };
|
||||
CBD5AB6629E6EFE3007B6625 /* MarkdownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD5AB6529E6EFE3007B6625 /* MarkdownView.swift */; };
|
||||
CBD5AB6929E707A1007B6625 /* MarkdownTheme.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD5AB6829E707A1007B6625 /* MarkdownTheme.swift */; };
|
||||
CBD5AB6B29E707F0007B6625 /* MarkdownTokenType.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBD5AB6A29E707F0007B6625 /* MarkdownTokenType.swift */; };
|
||||
CBFBD75A2A4697450090454B /* Yams in Frameworks */ = {isa = PBXBuildFile; productRef = CBFBD7592A4697450090454B /* Yams */; };
|
||||
CBFBD7632A4931AD0090454B /* PluginManifestAuth+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFBD75B2A4931AD0090454B /* PluginManifestAuth+CoreDataClass.swift */; };
|
||||
CBFBD7642A4931AD0090454B /* PluginManifestAuth+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFBD75C2A4931AD0090454B /* PluginManifestAuth+CoreDataProperties.swift */; };
|
||||
CBFBD7652A4931AD0090454B /* PluginManifestAPI+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFBD75D2A4931AD0090454B /* PluginManifestAPI+CoreDataClass.swift */; };
|
||||
CBFBD7662A4931AD0090454B /* PluginManifestAPI+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFBD75E2A4931AD0090454B /* PluginManifestAPI+CoreDataProperties.swift */; };
|
||||
CBFBD7702A528D050090454B /* SettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFBD76F2A528D050090454B /* SettingsView.swift */; };
|
||||
CBFBD7752A57C5400090454B /* PluginAPIInstall+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFBD7712A57C5400090454B /* PluginAPIInstall+CoreDataClass.swift */; };
|
||||
CBFBD7762A57C5400090454B /* PluginAPIInstall+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFBD7722A57C5400090454B /* PluginAPIInstall+CoreDataProperties.swift */; };
|
||||
CBFBD7772A57C5400090454B /* PluginManifest+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFBD7732A57C5400090454B /* PluginManifest+CoreDataClass.swift */; };
|
||||
CBFBD7782A57C5400090454B /* PluginManifest+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFBD7742A57C5400090454B /* PluginManifest+CoreDataProperties.swift */; };
|
||||
CBFBD77A2A5D0DE70090454B /* PluginPopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFBD7792A5D0DE70090454B /* PluginPopView.swift */; };
|
||||
CBFBD77D2A5E67040090454B /* Conversation+CoreDataClass.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFBD77B2A5E67040090454B /* Conversation+CoreDataClass.swift */; };
|
||||
CBFBD77E2A5E67040090454B /* Conversation+CoreDataProperties.swift in Sources */ = {isa = PBXBuildFile; fileRef = CBFBD77C2A5E67040090454B /* Conversation+CoreDataProperties.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXFileReference section */
|
||||
@@ -150,10 +162,10 @@
|
||||
CB28A52129C07BE500F0286A /* KeyboardMonitor.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = KeyboardMonitor.swift; sourceTree = "<group>"; };
|
||||
CB28A52729C1569900F0286A /* ThinkingAnimationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ThinkingAnimationView.swift; sourceTree = "<group>"; };
|
||||
CB298CE42A0A97390022EE6B /* ExportView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExportView.swift; sourceTree = "<group>"; };
|
||||
CB2A943729F830E500D3A048 /* Conversation+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Conversation+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
CB2A943829F830E500D3A048 /* Conversation+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Conversation+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
CB2A943F29F973F800D3A048 /* GoogleSearchPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleSearchPopView.swift; sourceTree = "<group>"; };
|
||||
CB2A944129F993A200D3A048 /* GoogleSearchSettingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GoogleSearchSettingView.swift; sourceTree = "<group>"; };
|
||||
CB2AADE52A41E15600747BEB /* PluginContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginContentView.swift; sourceTree = "<group>"; };
|
||||
CB2AADE72A42786F00747BEB /* PluginViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginViewModel.swift; sourceTree = "<group>"; };
|
||||
CB2D438729F0183A007742AE /* ChatGPT+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatGPT+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
CB2D438829F0183A007742AE /* ChatGPT+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "ChatGPT+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
CB2F972129CED6AE004EBD96 /* ChatRoomInputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatRoomInputView.swift; sourceTree = "<group>"; };
|
||||
@@ -163,12 +175,23 @@
|
||||
CB4D1FC729F195EA0010D063 /* zh-Hans */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = "zh-Hans"; path = "zh-Hans.lproj/Localizable.strings"; sourceTree = "<group>"; };
|
||||
CB53A3BC29D48C8F00A5B8FC /* Prompt+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Prompt+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
CB53A3BD29D48C8F00A5B8FC /* Prompt+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Prompt+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
CB55E0E92A3AAEA2006EBE2D /* PluginManifest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginManifest.swift; sourceTree = "<group>"; };
|
||||
CBC4B0FE29B8BF9600650296 /* OSXChatGPT.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = OSXChatGPT.xcdatamodel; sourceTree = "<group>"; };
|
||||
CBD5AB6329E6DE9A007B6625 /* ProjectSettingManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ProjectSettingManager.swift; sourceTree = "<group>"; };
|
||||
CBD5AB6529E6EFE3007B6625 /* MarkdownView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownView.swift; sourceTree = "<group>"; };
|
||||
CBD5AB6829E707A1007B6625 /* MarkdownTheme.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownTheme.swift; sourceTree = "<group>"; };
|
||||
CBD5AB6A29E707F0007B6625 /* MarkdownTokenType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MarkdownTokenType.swift; sourceTree = "<group>"; };
|
||||
CBFBD75B2A4931AD0090454B /* PluginManifestAuth+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginManifestAuth+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
CBFBD75C2A4931AD0090454B /* PluginManifestAuth+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginManifestAuth+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
CBFBD75D2A4931AD0090454B /* PluginManifestAPI+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginManifestAPI+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
CBFBD75E2A4931AD0090454B /* PluginManifestAPI+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginManifestAPI+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
CBFBD76F2A528D050090454B /* SettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingsView.swift; sourceTree = "<group>"; };
|
||||
CBFBD7712A57C5400090454B /* PluginAPIInstall+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginAPIInstall+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
CBFBD7722A57C5400090454B /* PluginAPIInstall+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginAPIInstall+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
CBFBD7732A57C5400090454B /* PluginManifest+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginManifest+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
CBFBD7742A57C5400090454B /* PluginManifest+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "PluginManifest+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
CBFBD7792A5D0DE70090454B /* PluginPopView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PluginPopView.swift; sourceTree = "<group>"; };
|
||||
CBFBD77B2A5E67040090454B /* Conversation+CoreDataClass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Conversation+CoreDataClass.swift"; sourceTree = "<group>"; };
|
||||
CBFBD77C2A5E67040090454B /* Conversation+CoreDataProperties.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Conversation+CoreDataProperties.swift"; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -181,6 +204,7 @@
|
||||
CB2F971F29CE12B6004EBD96 /* MarkdownUI in Frameworks */,
|
||||
CB0F5A5929D057FF005B71D2 /* Splash in Frameworks */,
|
||||
CB2AADE42A41AFC100747BEB /* SimpleToast in Frameworks */,
|
||||
CBFBD75A2A4697450090454B /* Yams in Frameworks */,
|
||||
CB2A943229F828E500D3A048 /* SwiftSoup in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -210,6 +234,8 @@
|
||||
children = (
|
||||
182B436329BC5C8700F06778 /* MainContentView.swift */,
|
||||
182B436029BC5C8700F06778 /* SessionsView.swift */,
|
||||
CB2AADE52A41E15600747BEB /* PluginContentView.swift */,
|
||||
CBFBD7792A5D0DE70090454B /* PluginPopView.swift */,
|
||||
182B436229BC5C8700F06778 /* ChatRoomView.swift */,
|
||||
CB27655B29D1C12C00897E0E /* MarkdownContentView.swift */,
|
||||
CB2F972729CEFB65004EBD96 /* ChatRoomToolBar.swift */,
|
||||
@@ -225,6 +251,7 @@
|
||||
182B437A29BC5FBE00F06778 /* EnterAPIView.swift */,
|
||||
188FB46629C1FA9700E3C18F /* EidtSessionRemarkView.swift */,
|
||||
CB28A52729C1569900F0286A /* ThinkingAnimationView.swift */,
|
||||
CBFBD76F2A528D050090454B /* SettingsView.swift */,
|
||||
);
|
||||
path = WindowView;
|
||||
sourceTree = "<group>";
|
||||
@@ -243,6 +270,7 @@
|
||||
182B437129BC5D1B00F06778 /* ChatGPTManager.swift */,
|
||||
182B436F29BC5D1B00F06778 /* CoreDataManager.swift */,
|
||||
182B437029BC5D1B00F06778 /* ViewModel.swift */,
|
||||
CB2AADE72A42786F00747BEB /* PluginViewModel.swift */,
|
||||
CB373A9A29F56CFF00B8D9BE /* Localization.swift */,
|
||||
CBD5AB6329E6DE9A007B6625 /* ProjectSettingManager.swift */,
|
||||
CB28A52129C07BE500F0286A /* KeyboardMonitor.swift */,
|
||||
@@ -368,14 +396,22 @@
|
||||
CBC4B11429B8CB1B00650296 /* Models */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
CBFBD7712A57C5400090454B /* PluginAPIInstall+CoreDataClass.swift */,
|
||||
CBFBD7722A57C5400090454B /* PluginAPIInstall+CoreDataProperties.swift */,
|
||||
CBFBD7732A57C5400090454B /* PluginManifest+CoreDataClass.swift */,
|
||||
CBFBD7742A57C5400090454B /* PluginManifest+CoreDataProperties.swift */,
|
||||
CBFBD75B2A4931AD0090454B /* PluginManifestAuth+CoreDataClass.swift */,
|
||||
CBFBD75C2A4931AD0090454B /* PluginManifestAuth+CoreDataProperties.swift */,
|
||||
CBFBD75D2A4931AD0090454B /* PluginManifestAPI+CoreDataClass.swift */,
|
||||
CBFBD75E2A4931AD0090454B /* PluginManifestAPI+CoreDataProperties.swift */,
|
||||
CB26A2DB29FC1DCF001EF861 /* GoogleSearch+CoreDataClass.swift */,
|
||||
CB26A2DC29FC1DCF001EF861 /* GoogleSearch+CoreDataProperties.swift */,
|
||||
CB26A2DD29FC1DCF001EF861 /* GoogleSearchResult+CoreDataClass.swift */,
|
||||
CB26A2DE29FC1DCF001EF861 /* GoogleSearchResult+CoreDataProperties.swift */,
|
||||
CB2A943729F830E500D3A048 /* Conversation+CoreDataClass.swift */,
|
||||
CB2A943829F830E500D3A048 /* Conversation+CoreDataProperties.swift */,
|
||||
CB2D438729F0183A007742AE /* ChatGPT+CoreDataClass.swift */,
|
||||
CB2D438829F0183A007742AE /* ChatGPT+CoreDataProperties.swift */,
|
||||
CBFBD77B2A5E67040090454B /* Conversation+CoreDataClass.swift */,
|
||||
CBFBD77C2A5E67040090454B /* Conversation+CoreDataProperties.swift */,
|
||||
CB1F015D29EB9D05009CF942 /* Message+CoreDataClass.swift */,
|
||||
CB1F015E29EB9D05009CF942 /* Message+CoreDataProperties.swift */,
|
||||
CB1F015729EAFBF5009CF942 /* MessageText+CoreDataClass.swift */,
|
||||
@@ -383,7 +419,6 @@
|
||||
CB53A3BC29D48C8F00A5B8FC /* Prompt+CoreDataClass.swift */,
|
||||
CB53A3BD29D48C8F00A5B8FC /* Prompt+CoreDataProperties.swift */,
|
||||
182B43A329BF730300F06778 /* NSColor.swift */,
|
||||
CB55E0E92A3AAEA2006EBE2D /* PluginManifest.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -431,6 +466,7 @@
|
||||
CB0F5A5A29D057FF005B71D2 /* SplashMarkdown */,
|
||||
CB2A943129F828E500D3A048 /* SwiftSoup */,
|
||||
CB2AADE32A41AFC100747BEB /* SimpleToast */,
|
||||
CBFBD7592A4697450090454B /* Yams */,
|
||||
);
|
||||
productName = OSXChatGPT;
|
||||
productReference = CB1DCABE29B4F09D00B1D4E1 /* OSXChatGPT.app */;
|
||||
@@ -466,6 +502,7 @@
|
||||
CB0F5A5729D057FF005B71D2 /* XCRemoteSwiftPackageReference "Splash" */,
|
||||
CB2A943029F828E500D3A048 /* XCRemoteSwiftPackageReference "SwiftSoup" */,
|
||||
CB2AADE22A41AFC100747BEB /* XCRemoteSwiftPackageReference "SimpleToast" */,
|
||||
CBFBD7582A4697450090454B /* XCRemoteSwiftPackageReference "Yams" */,
|
||||
);
|
||||
productRefGroup = CB1DCABF29B4F09D00B1D4E1 /* Products */;
|
||||
projectDirPath = "";
|
||||
@@ -497,7 +534,6 @@
|
||||
files = (
|
||||
CB1F012F29E999EF009CF942 /* Segment.swift in Sources */,
|
||||
CB1F015C29EAFBF5009CF942 /* MessageText+CoreDataProperties.swift in Sources */,
|
||||
CB2A943929F830E500D3A048 /* Conversation+CoreDataClass.swift in Sources */,
|
||||
CB373A9B29F56CFF00B8D9BE /* Localization.swift in Sources */,
|
||||
CB1F015129E9BC8C009CF942 /* Tokenizer.swift in Sources */,
|
||||
CB2449FA29D7FE38006EE829 /* ServerManager.swift in Sources */,
|
||||
@@ -509,30 +545,37 @@
|
||||
CB2A944229F993A200D3A048 /* GoogleSearchSettingView.swift in Sources */,
|
||||
182B43A429BF730300F06778 /* NSColor.swift in Sources */,
|
||||
CB1F012D29E99982009CF942 /* Grammar.swift in Sources */,
|
||||
CBFBD77E2A5E67040090454B /* Conversation+CoreDataProperties.swift in Sources */,
|
||||
CBFBD7632A4931AD0090454B /* PluginManifestAuth+CoreDataClass.swift in Sources */,
|
||||
CB27656629D1DA9800897E0E /* AIPromptView.swift in Sources */,
|
||||
CB26A2E229FC1DCF001EF861 /* GoogleSearchResult+CoreDataProperties.swift in Sources */,
|
||||
CB1F014929E99B5E009CF942 /* Sequence+Occurrences.swift in Sources */,
|
||||
182B436B29BC5CBA00F06778 /* AppDelegate.swift in Sources */,
|
||||
CBFBD7782A57C5400090454B /* PluginManifest+CoreDataProperties.swift in Sources */,
|
||||
CB1F015B29EAFBF5009CF942 /* MessageText+CoreDataClass.swift in Sources */,
|
||||
182B437329BC5D1B00F06778 /* CoreDataManager.swift in Sources */,
|
||||
CB2A944029F973F800D3A048 /* GoogleSearchPopView.swift in Sources */,
|
||||
188FB46729C1FA9700E3C18F /* EidtSessionRemarkView.swift in Sources */,
|
||||
CB2D438929F0183A007742AE /* ChatGPT+CoreDataClass.swift in Sources */,
|
||||
CBFBD77A2A5D0DE70090454B /* PluginPopView.swift in Sources */,
|
||||
CBFBD7702A528D050090454B /* SettingsView.swift in Sources */,
|
||||
CB1F014229E99B5E009CF942 /* CharacterSet+Contains.swift in Sources */,
|
||||
CB1F014629E99B5E009CF942 /* String+IsNumber.swift in Sources */,
|
||||
CB2A943A29F830E500D3A048 /* Conversation+CoreDataProperties.swift in Sources */,
|
||||
182B436629BC5C8700F06778 /* View.swift in Sources */,
|
||||
CB55E0EA2A3AAEA2006EBE2D /* PluginManifest.swift in Sources */,
|
||||
CBFBD7752A57C5400090454B /* PluginAPIInstall+CoreDataClass.swift in Sources */,
|
||||
CB1F012929E995BA009CF942 /* MarkdownTextBuilder.swift in Sources */,
|
||||
CB1F015F29EB9D05009CF942 /* Message+CoreDataClass.swift in Sources */,
|
||||
182B437B29BC5FBE00F06778 /* EnterAPIView.swift in Sources */,
|
||||
CB2AADE62A41E15600747BEB /* PluginContentView.swift in Sources */,
|
||||
CB0F5A5F29D059C4005B71D2 /* TextOutputFormat.swift in Sources */,
|
||||
182B437929BC5D6200F06778 /* OSXChatGPTApp.swift in Sources */,
|
||||
CBD5AB6929E707A1007B6625 /* MarkdownTheme.swift in Sources */,
|
||||
CBFBD7762A57C5400090454B /* PluginAPIInstall+CoreDataProperties.swift in Sources */,
|
||||
CB28A52229C07BE500F0286A /* KeyboardMonitor.swift in Sources */,
|
||||
CB1F014A29E99B5E009CF942 /* Sequence+AnyOf.swift in Sources */,
|
||||
182B437429BC5D1B00F06778 /* ViewModel.swift in Sources */,
|
||||
CB1F016029EB9D05009CF942 /* Message+CoreDataProperties.swift in Sources */,
|
||||
CBFBD7662A4931AD0090454B /* PluginManifestAPI+CoreDataProperties.swift in Sources */,
|
||||
CB1F014329E99B5E009CF942 /* Substring+HasSuffix.swift in Sources */,
|
||||
CB53A3BE29D48C8F00A5B8FC /* Prompt+CoreDataClass.swift in Sources */,
|
||||
182B436729BC5C8700F06778 /* ChatRoomView.swift in Sources */,
|
||||
@@ -541,6 +584,7 @@
|
||||
CB1F014729E99B5E009CF942 /* String+HTMLEntities.swift in Sources */,
|
||||
CB0F5A6029D059C4005B71D2 /* SplashCodeSyntaxHighlighter.swift in Sources */,
|
||||
CB1F014D29E9A4CC009CF942 /* MessageTextModel.swift in Sources */,
|
||||
CBFBD7652A4931AD0090454B /* PluginManifestAPI+CoreDataClass.swift in Sources */,
|
||||
182B436529BC5C8700F06778 /* SessionsView.swift in Sources */,
|
||||
CB1F014429E99B5E009CF942 /* String+PrefixChecking.swift in Sources */,
|
||||
CB27657529D33D7A00897E0E /* AIPromptInputView.swift in Sources */,
|
||||
@@ -548,15 +592,19 @@
|
||||
182B437229BC5D1B00F06778 /* HTTPClient.swift in Sources */,
|
||||
CB26A2DF29FC1DCF001EF861 /* GoogleSearch+CoreDataClass.swift in Sources */,
|
||||
CB53A3BF29D48C8F00A5B8FC /* Prompt+CoreDataProperties.swift in Sources */,
|
||||
CBFBD7642A4931AD0090454B /* PluginManifestAuth+CoreDataProperties.swift in Sources */,
|
||||
CB1F012729E9832F009CF942 /* MarkdownTextAttributesReader.swift in Sources */,
|
||||
CB1F012C29E99982009CF942 /* SwiftGrammar.swift in Sources */,
|
||||
CBD5AB6429E6DE9A007B6625 /* ProjectSettingManager.swift in Sources */,
|
||||
182B436829BC5C8700F06778 /* MainContentView.swift in Sources */,
|
||||
CB28A52829C1569900F0286A /* ThinkingAnimationView.swift in Sources */,
|
||||
CB2AADE82A42786F00747BEB /* PluginViewModel.swift in Sources */,
|
||||
CBD5AB6629E6EFE3007B6625 /* MarkdownView.swift in Sources */,
|
||||
CBD5AB6B29E707F0007B6625 /* MarkdownTokenType.swift in Sources */,
|
||||
CBFBD77D2A5E67040090454B /* Conversation+CoreDataClass.swift in Sources */,
|
||||
CB26A2E129FC1DCF001EF861 /* GoogleSearchResult+CoreDataClass.swift in Sources */,
|
||||
182B437529BC5D1B00F06778 /* ChatGPTManager.swift in Sources */,
|
||||
CBFBD7772A57C5400090454B /* PluginManifest+CoreDataClass.swift in Sources */,
|
||||
CB2D438A29F0183A007742AE /* ChatGPT+CoreDataProperties.swift in Sources */,
|
||||
CB2F972229CED6AE004EBD96 /* ChatRoomInputView.swift in Sources */,
|
||||
CB1F014529E99B5E009CF942 /* String+Removing.swift in Sources */,
|
||||
@@ -818,6 +866,14 @@
|
||||
minimumVersion = 2.0.0;
|
||||
};
|
||||
};
|
||||
CBFBD7582A4697450090454B /* XCRemoteSwiftPackageReference "Yams" */ = {
|
||||
isa = XCRemoteSwiftPackageReference;
|
||||
repositoryURL = "https://github.com/jpsim/Yams.git";
|
||||
requirement = {
|
||||
kind = upToNextMajorVersion;
|
||||
minimumVersion = 5.0.0;
|
||||
};
|
||||
};
|
||||
/* End XCRemoteSwiftPackageReference section */
|
||||
|
||||
/* Begin XCSwiftPackageProductDependency section */
|
||||
@@ -851,6 +907,11 @@
|
||||
package = CB2F971D29CE12B6004EBD96 /* XCRemoteSwiftPackageReference "swift-markdown-ui" */;
|
||||
productName = MarkdownUI;
|
||||
};
|
||||
CBFBD7592A4697450090454B /* Yams */ = {
|
||||
isa = XCSwiftPackageProductDependency;
|
||||
package = CBFBD7582A4697450090454B /* XCRemoteSwiftPackageReference "Yams" */;
|
||||
productName = Yams;
|
||||
};
|
||||
/* End XCSwiftPackageProductDependency section */
|
||||
|
||||
/* Begin XCVersionGroup section */
|
||||
|
||||
@@ -44,6 +44,15 @@
|
||||
"revision" : "f707b8680cddb96dc1855632340a572ef37bbb98",
|
||||
"version" : "2.5.3"
|
||||
}
|
||||
},
|
||||
{
|
||||
"identity" : "yams",
|
||||
"kind" : "remoteSourceControl",
|
||||
"location" : "https://github.com/jpsim/Yams.git",
|
||||
"state" : {
|
||||
"revision" : "0d9ee7ea8c4ebd4a489ad7a73d5c6cad55d6fed3",
|
||||
"version" : "5.0.6"
|
||||
}
|
||||
}
|
||||
],
|
||||
"version" : 2
|
||||
|
||||
@@ -50,6 +50,18 @@
|
||||
ReferencedContainer = "container:OSXChatGPT.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<EnvironmentVariables>
|
||||
<EnvironmentVariable
|
||||
key = "PYTHON_LIBRARY"
|
||||
value = "/Library/Frameworks/Python.framework/Versions/3.11/Python"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
<EnvironmentVariable
|
||||
key = "PYTHON_LOADER_LOAD"
|
||||
value = "TRUE"
|
||||
isEnabled = "YES">
|
||||
</EnvironmentVariable>
|
||||
</EnvironmentVariables>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
|
||||
@@ -489,6 +489,104 @@ extension ChatGPTManager {
|
||||
|
||||
}
|
||||
|
||||
// MARK: - plugin
|
||||
extension ChatGPTManager {
|
||||
func createFunctions(jsonArray: [[String: Any]]) {
|
||||
var functions: [[String: Any]] = []
|
||||
jsonArray.forEach { json in
|
||||
var function: [String: Any] = [:]
|
||||
if let components = json["components"] as? [String: Any] {
|
||||
let schemas = components["schemas"] as? [String: Any]
|
||||
if let keys = schemas?.keys.forEach({ key in
|
||||
|
||||
}) {
|
||||
print("")
|
||||
}
|
||||
print(schemas)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//
|
||||
//visitWebPageError:
|
||||
// type: object
|
||||
// properties:
|
||||
// code:
|
||||
// type: string
|
||||
// description: error code
|
||||
// message:
|
||||
// type: string
|
||||
// description: error message
|
||||
// detail:
|
||||
// type: string
|
||||
// description: error detail
|
||||
|
||||
//functions = [
|
||||
// {
|
||||
// "name": "get_current_weather",
|
||||
// "description": "Get the current weather in a given location",
|
||||
// "parameters": {
|
||||
// "type": "object",
|
||||
// "properties": {
|
||||
// "location": {
|
||||
// "type": "string",
|
||||
// "description": "The city and state, e.g. San Francisco, CA",
|
||||
// },
|
||||
// "unit": {"type": "string", "enum": ["celsius", "fahrenheit"]},
|
||||
// },
|
||||
// "required": ["location"],
|
||||
// },
|
||||
// }
|
||||
// ]
|
||||
|
||||
//- key : "visitWebPageError"
|
||||
//▿ value : 2 elements
|
||||
// ▿ 0 : 2 elements
|
||||
// ▿ key : AnyHashable("properties")
|
||||
// - value : "properties"
|
||||
// ▿ value : 3 elements
|
||||
// ▿ 0 : 2 elements
|
||||
// ▿ key : AnyHashable("message")
|
||||
// - value : "message"
|
||||
// ▿ value : 2 elements
|
||||
// ▿ 0 : 2 elements
|
||||
// ▿ key : AnyHashable("type")
|
||||
// - value : "type"
|
||||
// - value : "string"
|
||||
// ▿ 1 : 2 elements
|
||||
// ▿ key : AnyHashable("description")
|
||||
// - value : "description"
|
||||
// - value : "error message"
|
||||
// ▿ 1 : 2 elements
|
||||
// ▿ key : AnyHashable("code")
|
||||
// - value : "code"
|
||||
// ▿ value : 2 elements
|
||||
// ▿ 0 : 2 elements
|
||||
// ▿ key : AnyHashable("type")
|
||||
// - value : "type"
|
||||
// - value : "string"
|
||||
// ▿ 1 : 2 elements
|
||||
// ▿ key : AnyHashable("description")
|
||||
// - value : "description"
|
||||
// - value : "error code"
|
||||
// ▿ 2 : 2 elements
|
||||
// ▿ key : AnyHashable("detail")
|
||||
// - value : "detail"
|
||||
// ▿ value : 2 elements
|
||||
// ▿ 0 : 2 elements
|
||||
// ▿ key : AnyHashable("type")
|
||||
// - value : "type"
|
||||
// - value : "string"
|
||||
// ▿ 1 : 2 elements
|
||||
// ▿ key : AnyHashable("description")
|
||||
// - value : "description"
|
||||
// - value : "error detail"
|
||||
// ▿ 1 : 2 elements
|
||||
// ▿ key : AnyHashable("type")
|
||||
// - value : "type"
|
||||
// - value : "object"
|
||||
|
||||
// MARK: - search
|
||||
extension ChatGPTManager {
|
||||
func search(search: GoogleSearch?, callback:@escaping (_ searchResult: GoogleSearchResponse?, _ err: String?) -> Void) {
|
||||
|
||||
180
OSXChatGPT/OSXChatGPT/DataProvider/PluginViewModel.swift
Normal file
180
OSXChatGPT/OSXChatGPT/DataProvider/PluginViewModel.swift
Normal file
@@ -0,0 +1,180 @@
|
||||
//
|
||||
// PluginViewModel.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/6/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Yams
|
||||
import CoreData
|
||||
|
||||
|
||||
class PluginViewModel: ObservableObject {
|
||||
|
||||
@Published var pluginList: [PluginManifest] = []
|
||||
|
||||
|
||||
init() {
|
||||
fetchAllPlugin()
|
||||
}
|
||||
/// 获取所有插件
|
||||
func fetchAllPlugin() {
|
||||
var aa: [PluginManifest] = CoreDataManager.shared.fetch("PluginManifest", sorting: nil)
|
||||
let remove = aa.filter { $0.name_for_human == nil }
|
||||
if remove.count > 0 {
|
||||
CoreDataManager.shared.delete(objects: remove)
|
||||
aa.removeAll(where: { $0.name_for_human == nil })
|
||||
CoreDataManager.shared.saveData()
|
||||
}
|
||||
pluginList = aa
|
||||
}
|
||||
|
||||
static func fetchAllInstallPlugins(_ select:[PluginAPIInstall]) -> [PluginSelectViewModel] {
|
||||
|
||||
let request: NSFetchRequest<PluginManifest> = PluginManifest.fetchRequest()
|
||||
request.predicate = NSPredicate(format: "install != NULL")
|
||||
let results: [PluginManifest] = CoreDataManager.shared.fetch(request: request)
|
||||
var temp: [PluginSelectViewModel] = []
|
||||
results.forEach { manifest in
|
||||
if let insta = manifest.install {
|
||||
let model = PluginSelectViewModel(plugin: insta)
|
||||
if select.contains(where: { $0.name_for_human == insta.name_for_human}) {
|
||||
model.isMySelect = true
|
||||
}
|
||||
temp.append(model)
|
||||
}
|
||||
}
|
||||
|
||||
return temp
|
||||
}
|
||||
|
||||
/// 安装插件
|
||||
func InstallPlugin(manifest: PluginManifest, progress:@escaping ((Progress) -> Void), complete:@escaping ((String?) -> Void)) {
|
||||
guard let api = manifest.api?.url else {
|
||||
complete("api URL Error")
|
||||
return
|
||||
}
|
||||
HTTPClient.shared.downloadData(urlStr: api, progress: progress) { resUrl, err in
|
||||
if let url = resUrl {
|
||||
do {
|
||||
let data = try Data(contentsOf: url)
|
||||
if let content = String(data: data, encoding: .utf8),
|
||||
let json = try Yams.load(yaml: content) as? [String: Any] {
|
||||
self.installPluginToCoreData(manifest: manifest, jsonString: content, json: json)
|
||||
complete(nil)
|
||||
print(json)
|
||||
}else {
|
||||
complete("Install Error")
|
||||
}
|
||||
} catch {
|
||||
print("读取文件失败: \(error)")
|
||||
complete("Install Error")
|
||||
}
|
||||
}else {
|
||||
complete("Install Error")
|
||||
}
|
||||
// print("downloadData reUrl:\(resUrl) \n err:\(err)")
|
||||
}
|
||||
}
|
||||
func uninstall(manifest: PluginManifest) -> String? {
|
||||
if let plugin = fetchManifest(name_for_human: manifest.name_for_human) {
|
||||
if let install = plugin.install {
|
||||
CoreDataManager.shared.delete(object: install)
|
||||
}
|
||||
plugin.install = nil
|
||||
if let index = pluginList.firstIndex(where: { $0.name_for_human == plugin.name_for_human }) {
|
||||
pluginList[index] = plugin
|
||||
}
|
||||
CoreDataManager.shared.saveData()
|
||||
return nil
|
||||
}else {
|
||||
return "uninstall Error"
|
||||
}
|
||||
}
|
||||
private func installPluginToCoreData(manifest: PluginManifest, jsonString: String, json: [String: Any]) {
|
||||
let install = PluginAPIInstall(context: CoreDataManager.shared.container.viewContext)
|
||||
if let plugin = fetchManifest(name_for_human: manifest.name_for_human) {
|
||||
install.schema_version = manifest.schema_version
|
||||
install.apiJsonString = jsonString;
|
||||
install.logo_url = manifest.logo_url
|
||||
install.name_for_human = manifest.name_for_human
|
||||
install.description_for_human = manifest.description_for_human
|
||||
install.apiJson = json
|
||||
plugin.install = install
|
||||
if let index = pluginList.firstIndex(where: { $0.name_for_human == plugin.name_for_human }) {
|
||||
DispatchQueue.main.async {
|
||||
self.pluginList[index] = plugin
|
||||
}
|
||||
}
|
||||
CoreDataManager.shared.saveData()
|
||||
}
|
||||
|
||||
}
|
||||
func reloadAllManifestes() {
|
||||
HTTPClient.getManifests { datas, err in
|
||||
if let er = err {
|
||||
print("error:\(er)")
|
||||
return
|
||||
}
|
||||
if let jsons = datas as? [[String:Any]] {
|
||||
var temp : [PluginManifest] = []
|
||||
jsons.forEach { json in
|
||||
let name_for_human = json["name_for_human"] as? String
|
||||
var plugin = self.fetchPlugin(name_for_human: name_for_human ?? "")
|
||||
if plugin?.id == nil {
|
||||
plugin?.id = UUID()
|
||||
}
|
||||
if plugin == nil {
|
||||
plugin = PluginManifest(context: CoreDataManager.shared.container.viewContext)
|
||||
plugin?.id = UUID()
|
||||
plugin?.api = PluginManifestAPI(context: CoreDataManager.shared.container.viewContext)
|
||||
plugin?.auth = PluginManifestAuth(context: CoreDataManager.shared.container.viewContext)
|
||||
|
||||
}
|
||||
plugin?.setValue(json["schema_version"], forKey: "schema_version")
|
||||
plugin?.setValue(json["name_for_human"], forKey: "name_for_human")
|
||||
plugin?.setValue(json["name_for_model"], forKey: "name_for_model")
|
||||
plugin?.setValue(json["description_for_human"], forKey: "description_for_human")
|
||||
plugin?.setValue(json["description_for_model"], forKey: "description_for_model")
|
||||
plugin?.setValue(json["logo_url"], forKey: "logo_url")
|
||||
plugin?.setValue(json["contact_email"], forKey: "contact_email")
|
||||
plugin?.setValue(json["legal_info_url"], forKey: "legal_info_url")
|
||||
plugin?.api?.setValue((json["api"] as? [String: Any])?["type"], forKey: "type")
|
||||
plugin?.api?.setValue((json["api"] as? [String: Any])?["url"], forKey: "url")
|
||||
plugin?.api?.setValue((json["api"] as? [String: Any])?["has_user_authentication"], forKey: "has_user_authentication")
|
||||
plugin?.auth?.setValue((json["auth"] as? [String: Any])?["type"], forKey: "type")
|
||||
// print(plugin)
|
||||
if let plag = plugin {
|
||||
temp.append(plag)
|
||||
}
|
||||
}
|
||||
CoreDataManager.shared.saveData()
|
||||
DispatchQueue.main.async {
|
||||
self.pluginList = temp
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension PluginViewModel {
|
||||
|
||||
private func fetchPlugin(name_for_human: String) -> PluginManifest? {
|
||||
let request: NSFetchRequest<PluginManifest> = PluginManifest.fetchRequest()
|
||||
request.predicate = NSPredicate(format: "name_for_human == %@", name_for_human)
|
||||
let results: [PluginManifest] = CoreDataManager.shared.fetch(request: request)
|
||||
return results.first
|
||||
}
|
||||
private func fetchManifest(name_for_human: String?) -> PluginManifest? {
|
||||
guard let name = name_for_human, !name.isEmpty else { return nil }
|
||||
let request: NSFetchRequest<PluginManifest> = PluginManifest.fetchRequest()
|
||||
request.predicate = NSPredicate(format: "name_for_human == %@", name)
|
||||
let results: [PluginManifest] = CoreDataManager.shared.fetch(request: request)
|
||||
return results.first
|
||||
|
||||
}
|
||||
}
|
||||
@@ -10,6 +10,7 @@ import Foundation
|
||||
let getTokenUrl = "https://raw.githubusercontent.com/CoderLineChan/data/main/data/token.json"
|
||||
let githubUrl = "https://api.github.com/repos/CoderLineChan/OSXChatGptDataUpload/contents/data"
|
||||
let githubGetUrl = "https://api.github.com/repos/CoderLineChan/OSXChatGptData/contents/data/data.json"
|
||||
let githubPluginGetUrl = "https://api.github.com/repos/CoderLineChan/OSXChatGptData/contents/data/manifests.json"
|
||||
|
||||
class ServerManager {
|
||||
static let shared = ServerManager()
|
||||
@@ -35,17 +36,18 @@ class ServerManager {
|
||||
let da = NSData(base64Encoded: getDataBase64, options: NSData.Base64DecodingOptions.init(rawValue: 0)),
|
||||
let dataString = String(data: da as Data, encoding: .utf8) {
|
||||
self.getDataToken = dataString
|
||||
// print("获取getDataToken成功")
|
||||
// print("获取getDataToken成功:\(dataString)")
|
||||
|
||||
}else {
|
||||
// print("获取getDataToken失败")
|
||||
print("获取getDataToken失败")
|
||||
}
|
||||
if let uploadDataBase64 = json["uploadData"] as? String,
|
||||
let da = NSData(base64Encoded: uploadDataBase64, options: NSData.Base64DecodingOptions.init(rawValue: 0)),
|
||||
let dataString = String(data: da as Data, encoding: .utf8) {
|
||||
self.uploadDataToken = dataString
|
||||
// print("获取uploadDataToken成功")
|
||||
// print("获取uploadDataToken成功:\(dataString)")
|
||||
}else {
|
||||
// print("获取uploadDataToken失败")
|
||||
print("获取uploadDataToken失败")
|
||||
}
|
||||
self.loading = false
|
||||
if self.uploadDataToken.isEmpty || self.getDataToken.isEmpty {
|
||||
@@ -54,7 +56,7 @@ class ServerManager {
|
||||
}
|
||||
}
|
||||
}else {
|
||||
// print("获取Token失败")
|
||||
print("获取Token失败")
|
||||
self.loading = false
|
||||
DispatchQueue.global().asyncAfter(deadline: .now() + 3) {
|
||||
self.checkToken()
|
||||
|
||||
@@ -14,14 +14,18 @@ import SwiftSoup
|
||||
|
||||
|
||||
@MainActor class ViewModel: ObservableObject {
|
||||
|
||||
@Published var showDynamicBackground: Bool = ProjectSettingManager.shared.showDynamicBackground//动态背景
|
||||
@Published var conversations: [Conversation] = []//所有会话
|
||||
@Published var messages: [Message] = []//当前会话的消息
|
||||
@Published var showUserInitialize: Bool = false//显示设置页
|
||||
@Published var showEditRemark: Bool = false//显示编辑备注
|
||||
@Published var showAIPrompt: Bool = false //显示自定义提示
|
||||
@Published var showPluginView: Bool = false //显示插件
|
||||
var editConversation: Conversation?//编辑备注的会话
|
||||
@Published var createNewChat: Bool = false//创建新会话
|
||||
@Published var currentPlugins: [PluginAPIInstall] = []//当前会话的插件
|
||||
@Published var isDisabledPlugin: Bool = true
|
||||
@Published var changeMsgText: String = ""
|
||||
@State var scrollID = UUID()
|
||||
var currentConversation: Conversation?//当前会话
|
||||
@@ -650,6 +654,36 @@ extension ViewModel {
|
||||
}
|
||||
|
||||
}
|
||||
// MARK: - plugin
|
||||
extension ViewModel {
|
||||
func refreshCurrentPlugins() {
|
||||
if let plugins = currentConversation?.plugin?.array as? [PluginAPIInstall] {
|
||||
currentPlugins = plugins
|
||||
}else {
|
||||
currentPlugins = []
|
||||
}
|
||||
|
||||
if let con = currentConversation, con.lastMessage != nil {
|
||||
isDisabledPlugin = true
|
||||
}else {
|
||||
isDisabledPlugin = false
|
||||
}
|
||||
}
|
||||
func updateCurrentPlugins(plugins: [PluginAPIInstall]) {
|
||||
if let plugin = currentConversation?.plugin {
|
||||
currentConversation?.removeFromPlugin(plugin)
|
||||
}
|
||||
var arr: [[String: Any]] = []
|
||||
plugins.forEach { install in
|
||||
currentConversation?.addToPlugin(install)
|
||||
if let json = install.apiJson {
|
||||
arr.append(json)
|
||||
}
|
||||
}
|
||||
ChatGPTManager.shared.createFunctions(jsonArray: arr)
|
||||
CoreDataManager.shared.saveData()
|
||||
}
|
||||
}
|
||||
extension ViewModel {
|
||||
func getChatRoomView(conversation: Conversation?) -> ChatRoomView {
|
||||
if conversation == nil || conversation!.sesstionId == "" {
|
||||
|
||||
@@ -11,7 +11,7 @@ import Colorful
|
||||
struct OSXChatGPTApp: App {
|
||||
@NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
|
||||
@StateObject var viewModel = ViewModel()
|
||||
|
||||
@State private var showSettings = false
|
||||
var body: some Scene {
|
||||
WindowGroup {
|
||||
ZStack {
|
||||
@@ -20,8 +20,16 @@ struct OSXChatGPTApp: App {
|
||||
.ignoresSafeArea()
|
||||
}
|
||||
MainContentView().environmentObject(viewModel).edgesIgnoringSafeArea(.top).frame(minWidth: 900, maxWidth: .infinity, minHeight: 600, maxHeight: .infinity)
|
||||
.onAppear {
|
||||
if showSettings {
|
||||
openSettingsWindow()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
.windowToolbarStyle(.unified)
|
||||
.commands { SidebarCommands() }
|
||||
.commands { CommandGroup(replacing: CommandGroupPlacement.newItem) {} }
|
||||
@@ -36,6 +44,43 @@ struct OSXChatGPTApp: App {
|
||||
}
|
||||
}
|
||||
}
|
||||
// .commands {
|
||||
// CommandGroup(replacing: CommandGroupPlacement.appInfo) {
|
||||
//// Button("Custom Shortcut") {
|
||||
//// // 在这里添加自定义快捷键触发时的操作
|
||||
//// print("Custom Shortcut Triggered")
|
||||
//// NSApplication.shared.hide(nil)
|
||||
//// }
|
||||
//// .keyboardShortcut(KeyEquivalent("N"), modifiers: .command)
|
||||
// Button(Localization.Setting.localized) {
|
||||
//// showSettings.toggle()
|
||||
// openSettingsWindow()
|
||||
// print("Localization.Setting.localized")
|
||||
//
|
||||
// }
|
||||
// .keyboardShortcut(KeyEquivalent(","), modifiers: .command)
|
||||
// }
|
||||
// }
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
func openSettingsWindow() {
|
||||
let settingsWindow = NSWindow(
|
||||
contentRect: NSRect(x: 0, y: 0, width: 400, height: 300),
|
||||
styleMask: [.titled, .closable, .miniaturizable, .resizable],
|
||||
backing: .buffered,
|
||||
defer: false
|
||||
)
|
||||
|
||||
let settingsWindowController = NSWindowController(window: settingsWindow)
|
||||
settingsWindowController.contentViewController = NSHostingController(rootView: SettingsView())
|
||||
|
||||
settingsWindow.makeKeyAndOrderFront(nil)
|
||||
NSApp.activate(ignoringOtherApps: true)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Conversation+CoreDataClass.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/4/26.
|
||||
// Created by CoderChan on 2023/7/12.
|
||||
//
|
||||
//
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
// Conversation+CoreDataProperties.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/4/26.
|
||||
// Created by CoderChan on 2023/7/12.
|
||||
//
|
||||
//
|
||||
|
||||
@@ -24,6 +24,42 @@ extension Conversation {
|
||||
@NSManaged public var lastMessage: Message?
|
||||
@NSManaged public var prompt: Prompt?
|
||||
@NSManaged public var search: GoogleSearch?
|
||||
@NSManaged public var plugin: NSOrderedSet?
|
||||
|
||||
}
|
||||
|
||||
// MARK: Generated accessors for plugin
|
||||
extension Conversation {
|
||||
|
||||
@objc(insertObject:inPluginAtIndex:)
|
||||
@NSManaged public func insertIntoPlugin(_ value: PluginAPIInstall, at idx: Int)
|
||||
|
||||
@objc(removeObjectFromPluginAtIndex:)
|
||||
@NSManaged public func removeFromPlugin(at idx: Int)
|
||||
|
||||
@objc(insertPlugin:atIndexes:)
|
||||
@NSManaged public func insertIntoPlugin(_ values: [PluginAPIInstall], at indexes: NSIndexSet)
|
||||
|
||||
@objc(removePluginAtIndexes:)
|
||||
@NSManaged public func removeFromPlugin(at indexes: NSIndexSet)
|
||||
|
||||
@objc(replaceObjectInPluginAtIndex:withObject:)
|
||||
@NSManaged public func replacePlugin(at idx: Int, with value: PluginAPIInstall)
|
||||
|
||||
@objc(replacePluginAtIndexes:withPlugin:)
|
||||
@NSManaged public func replacePlugin(at indexes: NSIndexSet, with values: [PluginAPIInstall])
|
||||
|
||||
@objc(addPluginObject:)
|
||||
@NSManaged public func addToPlugin(_ value: PluginAPIInstall)
|
||||
|
||||
@objc(removePluginObject:)
|
||||
@NSManaged public func removeFromPlugin(_ value: PluginAPIInstall)
|
||||
|
||||
@objc(addPlugin:)
|
||||
@NSManaged public func addToPlugin(_ values: NSOrderedSet)
|
||||
|
||||
@objc(removePlugin:)
|
||||
@NSManaged public func removeFromPlugin(_ values: NSOrderedSet)
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
//
|
||||
// PluginAPIInstall+CoreDataClass.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/7/7.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
import Yams
|
||||
|
||||
|
||||
public class PluginAPIInstall: NSManagedObject {
|
||||
|
||||
lazy var apiJson: [String: Any]? = {
|
||||
if let content = apiJsonString {
|
||||
let json = try? Yams.load(yaml: content) as? [String: Any]
|
||||
return json
|
||||
}
|
||||
return nil
|
||||
}()
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
//
|
||||
// PluginAPIInstall+CoreDataProperties.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/7/7.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
extension PluginAPIInstall {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<PluginAPIInstall> {
|
||||
return NSFetchRequest<PluginAPIInstall>(entityName: "PluginAPIInstall")
|
||||
}
|
||||
|
||||
@NSManaged public var apiJsonString: String?
|
||||
@NSManaged public var schema_version: String?
|
||||
@NSManaged public var logo_url: String?
|
||||
@NSManaged public var name_for_human: String?
|
||||
@NSManaged public var description_for_human: String?
|
||||
}
|
||||
|
||||
extension PluginAPIInstall : Identifiable {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// PluginManifest+CoreDataClass.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/7/7.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
public class PluginManifest: NSManagedObject {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
//
|
||||
// PluginManifest+CoreDataProperties.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/7/7.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
extension PluginManifest {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<PluginManifest> {
|
||||
return NSFetchRequest<PluginManifest>(entityName: "PluginManifest")
|
||||
}
|
||||
@NSManaged public var id: UUID?
|
||||
@NSManaged public var schema_version: String?
|
||||
@NSManaged public var name_for_human: String?
|
||||
@NSManaged public var name_for_model: String?
|
||||
@NSManaged public var description_for_human: String?
|
||||
@NSManaged public var description_for_model: String?
|
||||
@NSManaged public var logo_url: String?
|
||||
@NSManaged public var contact_email: String?
|
||||
@NSManaged public var legal_info_url: String?
|
||||
@NSManaged public var api: PluginManifestAPI?
|
||||
@NSManaged public var auth: PluginManifestAuth?
|
||||
@NSManaged public var install: PluginAPIInstall?
|
||||
|
||||
}
|
||||
|
||||
extension PluginManifest : Identifiable {
|
||||
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
//
|
||||
// PluginManifest.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/6/15.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
|
||||
struct PluginManifest {
|
||||
var schema_version: String = ""
|
||||
var name_for_human: String = ""
|
||||
var name_for_model: String = ""
|
||||
var description_for_human: String = ""
|
||||
var description_for_model: String = ""
|
||||
var auth: PluginManifestAuth = PluginManifestAuth()
|
||||
var api: PluginManifestAPI = PluginManifestAPI()
|
||||
var logo_url: String = ""
|
||||
var contact_email: String = ""
|
||||
var legal_info_url: String = ""
|
||||
}
|
||||
|
||||
|
||||
struct PluginManifestAuth {
|
||||
var type: String = ""
|
||||
}
|
||||
|
||||
struct PluginManifestAPI {
|
||||
var type: String = ""
|
||||
var url: String = ""
|
||||
var has_user_authentication: Bool = false
|
||||
}
|
||||
|
||||
// "auth": {
|
||||
// "type": "oauth",
|
||||
// "authorization_content_type": "application/x-www-form-urlencoded",
|
||||
// "client_url": "https://auth.buildbetter.app/realms/buildbetter/protocol/openid-connect/auth",
|
||||
// "authorization_url": "https://auth.buildbetter.app/realms/buildbetter/protocol/openid-connect/token",
|
||||
// "scope": "email profile",
|
||||
// "verification_tokens": {
|
||||
// "openai": "28e7c6a759b94f4cac07cd4693433997"
|
||||
// }
|
||||
// },
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// PluginManifestAPI+CoreDataClass.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/6/26.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
public class PluginManifestAPI: NSManagedObject {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
//
|
||||
// PluginManifestAPI+CoreDataProperties.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/6/26.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
extension PluginManifestAPI {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<PluginManifestAPI> {
|
||||
return NSFetchRequest<PluginManifestAPI>(entityName: "PluginManifestAPI")
|
||||
}
|
||||
|
||||
@NSManaged public var type: String?
|
||||
@NSManaged public var url: String?
|
||||
@NSManaged public var has_user_authentication: Bool
|
||||
|
||||
}
|
||||
|
||||
extension PluginManifestAPI : Identifiable {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
//
|
||||
// PluginManifestAuth+CoreDataClass.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/6/26.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
public class PluginManifestAuth: NSManagedObject {
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
//
|
||||
// PluginManifestAuth+CoreDataProperties.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/6/26.
|
||||
//
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import CoreData
|
||||
|
||||
|
||||
extension PluginManifestAuth {
|
||||
|
||||
@nonobjc public class func fetchRequest() -> NSFetchRequest<PluginManifestAuth> {
|
||||
return NSFetchRequest<PluginManifestAuth>(entityName: "PluginManifestAuth")
|
||||
}
|
||||
|
||||
@NSManaged public var type: String?
|
||||
|
||||
}
|
||||
|
||||
extension PluginManifestAuth : Identifiable {
|
||||
|
||||
}
|
||||
@@ -52,22 +52,32 @@ struct GoogleSearchItem: Decodable {
|
||||
}
|
||||
|
||||
|
||||
class HTTPClient {
|
||||
class HTTPClient: NSObject {
|
||||
static let shared = HTTPClient()
|
||||
fileprivate var urlSession: URLSession!
|
||||
fileprivate var sessionConfiguration: URLSessionConfiguration!
|
||||
private var isCancelStreamRequest: Bool = false
|
||||
private var currentTask: URLSessionTask?
|
||||
private var downloadProgress: ((Progress) -> Void)?
|
||||
private var downloadComplete: ((URL?, Error?) -> Void)?
|
||||
private let jsonDecoder: JSONDecoder = {
|
||||
let jsonDecoder = JSONDecoder()
|
||||
jsonDecoder.keyDecodingStrategy = .convertFromSnakeCase
|
||||
return jsonDecoder
|
||||
}()
|
||||
init() {
|
||||
override init() {
|
||||
sessionConfiguration = URLSessionConfiguration.default
|
||||
urlSession = URLSession(configuration: sessionConfiguration)
|
||||
}
|
||||
|
||||
override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
|
||||
if keyPath == "fractionCompleted", let progress = object as? Progress {
|
||||
DispatchQueue.main.async {
|
||||
self.updateProgress(progress)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func setAdditionalHeaders(_ headers: Dictionary<String, AnyObject>) {
|
||||
sessionConfiguration.httpAdditionalHeaders = headers
|
||||
}
|
||||
@@ -398,6 +408,70 @@ extension HTTPClient {
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
// MARK: - 下载
|
||||
extension HTTPClient {
|
||||
func downloadData(urlStr: String, progress:@escaping ((Progress) -> Void), complete:@escaping ((URL?, Error?) -> Void)) {
|
||||
guard let url = URL(string: urlStr) else {
|
||||
complete(nil, nil)
|
||||
return
|
||||
}
|
||||
downloadProgress = progress
|
||||
let task = urlSession.downloadTask(with: URLRequest(url: url)) { reUrl, rep, err in
|
||||
complete(reUrl, err)
|
||||
}
|
||||
task.resume()
|
||||
task.progress.addObserver(self, forKeyPath: "fractionCompleted", options: .new, context: nil)
|
||||
}
|
||||
|
||||
func updateProgress(_ progress: Progress) {
|
||||
downloadProgress?(progress)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - plugin manifests
|
||||
extension HTTPClient {
|
||||
static func getManifests(callback:@escaping (_ datas: [Any], _ err: String?) -> Void) {
|
||||
let url = URL(string: githubPluginGetUrl)!
|
||||
var request = URLRequest(url: url)
|
||||
let authorizationValue = "Bearer \(ServerManager.shared.getDataToken)"
|
||||
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
|
||||
request.setValue(authorizationValue, forHTTPHeaderField: "Authorization")
|
||||
request.httpMethod = "GET"
|
||||
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
|
||||
if let error = error {
|
||||
print("error: \(error.localizedDescription)")
|
||||
callback([], error.localizedDescription)
|
||||
return
|
||||
}
|
||||
var code = 0
|
||||
if let response = response as? HTTPURLResponse {
|
||||
code = response.statusCode
|
||||
}
|
||||
if let data = data,
|
||||
let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any] {
|
||||
if let base64Str = json["content"] as? String {
|
||||
let base64String = base64Str.replacingOccurrences(of: "\n", with: "")
|
||||
if let da = NSData(base64Encoded: base64String, options: NSData.Base64DecodingOptions.init(rawValue: 0)),
|
||||
let dataString = String(data: da as Data, encoding: .utf8),
|
||||
let jsonData = dataString.data(using: .utf8),
|
||||
let jsonObject = try? JSONSerialization.jsonObject(with: jsonData, options: []) as? [Any] {
|
||||
DispatchQueue.main.async {
|
||||
callback(jsonObject, nil)
|
||||
}
|
||||
}else {
|
||||
callback([], "data error code:\(code)")
|
||||
}
|
||||
}else {
|
||||
//获取不到数据,需要更新token
|
||||
callback([], "data error code:\(code)")
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
task.resume()
|
||||
}
|
||||
}
|
||||
|
||||
struct HTTPResponse1: Decodable {
|
||||
let json: HTTPResponse2
|
||||
|
||||
@@ -4,6 +4,8 @@
|
||||
<dict>
|
||||
<key>com.apple.security.app-sandbox</key>
|
||||
<true/>
|
||||
<key>com.apple.security.cs.disable-library-validation</key>
|
||||
<true/>
|
||||
<key>com.apple.security.files.user-selected.read-write</key>
|
||||
<true/>
|
||||
<key>com.apple.security.network.client</key>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
<attribute name="updateData" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
|
||||
<relationship name="chatGPT" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ChatGPT"/>
|
||||
<relationship name="lastMessage" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Message"/>
|
||||
<relationship name="plugin" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="PluginAPIInstall"/>
|
||||
<relationship name="prompt" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="Prompt" inverseName="sesstion" inverseEntity="Prompt"/>
|
||||
<relationship name="search" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="GoogleSearch"/>
|
||||
</entity>
|
||||
@@ -48,6 +49,35 @@
|
||||
<attribute name="text" optional="YES" attributeType="String"/>
|
||||
<attribute name="type" optional="YES" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
</entity>
|
||||
<entity name="PluginAPIInstall" representedClassName=".PluginAPIInstall" syncable="YES">
|
||||
<attribute name="apiJsonString" attributeType="String"/>
|
||||
<attribute name="description_for_human" optional="YES" attributeType="String"/>
|
||||
<attribute name="logo_url" optional="YES" attributeType="String"/>
|
||||
<attribute name="name_for_human" optional="YES" attributeType="String"/>
|
||||
<attribute name="schema_version" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="PluginManifest" representedClassName=".PluginManifest" syncable="YES">
|
||||
<attribute name="contact_email" optional="YES" attributeType="String"/>
|
||||
<attribute name="description_for_human" optional="YES" attributeType="String"/>
|
||||
<attribute name="description_for_model" optional="YES" attributeType="String"/>
|
||||
<attribute name="id" optional="YES" attributeType="UUID" usesScalarValueType="NO"/>
|
||||
<attribute name="legal_info_url" optional="YES" attributeType="String"/>
|
||||
<attribute name="logo_url" optional="YES" attributeType="String"/>
|
||||
<attribute name="name_for_human" optional="YES" attributeType="String"/>
|
||||
<attribute name="name_for_model" optional="YES" attributeType="String"/>
|
||||
<attribute name="schema_version" optional="YES" attributeType="String"/>
|
||||
<relationship name="api" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PluginManifestAPI"/>
|
||||
<relationship name="auth" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PluginManifestAuth"/>
|
||||
<relationship name="install" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="PluginAPIInstall"/>
|
||||
</entity>
|
||||
<entity name="PluginManifestAPI" representedClassName=".PluginManifestAPI" syncable="YES">
|
||||
<attribute name="has_user_authentication" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
|
||||
<attribute name="type" optional="YES" attributeType="String"/>
|
||||
<attribute name="url" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="PluginManifestAuth" representedClassName=".PluginManifestAuth" syncable="YES">
|
||||
<attribute name="type" optional="YES" attributeType="String"/>
|
||||
</entity>
|
||||
<entity name="Prompt" representedClassName=".Prompt" syncable="YES">
|
||||
<attribute name="author" optional="YES" attributeType="String"/>
|
||||
<attribute name="cloudId" optional="YES" attributeType="Integer 32" defaultValueString="0" usesScalarValueType="YES"/>
|
||||
|
||||
@@ -18,6 +18,7 @@ struct ChatRoomToolBar: View {
|
||||
@State private var model: String = ""
|
||||
@State private var context: String = ""
|
||||
@State private var isOn: Bool = false
|
||||
@State var showPluginView: Bool = false //显示插件
|
||||
@EnvironmentObject var viewModel: ViewModel
|
||||
|
||||
@State private var isAnswerTypeTrue = ChatGPTManager.shared.answerType.valueBool
|
||||
@@ -80,21 +81,24 @@ struct ChatRoomToolBar: View {
|
||||
viewModel.updateConversation(sesstionId: viewModel.currentConversation?.sesstionId ?? "", message: nil)
|
||||
}
|
||||
|
||||
Button {
|
||||
showSearchView.toggle()
|
||||
} label: {
|
||||
Text("谷歌搜索")
|
||||
}
|
||||
// Button {
|
||||
// showSearchView.toggle()
|
||||
// } label: {
|
||||
// Text("谷歌搜索")
|
||||
// }
|
||||
// .popover(isPresented: $showSearchSettingView) {
|
||||
// GoogleSearchSettingView().environmentObject(viewModel)
|
||||
// }
|
||||
// .popover(isPresented: $showSearchView) {
|
||||
// GoogleSearchPopView(showSearchView: $showSearchView, showSearchSettingView: $showSearchSettingView).environmentObject(viewModel)
|
||||
// }
|
||||
PluginToolView(showPluginView: $showPluginView, isDisabledPlugin: $viewModel.isDisabledPlugin)
|
||||
.sheet(isPresented: $showPluginView, content: {
|
||||
PluginPopView(plugins: PluginViewModel.fetchAllInstallPlugins(viewModel.currentConversation?.plugin?.array as? [PluginAPIInstall] ?? [])).environmentObject(viewModel)
|
||||
})
|
||||
|
||||
|
||||
|
||||
.popover(isPresented: $showSearchView) {
|
||||
GoogleSearchPopView(showSearchView: $showSearchView, showSearchSettingView: $showSearchSettingView).environmentObject(viewModel)
|
||||
}
|
||||
|
||||
.popover(isPresented: $showSearchSettingView) {
|
||||
GoogleSearchSettingView().environmentObject(viewModel)
|
||||
}
|
||||
|
||||
Spacer()
|
||||
if viewModel.showStopAnswerBtn {
|
||||
|
||||
249
OSXChatGPT/OSXChatGPT/WindowView/PluginContentView.swift
Normal file
249
OSXChatGPT/OSXChatGPT/WindowView/PluginContentView.swift
Normal file
@@ -0,0 +1,249 @@
|
||||
//
|
||||
// PluginContentView.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/6/20.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleToast
|
||||
|
||||
struct PluginContentView: View {
|
||||
@EnvironmentObject var viewModel: ViewModel
|
||||
@StateObject var pluginViewModel = PluginViewModel()
|
||||
@State private var toastOptions: SimpleToastOptions = SimpleToastOptions(alignment: .center, hideAfter: 2.5, backdrop: nil, animation: nil, modifierType: .fade, dismissOnTap: true)
|
||||
@State private var isShowToast: Bool = false
|
||||
@State private var errorToast: String = ""
|
||||
@State private var isShowing: Bool = false
|
||||
var body: some View {
|
||||
VStack {
|
||||
GeometryReader { geometry in
|
||||
Spacer()
|
||||
HStack {
|
||||
Spacer()
|
||||
Button {
|
||||
pluginViewModel.reloadAllManifestes()
|
||||
} label: {
|
||||
Text("更新插件")
|
||||
}
|
||||
.padding()
|
||||
|
||||
}
|
||||
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||
.background(Color.white)
|
||||
|
||||
}.frame(height: 40)
|
||||
.padding(.top, 1)
|
||||
|
||||
ScrollView {
|
||||
LazyVGrid(columns: [GridItem(.adaptive(minimum: 180, maximum: 230), spacing: 16)], alignment: .leading, spacing: 16) {
|
||||
ForEach(pluginViewModel.pluginList) { item in
|
||||
ZStack {
|
||||
RoundedRectangle(cornerRadius: 10)
|
||||
.fill(.white)
|
||||
.frame(height: 165)
|
||||
PluginItemView(manifest: item, pluginViewModel: pluginViewModel, showToast: { err in
|
||||
self.isShowToast = false
|
||||
self.toastOptions = self.createNewToastOptions()
|
||||
self.errorToast = err
|
||||
self.isShowToast = true
|
||||
}).environmentObject(viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
Spacer()
|
||||
}
|
||||
.simpleToast(isPresented: $isShowToast, options: self.toastOptions, content: {
|
||||
VStack {
|
||||
Text(self.errorToast)
|
||||
.padding()
|
||||
.foregroundColor(.white)
|
||||
.background(.black.opacity(0.9))
|
||||
.cornerRadius(5)
|
||||
}
|
||||
.padding()
|
||||
|
||||
})
|
||||
|
||||
.onAppear {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
private func createNewToastOptions() -> SimpleToastOptions {
|
||||
return SimpleToastOptions(alignment: .center, hideAfter: 2.5, backdrop: nil, animation: nil, modifierType: .fade, dismissOnTap: true)
|
||||
}
|
||||
}
|
||||
|
||||
struct PluginItemView: View {
|
||||
let manifest: PluginManifest
|
||||
let pluginViewModel: PluginViewModel
|
||||
var showToast: (String) -> ()
|
||||
@EnvironmentObject var viewModel: ViewModel
|
||||
|
||||
@State private var isShowLoading: Bool = false
|
||||
@State private var downloadProgress: Double = 0.0
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
VStack(alignment: .leading) {
|
||||
HStack {
|
||||
AsyncImage(url: URL(string: manifest.logo_url ?? "")) { image in
|
||||
image.resizable()
|
||||
} placeholder: {
|
||||
ProgressView()
|
||||
}
|
||||
.frame(width: 50, height: 50)
|
||||
.cornerRadius(5)
|
||||
VStack(alignment: .leading) {
|
||||
Text(manifest.name_for_human ?? manifest.name_for_model ?? "NULL")
|
||||
.textSelection(.enabled)
|
||||
HStack {
|
||||
Button {
|
||||
if manifest.install == nil {
|
||||
installPlugin(manifest: manifest)
|
||||
}else if manifest.install?.schema_version != manifest.schema_version {
|
||||
installPlugin(manifest: manifest)
|
||||
} else {
|
||||
uninstall(manifest: manifest)
|
||||
}
|
||||
} label: {
|
||||
HStack {
|
||||
if manifest.install == nil {
|
||||
Text("安装")
|
||||
.foregroundColor(.white)
|
||||
if isShowLoading {
|
||||
PluginDownloadProgressView(circleBackdropColor: .white.opacity(0.5), circleForegroundColor: .white, progress: $downloadProgress)
|
||||
.frame(width: 15, height: 15)
|
||||
}else {
|
||||
Image(systemName: "arrow.down.circle.fill")
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
}else if manifest.install?.schema_version != manifest.schema_version {
|
||||
Text("更新")
|
||||
.foregroundColor(.white)
|
||||
Image(systemName: "arrow.clockwise.circle.fill")
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 15, height: 15)
|
||||
} else {
|
||||
Text("卸载")
|
||||
.foregroundColor(.white)
|
||||
Image(systemName: "xmark.bin.circle.fill")
|
||||
.foregroundColor(.white)
|
||||
.frame(width: 15, height: 15)
|
||||
}
|
||||
}
|
||||
.padding(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0))
|
||||
.frame(width: 80, height: 30)
|
||||
.background((manifest.install == nil || manifest.install?.schema_version != manifest.schema_version) ? .blue : .red)
|
||||
}.buttonStyle(PlainButtonStyle())
|
||||
.frame(width: 80, height: 30)
|
||||
.cornerRadius(5)
|
||||
}
|
||||
}
|
||||
Spacer()
|
||||
}
|
||||
.padding(.leading, 16)
|
||||
.padding(.trailing, 10)
|
||||
.padding(.top, 10)
|
||||
Spacer()
|
||||
}
|
||||
VStack {
|
||||
Text(manifest.description_for_human ?? manifest.description_for_model ?? "")
|
||||
.padding(EdgeInsets(top: 70, leading: 12, bottom: 5, trailing: 12))
|
||||
.textSelection(.enabled)
|
||||
}
|
||||
}
|
||||
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||
}
|
||||
}
|
||||
|
||||
func installPlugin(manifest: PluginManifest) {
|
||||
if isShowLoading == true {
|
||||
return
|
||||
}
|
||||
downloadProgress = 0
|
||||
isShowLoading = true
|
||||
pluginViewModel.InstallPlugin(manifest: manifest) { pro in
|
||||
self.downloadProgress = Double(pro.completedUnitCount / pro.totalUnitCount)
|
||||
print("Progress:\(pro.totalUnitCount), \(pro.completedUnitCount)")
|
||||
} complete: { err in
|
||||
if let error = err {
|
||||
self.installPluginError(err: error)
|
||||
}else {
|
||||
//成功
|
||||
showToast("安装成功")
|
||||
}
|
||||
}
|
||||
}
|
||||
func installPluginError(err: String) {
|
||||
showToast(err)
|
||||
downloadProgress = 0
|
||||
isShowLoading = false
|
||||
}
|
||||
func test() {
|
||||
if downloadProgress >= 1 {
|
||||
withAnimation {
|
||||
isShowLoading = false
|
||||
}
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||
self.downloadProgress += 0.1
|
||||
self.test()
|
||||
}
|
||||
}
|
||||
func uninstall(manifest: PluginManifest) {
|
||||
if let err = pluginViewModel.uninstall(manifest: manifest) {
|
||||
showToast(err)
|
||||
}else {
|
||||
showToast("卸载成功")
|
||||
downloadProgress = 0
|
||||
isShowLoading = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct PluginContentView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
PluginContentView()
|
||||
}
|
||||
}
|
||||
|
||||
struct PluginDownloadProgressView: View {
|
||||
let lineWidth: CGFloat = 2
|
||||
let circleBackdropColor: Color
|
||||
let circleForegroundColor: Color
|
||||
@Binding var progress: Double
|
||||
var body: some View {
|
||||
GeometryReader { geometry in
|
||||
ZStack {
|
||||
Circle() // Inactive
|
||||
.stroke(lineWidth: lineWidth)
|
||||
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||
.foregroundColor(circleBackdropColor)
|
||||
|
||||
Rectangle()
|
||||
.frame(width: geometry.size.width * 0.4, height: geometry.size.width * 0.4)
|
||||
.foregroundColor(circleForegroundColor)
|
||||
.cornerRadius(2)
|
||||
// ProgressView()
|
||||
// .frame(width: geometry.size.width * 0.5, height: geometry.size.height * 0.5)
|
||||
// .foregroundColor(circleForegroundColor)
|
||||
// .cornerRadius(2)
|
||||
|
||||
Circle()
|
||||
.trim(from: 0, to: progress)
|
||||
.stroke(style: StrokeStyle(lineWidth: lineWidth, lineCap: .round, lineJoin: .round))
|
||||
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||
.foregroundColor(circleForegroundColor)
|
||||
.rotationEffect(.degrees(-90))
|
||||
.animation(.linear)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
172
OSXChatGPT/OSXChatGPT/WindowView/PluginPopView.swift
Normal file
172
OSXChatGPT/OSXChatGPT/WindowView/PluginPopView.swift
Normal file
@@ -0,0 +1,172 @@
|
||||
//
|
||||
// PluginPopView.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/7/11.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
import SimpleToast
|
||||
|
||||
class PluginSelectViewModel: ObservableObject, Identifiable {
|
||||
@Published var plugin: PluginAPIInstall
|
||||
@Published var isMySelect: Bool
|
||||
init(plugin: PluginAPIInstall) {
|
||||
self.plugin = plugin
|
||||
self.isMySelect = false
|
||||
}
|
||||
class func createDatas(_ plugins: [PluginAPIInstall]) -> [PluginSelectViewModel] {
|
||||
var arr : [PluginSelectViewModel] = []
|
||||
plugins.forEach { plu in
|
||||
arr.append(PluginSelectViewModel(plugin: plu))
|
||||
}
|
||||
return arr
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct PluginToolView: View {
|
||||
@EnvironmentObject var viewModel: ViewModel
|
||||
@Binding var showPluginView: Bool
|
||||
@Binding var isDisabledPlugin: Bool
|
||||
var body: some View {
|
||||
Button {
|
||||
showPluginView.toggle()
|
||||
} label: {
|
||||
GeometryReader { geometry in
|
||||
if viewModel.currentPlugins.count > 0 {
|
||||
HStack(alignment: .center, spacing: 5) {
|
||||
ForEach(viewModel.currentPlugins) { plugin in
|
||||
AsyncImage(url: URL(string: plugin.logo_url ?? "")) { image in
|
||||
image.resizable()
|
||||
} placeholder: {
|
||||
ProgressView()
|
||||
}
|
||||
.frame(width: 16, height: 16)
|
||||
.cornerRadius(2)
|
||||
}
|
||||
}
|
||||
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||
.opacity(isDisabledPlugin ? 0.6 : 1)
|
||||
}else {
|
||||
Text("No Plugin")
|
||||
.frame(width: geometry.size.width, height: geometry.size.height)
|
||||
.opacity(isDisabledPlugin ? 0.6 : 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.frame(width: 75, height: 22)
|
||||
.disabled(isDisabledPlugin)
|
||||
.onAppear{
|
||||
viewModel.refreshCurrentPlugins()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
struct PluginPopView: View {
|
||||
@EnvironmentObject var viewModel: ViewModel
|
||||
@Environment(\.presentationMode) var presentationMode
|
||||
@State var plugins: [PluginSelectViewModel]
|
||||
@State private var toastOptions: SimpleToastOptions = SimpleToastOptions(alignment: .center, hideAfter: 2.5, backdrop: nil, animation: nil, modifierType: .fade, dismissOnTap: true)
|
||||
@State private var isShowToast: Bool = false
|
||||
var body: some View {
|
||||
VStack(alignment: .center) {
|
||||
HStack(alignment: .center) {
|
||||
Button {
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
} label: {
|
||||
Text("取消")
|
||||
}.padding()
|
||||
Spacer()
|
||||
|
||||
Button {
|
||||
var select: [PluginAPIInstall] = []
|
||||
plugins.forEach { viewM in
|
||||
if viewM.isMySelect {
|
||||
select.append(viewM.plugin)
|
||||
}
|
||||
}
|
||||
viewModel.updateCurrentPlugins(plugins: select)
|
||||
viewModel.refreshCurrentPlugins()
|
||||
self.presentationMode.wrappedValue.dismiss()
|
||||
} label: {
|
||||
Text("确定")
|
||||
}.padding()
|
||||
|
||||
}
|
||||
.padding(.top, 16)
|
||||
.frame(height: 35)
|
||||
.frame(maxWidth: .infinity)
|
||||
.background(.white)
|
||||
|
||||
List {
|
||||
ForEach(plugins, id: \.plugin.id) { plugin in
|
||||
Toggle(isOn: Binding(get: { plugins[getIndex(for: plugin)].isMySelect }, set: { newValue in
|
||||
if newValue == true {
|
||||
if plugins.filter( {$0.isMySelect == true }).count >= 3 {
|
||||
self.isShowToast = true
|
||||
return
|
||||
}
|
||||
}
|
||||
plugin.isMySelect = newValue;
|
||||
plugins[getIndex(for: plugin)] = plugin
|
||||
})) {
|
||||
HStack {
|
||||
AsyncImage(url: URL(string: plugin.plugin.logo_url ?? "")) { image in
|
||||
image.resizable()
|
||||
} placeholder: {
|
||||
ProgressView()
|
||||
}
|
||||
.frame(width: 35, height: 35)
|
||||
.cornerRadius(5)
|
||||
VStack(alignment: .center) {
|
||||
HStack {
|
||||
Text(plugin.plugin.name_for_human ?? "")
|
||||
.font(Font.system(size: 14))
|
||||
Spacer()
|
||||
}
|
||||
.padding(.bottom, 1)
|
||||
Text(plugin.plugin.description_for_human ?? "")
|
||||
.font(Font.system(size: 12))
|
||||
}
|
||||
.frame(maxWidth: .infinity, minHeight: 45, maxHeight: 45, alignment: .leading)
|
||||
|
||||
}
|
||||
}
|
||||
.padding(5)
|
||||
.background(Color.black.opacity(0.1))
|
||||
.cornerRadius(6)
|
||||
}
|
||||
}
|
||||
.padding(.top, 1)
|
||||
.frame(width: 380, height: 500)
|
||||
|
||||
.onAppear {
|
||||
|
||||
}
|
||||
}
|
||||
.simpleToast(isPresented: $isShowToast, options: self.toastOptions, content: {
|
||||
VStack {
|
||||
Text("最多选三个插件")
|
||||
.padding()
|
||||
.foregroundColor(.white)
|
||||
.background(.black.opacity(0.9))
|
||||
.cornerRadius(5)
|
||||
}
|
||||
.padding()
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
func getIndex(for plugin: PluginSelectViewModel) -> Int {
|
||||
plugins.firstIndex(where: { $0.plugin.id == plugin.plugin.id })!
|
||||
}
|
||||
}
|
||||
|
||||
//struct PluginPopView_Previews: PreviewProvider {
|
||||
// static var previews: some View {
|
||||
// PluginPopView()
|
||||
// }
|
||||
//}
|
||||
@@ -25,7 +25,7 @@ struct SessionsView: View {
|
||||
Button(action: {
|
||||
viewModel.showUserInitialize = false
|
||||
viewModel.showAIPrompt = false
|
||||
|
||||
viewModel.showPluginView = false
|
||||
// 点击 New Chat 按钮的操作
|
||||
viewModel.currentConversation = viewModel.addNewConversation()
|
||||
viewModel.createNewChat = true
|
||||
@@ -36,6 +36,7 @@ struct SessionsView: View {
|
||||
Text(Localization.newChat.localized)
|
||||
}
|
||||
.padding(10)
|
||||
.padding(.leading, 0)
|
||||
.foregroundColor(.white)
|
||||
.background(Color.blue.opacity(0.8))
|
||||
.cornerRadius(5)
|
||||
@@ -52,6 +53,7 @@ struct SessionsView: View {
|
||||
}
|
||||
viewModel.createNewChat = false
|
||||
viewModel.showAIPrompt = false
|
||||
viewModel.showPluginView = false
|
||||
viewModel.showUserInitialize = true
|
||||
KeyboardMonitor.shared.stopKeyMonitor()
|
||||
KeyboardMonitor.shared.stopMonitorPasteboard()
|
||||
@@ -74,6 +76,7 @@ struct SessionsView: View {
|
||||
}
|
||||
viewModel.createNewChat = false
|
||||
viewModel.showUserInitialize = false
|
||||
viewModel.showPluginView = false
|
||||
viewModel.showAIPrompt = true
|
||||
KeyboardMonitor.shared.stopKeyMonitor()
|
||||
KeyboardMonitor.shared.stopMonitorPasteboard()
|
||||
@@ -85,11 +88,35 @@ struct SessionsView: View {
|
||||
.cornerRadius(5)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.padding(.trailing, 10)
|
||||
.padding(.trailing, 0)
|
||||
|
||||
}.buttonStyle(PlainButtonStyle())
|
||||
|
||||
|
||||
NavigationLink(destination: PluginContentView().environmentObject(viewModel), isActive: $viewModel.showPluginView) {
|
||||
Button(action: {
|
||||
// 点击右边按钮的操作
|
||||
if viewModel.currentConversation != nil {
|
||||
viewModel.currentConversation = nil//先取消会话
|
||||
}
|
||||
viewModel.createNewChat = false
|
||||
viewModel.showUserInitialize = false
|
||||
viewModel.showAIPrompt = false
|
||||
viewModel.showPluginView = true
|
||||
KeyboardMonitor.shared.stopKeyMonitor()
|
||||
KeyboardMonitor.shared.stopMonitorPasteboard()
|
||||
}) {
|
||||
Image(systemName: "powerplug.fill")
|
||||
.padding(10)
|
||||
.foregroundColor(.white)
|
||||
.background(Color.gray)
|
||||
.cornerRadius(5)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle())
|
||||
.padding(.trailing, 10)
|
||||
|
||||
}.buttonStyle(PlainButtonStyle())
|
||||
|
||||
})
|
||||
.frame(height: 20)
|
||||
.sheet(isPresented: $viewModel.showEditRemark) {
|
||||
|
||||
21
OSXChatGPT/OSXChatGPT/WindowView/SettingsView.swift
Normal file
21
OSXChatGPT/OSXChatGPT/WindowView/SettingsView.swift
Normal file
@@ -0,0 +1,21 @@
|
||||
//
|
||||
// SettingsView.swift
|
||||
// OSXChatGPT
|
||||
//
|
||||
// Created by CoderChan on 2023/7/3.
|
||||
//
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct SettingsView: View {
|
||||
var body: some View {
|
||||
Text(/*@START_MENU_TOKEN@*/"Hello, World!"/*@END_MENU_TOKEN@*/)
|
||||
.background(.red)
|
||||
}
|
||||
}
|
||||
|
||||
struct SettingsView_Previews: PreviewProvider {
|
||||
static var previews: some View {
|
||||
SettingsView()
|
||||
}
|
||||
}
|
||||
@@ -101,20 +101,20 @@ struct UserInitializeView: View {
|
||||
.buttonStyle(PlainButtonStyle()) // 隐藏按钮的默认样式
|
||||
|
||||
|
||||
Button(action: {
|
||||
showGoogleSetting.toggle()
|
||||
}) {
|
||||
HStack(spacing: 8) {
|
||||
Text("谷歌搜索配置")
|
||||
}
|
||||
.foregroundColor(.white)
|
||||
.padding(.horizontal, 16)
|
||||
.padding(.vertical, 8)
|
||||
.background(Color.green)
|
||||
.cornerRadius(10)
|
||||
.padding(.leading, 16)
|
||||
}
|
||||
.buttonStyle(PlainButtonStyle()) // 隐藏按钮的默认样式
|
||||
// Button(action: {
|
||||
// showGoogleSetting.toggle()
|
||||
// }) {
|
||||
// HStack(spacing: 8) {
|
||||
// Text("谷歌搜索配置")
|
||||
// }
|
||||
// .foregroundColor(.white)
|
||||
// .padding(.horizontal, 16)
|
||||
// .padding(.vertical, 8)
|
||||
// .background(Color.green)
|
||||
// .cornerRadius(10)
|
||||
// .padding(.leading, 16)
|
||||
// }
|
||||
// .buttonStyle(PlainButtonStyle()) // 隐藏按钮的默认样式
|
||||
}
|
||||
|
||||
if apiKey.count > 0 {
|
||||
|
||||
@@ -10,7 +10,7 @@ import SwiftUI
|
||||
extension View {
|
||||
func leftSessionContentSize() -> some View {
|
||||
frame(
|
||||
minWidth: 250, idealWidth: 250, maxWidth: .infinity,
|
||||
minWidth: 270, idealWidth: 270, maxWidth: .infinity,
|
||||
minHeight: 300, idealHeight: 350, maxHeight: .infinity,
|
||||
alignment: .leading
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user