diff --git a/PlantUML/PlantUML+OpenAI.swift b/PlantUML/PlantUML+OpenAI.swift index 25f63e1..b298015 100644 --- a/PlantUML/PlantUML+OpenAI.swift +++ b/PlantUML/PlantUML+OpenAI.swift @@ -86,7 +86,7 @@ class LILOFixedSizeQueue : LILOQueue { class OpenAIService : ObservableObject { - enum Status { + enum Status : Equatable { case Ready case Error( String ) case Editing @@ -100,7 +100,7 @@ class OpenAIService : ObservableObject { // @Published public var inputModel:String - @AppStorage("openaiModel") private var openAIModel:String = "text-davinci-edit-001" + @AppStorage("openaiModel") private var openAIModel:String = "gpt-3.5-turbo" @AppSecureStorage("openaikey") private var openAIKey:String? @AppSecureStorage("openaiorg") private var openAIOrg:String? @@ -166,9 +166,8 @@ class OpenAIService : ObservableObject { } - @MainActor - func generateEdit( input: String, instruction: String ) async -> String? { + func generateChatCompletion( input: String, instruction: String ) async -> String? { guard let openAI /*, let openAIModel */, case .Ready = status else { return nil @@ -177,17 +176,28 @@ class OpenAIService : ObservableObject { self.status = .Editing do { - let editParameter = EditParameters( + + let chat: [ChatMessage] = [ + ChatMessage(role: .system, content: + """ + You are my plantUML assistant. + You must answer exclusively with diagram syntax. + """), + ChatMessage(role: .assistant, content: input), + ChatMessage(role: .user, content: instruction), + ] + + let chatParameters = ChatParameters( model: openAIModel, - input: input, - instruction: instruction, + messages: chat, temperature: 0.0, - topP: 1.0 - ) + topP: 1.0) - let editResponse = try await openAI.generateEdit(parameters: editParameter) - - let result = editResponse.choices[0].text + let chatCompletion = try await openAI.generateChatCompletion( + parameters: chatParameters + ) + + let result = chatCompletion.choices[0].message.content return result } @@ -340,7 +350,7 @@ extension OpenAIView { Task { let input = "@startuml\n\(result)\n@enduml" - if let res = await service.generateEdit( input: input, instruction: instruction ) { + if let res = await service.generateChatCompletion( input: input, instruction: instruction ) { service.status = .Ready service.clipboard.push( result ) diff --git a/PlantUML/PlantUMLContentView.swift b/PlantUML/PlantUMLContentView.swift index 821789d..6f1aeb5 100644 --- a/PlantUML/PlantUMLContentView.swift +++ b/PlantUML/PlantUMLContentView.swift @@ -48,12 +48,31 @@ struct PlantUMLContentView: View { @State private var saving = false @State private var diagramImage:UIImage? + @State private var editorViewId = 1 var body: some View { VStack { GeometryReader { geometry in + if( isEditorVisible ) { - EditorView_Fragment + VStack { + PlantUMLEditorView( content: $document.text, + darkTheme: CodeWebView.Theme(rawValue: darkTheme)!, + lightTheme: CodeWebView.Theme(rawValue: lightTheme)!, + isReadOnly: false, + fontSize: CGFloat(fontSize), + showGutter: showLine + ) + .id( editorViewId ) + .if( isRunningTests ) { /// this need for catching current editor data from UI test + $0.overlay(alignment: .bottom) { + Text( document.text ) + .frame( width: 0, height: 0) + .opacity(0) + .accessibilityIdentifier("editor-text") + } + } + } } if isDiagramVisible { @@ -71,8 +90,16 @@ struct PlantUMLContentView: View { } } if isOpenAIVisible /* && interfaceOrientation.value.isPortrait */ { - OpenAIView_Fragment + OpenAIView( service: openAIService, result: $document.text ) .frame( height: 200 ) + .onChange(of: openAIService.status ) { newStatus in + if( .Ready == newStatus ) { + // Force rendering editor view +// print( "FORCE RENDERING OF EDITOR VIEW") + editorViewId += 1 + } + } + } } .onChange(of: document.text ) { _ in @@ -127,45 +154,11 @@ struct PlantUMLContentView: View { } } -// -// MARK: - OpenAI extension - -// -extension PlantUMLContentView { - - var OpenAIView_Fragment: some View { - - OpenAIView( service: openAIService, result: $document.text ) - - } - -} - // // MARK: - Editor extension - // extension PlantUMLContentView { - var EditorView_Fragment: some View { - - VStack { - PlantUMLEditorView( content: $document.text, - darkTheme: CodeWebView.Theme(rawValue: darkTheme)!, - lightTheme: CodeWebView.Theme(rawValue: lightTheme)!, - isReadOnly: false, - fontSize: CGFloat(fontSize), - showGutter: showLine - ) - .if( isRunningTests ) { /// this need for catching current editor data from UI test - $0.overlay(alignment: .bottom) { - Text( document.text ) - .frame( width: 0, height: 0) - .opacity(0) - .accessibilityIdentifier("editor-text") - } - } - } - } - // [SwiftUI Let View disappear automatically](https://stackoverflow.com/a/60820491/521197) struct SavedStateView: View { @Binding var visible: Bool diff --git a/PlantUML/Settings.bundle/Root.plist b/PlantUML/Settings.bundle/Root.plist index b34de75..e23007a 100644 --- a/PlantUML/Settings.bundle/Root.plist +++ b/PlantUML/Settings.bundle/Root.plist @@ -232,22 +232,22 @@ DefaultValue - text-davinci-edit-001 + gpt-3.5-turbo Key openaiModel Title Model Titles - code-davinci-edit-001 - text-davinci-edit-001 + gpt-3.5-turbo + gpt-4 Type PSMultiValueSpecifier Values - code-davinci-edit-001 - text-davinci-edit-001 + gpt-3.5-turbo + gpt-4 diff --git a/PlantUMLEditor/Sources/CodeViewer/CodeViewer.swift b/PlantUMLEditor/Sources/CodeViewer/CodeViewer.swift index 7a0fa61..eed3c66 100644 --- a/PlantUMLEditor/Sources/CodeViewer/CodeViewer.swift +++ b/PlantUMLEditor/Sources/CodeViewer/CodeViewer.swift @@ -60,7 +60,7 @@ public struct CodeViewer: ViewRepresentable { } public func makeCoordinator() -> Coordinator { - Coordinator(content: $content, + Coordinator(content: content, colorScheme: colorScheme, lightTheme: lightTheme, darkTheme: darkTheme, @@ -78,7 +78,8 @@ public struct CodeViewer: ViewRepresentable { codeView.clearSelection() codeView.setShowGutter(showGutter) codeView.textDidChanged = { text in - context.coordinator.set(content: text) + self.content = text + context.coordinator.content = text self.textDidChanged?(text) } codeView.setTheme( colorScheme == .dark ? darkTheme : lightTheme ) @@ -87,6 +88,11 @@ public struct CodeViewer: ViewRepresentable { } private func updateView(_ webview: CodeWebView, context: Context) { + if context.coordinator.content != content { + context.coordinator.content = content + webview.setContent(content) + print(content) + } if context.coordinator.fontSize != fontSize { context.coordinator.fontSize = fontSize @@ -145,21 +151,21 @@ public struct CodeViewer: ViewRepresentable { public extension CodeViewer { class Coordinator: NSObject { - @Binding private(set) var content: String + var content: String var colorScheme: ColorScheme var fontSize: CGFloat var showGutter: Bool var darkTheme:CodeWebView.Theme var lightTheme:CodeWebView.Theme - init(content: Binding, + init(content: String, colorScheme: ColorScheme, lightTheme:CodeWebView.Theme, darkTheme:CodeWebView.Theme, fontSize: CGFloat, showGutter: Bool ) { - _content = content + self.content = content self.colorScheme = colorScheme self.darkTheme = darkTheme self.lightTheme = lightTheme @@ -167,12 +173,6 @@ public extension CodeViewer { self.showGutter = showGutter } - func set(content: String) { - if self.content != content { - self.content = content - } - } - } } diff --git a/PlantUMLEditor/Sources/CodeViewer/CodeWebView.swift b/PlantUMLEditor/Sources/CodeViewer/CodeWebView.swift index 9df37ba..14e1ba3 100644 --- a/PlantUMLEditor/Sources/CodeViewer/CodeWebView.swift +++ b/PlantUMLEditor/Sources/CodeViewer/CodeWebView.swift @@ -99,7 +99,7 @@ public class CodeWebView: CustomView { // And use String.raw to prevent escape some special string -> String will show exactly how it's // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Template_literals // - let first = "var content = String.raw`" + let first = "const content = String.raw`" let content = """ \(value) """