From 8111de51ab9eaa0a62787adec35501368b511567 Mon Sep 17 00:00:00 2001 From: Leon Nissen <> Date: Fri, 6 Dec 2024 10:05:45 -0800 Subject: [PATCH 1/6] intermediate commit --- Sources/SpeziChat/MessageView.swift | 28 ++++++- .../Models/ChatEntity+Alignment.swift | 10 +++ Sources/SpeziChat/Models/ChatEntity.swift | 4 + .../SpeziChat/Resources/Localizable.xcstrings | 22 +++++ Sources/SpeziChat/ToolInteractionView.swift | 81 +++++++++++++++++++ 5 files changed, 142 insertions(+), 3 deletions(-) create mode 100644 Sources/SpeziChat/ToolInteractionView.swift diff --git a/Sources/SpeziChat/MessageView.swift b/Sources/SpeziChat/MessageView.swift index faeb554..a4792c1 100644 --- a/Sources/SpeziChat/MessageView.swift +++ b/Sources/SpeziChat/MessageView.swift @@ -45,7 +45,7 @@ public struct MessageView: View { private var shouldDisplayMessage: Bool { switch chat.role { - case .user, .assistant: return true + case .user, .assistant, .assistantToolCall, .assistantToolResponse: return true case .hidden(let type): if case .custom(let hiddenMessageTypes) = hideMessages { return !hiddenMessageTypes.contains(type) @@ -55,14 +55,30 @@ public struct MessageView: View { } } + private var isToolInteraction: Bool { + switch chat.role { + case .assistantToolCall, .assistantToolResponse: + return true + default: + return false + } + } + public var body: some View { if shouldDisplayMessage { HStack { if chat.alignment == .trailing { Spacer(minLength: 32) } - Text(chat.attributedContent) - .chatMessageStyle(alignment: chat.alignment) + VStack(alignment: chat.horziontalAlignment) { + if isToolInteraction { + ToolInteractionView(entity: chat) + } else { + Text(chat.attributedContent) + .chatMessageStyle(alignment: chat.alignment) + } + } + if chat.alignment == .leading { Spacer(minLength: 32) } @@ -89,6 +105,12 @@ public struct MessageView: View { MessageView(ChatEntity(role: .assistant, content: "Assistant Message!")) MessageView(ChatEntity(role: .user, content: "Long User Message that spans over two lines!")) MessageView(ChatEntity(role: .assistant, content: "Long Assistant Message that spans over two lines!")) + MessageView(ChatEntity(role: .assistantToolCall, content: "assistent_too_call(parameter: value)")) + MessageView(ChatEntity(role: .assistantToolResponse, content: """ + { + "some": "response" + } + """)) MessageView(ChatEntity(role: .hidden(type: .unknown), content: "Hidden message! (invisible)")) MessageView( ChatEntity( diff --git a/Sources/SpeziChat/Models/ChatEntity+Alignment.swift b/Sources/SpeziChat/Models/ChatEntity+Alignment.swift index 06128a4..7f2d454 100644 --- a/Sources/SpeziChat/Models/ChatEntity+Alignment.swift +++ b/Sources/SpeziChat/Models/ChatEntity+Alignment.swift @@ -7,6 +7,7 @@ // import Foundation +import SwiftUI extension ChatEntity { @@ -26,4 +27,13 @@ extension ChatEntity { return .leading } } + + var horziontalAlignment: HorizontalAlignment { + switch self.alignment { + case .leading: + return .leading + case .trailing: + return .trailing + } + } } diff --git a/Sources/SpeziChat/Models/ChatEntity.swift b/Sources/SpeziChat/Models/ChatEntity.swift index 937e419..a409158 100644 --- a/Sources/SpeziChat/Models/ChatEntity.swift +++ b/Sources/SpeziChat/Models/ChatEntity.swift @@ -19,6 +19,8 @@ public struct ChatEntity: Codable, Equatable, Hashable, Identifiable { public enum Role: Codable, Equatable, Hashable { case user case assistant + case assistantToolCall + case assistantToolResponse case hidden(type: ChatEntity.HiddenMessageType) @@ -26,6 +28,8 @@ public struct ChatEntity: Codable, Equatable, Hashable, Identifiable { switch self { case .user: "user" case .assistant: "assistant" + case .assistantToolCall: "assistant_tool_call" + case .assistantToolResponse: "assistant_tool_response" case .hidden(let type): "hidden_\(type.name)" } } diff --git a/Sources/SpeziChat/Resources/Localizable.xcstrings b/Sources/SpeziChat/Resources/Localizable.xcstrings index 238eda0..9d6c0ec 100644 --- a/Sources/SpeziChat/Resources/Localizable.xcstrings +++ b/Sources/SpeziChat/Resources/Localizable.xcstrings @@ -75,6 +75,28 @@ } } }, + "SEE_MORE" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Details anzeigen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Show Details" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Ver detalles" + } + } + } + }, "SEND_MESSAGE" : { "localizations" : { "de" : { diff --git a/Sources/SpeziChat/ToolInteractionView.swift b/Sources/SpeziChat/ToolInteractionView.swift new file mode 100644 index 0000000..4f2c4e6 --- /dev/null +++ b/Sources/SpeziChat/ToolInteractionView.swift @@ -0,0 +1,81 @@ +// +// This source file is part of the Stanford Spezi open source project +// +// SPDX-FileCopyrightText: 2024 Stanford University and the project authors (see CONTRIBUTORS.md) +// +// SPDX-License-Identifier: MIT +// + +import SwiftUI + +/// The view that is represented when a tool call or tool reponse based on the ``ChatEntity/Role``. +struct ToolInteractionView: View { + let entity: ChatEntity + @State private var isExpanded: Bool = false + + var body: some View { + switch entity.role { + case .assistantToolCall: + toolCallView(content: entity.content) + case .assistantToolResponse: + toolResponseView(content: entity.content) + default: + EmptyView() + } + } + + private func toolCallView(content: String) -> some View { + HStack { + Image(systemName: "function") + .frame(width: 20) + Text(content) + .foregroundStyle(.secondary) + .font(.footnote) + .lineLimit(isExpanded ? nil : 0) + } + .padding(.horizontal, 10) + .padding(.top, 8) + .onTapGesture { + withAnimation { + isExpanded.toggle() + } + } + } + + private func toolResponseView(content: String) -> some View { + HStack { + Image(systemName: "equal") + .frame(width: 20) + + Group { + if content.contains(where: \.isNewline) && !isExpanded { + Text(String(localized: "SEE_MORE", bundle: .module)) + .italic() + } else { + Text(content) + } + } + .foregroundStyle(.secondary) + .font(.footnote) + .lineLimit(isExpanded ? nil : 0) + + } + .padding(.horizontal, 10) + .padding(.top, 8) + .padding(.bottom, 4) + .onTapGesture { + withAnimation { + isExpanded.toggle() + } + } + } +} + +#Preview { + ToolInteractionView(entity: .init(role: .assistantToolCall, content: "foo(bar: baz) This is a very long text that should be truncated")) + ToolInteractionView(entity: .init(role: .assistantToolResponse, content: """ + { + "some": "response" + } + """)) +} From f24789050406ac89da777af5e82eccc159495486 Mon Sep 17 00:00:00 2001 From: Leon Nissen <> Date: Fri, 6 Dec 2024 10:36:55 -0800 Subject: [PATCH 2/6] intermediate commit --- Sources/SpeziChat/ToolInteractionView.swift | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Sources/SpeziChat/ToolInteractionView.swift b/Sources/SpeziChat/ToolInteractionView.swift index 4f2c4e6..d544002 100644 --- a/Sources/SpeziChat/ToolInteractionView.swift +++ b/Sources/SpeziChat/ToolInteractionView.swift @@ -11,7 +11,7 @@ import SwiftUI /// The view that is represented when a tool call or tool reponse based on the ``ChatEntity/Role``. struct ToolInteractionView: View { let entity: ChatEntity - @State private var isExpanded: Bool = false + @State private var isExpanded = false var body: some View { switch entity.role { @@ -27,6 +27,7 @@ struct ToolInteractionView: View { private func toolCallView(content: String) -> some View { HStack { Image(systemName: "function") + .accessibilityLabel("Function F of X") .frame(width: 20) Text(content) .foregroundStyle(.secondary) @@ -45,6 +46,7 @@ struct ToolInteractionView: View { private func toolResponseView(content: String) -> some View { HStack { Image(systemName: "equal") + .accessibilityLabel("Equal sign") .frame(width: 20) Group { @@ -58,7 +60,6 @@ struct ToolInteractionView: View { .foregroundStyle(.secondary) .font(.footnote) .lineLimit(isExpanded ? nil : 0) - } .padding(.horizontal, 10) .padding(.top, 8) @@ -70,12 +71,3 @@ struct ToolInteractionView: View { } } } - -#Preview { - ToolInteractionView(entity: .init(role: .assistantToolCall, content: "foo(bar: baz) This is a very long text that should be truncated")) - ToolInteractionView(entity: .init(role: .assistantToolResponse, content: """ - { - "some": "response" - } - """)) -} From 3f67a4e09bbd9db66d89b68b487c4d82660a23bd Mon Sep 17 00:00:00 2001 From: Leon Nissen <> Date: Fri, 6 Dec 2024 10:50:31 -0800 Subject: [PATCH 3/6] fix UItests --- Sources/SpeziChat/Resources/Localizable.xcstrings | 6 ++++++ Tests/UITests/TestAppUITests/TestAppUITests.swift | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/Sources/SpeziChat/Resources/Localizable.xcstrings b/Sources/SpeziChat/Resources/Localizable.xcstrings index 9d6c0ec..55600af 100644 --- a/Sources/SpeziChat/Resources/Localizable.xcstrings +++ b/Sources/SpeziChat/Resources/Localizable.xcstrings @@ -20,6 +20,9 @@ } } } + }, + "Equal sign" : { + }, "EXPORT_CHAT_BUTTON" : { "localizations" : { @@ -30,6 +33,9 @@ } } } + }, + "Function F of X" : { + }, "MESSAGE_INPUT_TEXTFIELD" : { "localizations" : { diff --git a/Tests/UITests/TestAppUITests/TestAppUITests.swift b/Tests/UITests/TestAppUITests/TestAppUITests.swift index d842d30..5a0569d 100644 --- a/Tests/UITests/TestAppUITests/TestAppUITests.swift +++ b/Tests/UITests/TestAppUITests/TestAppUITests.swift @@ -28,7 +28,7 @@ class TestAppUITests: XCTestCase { XCTAssert(app.staticTexts["SpeziChat"].waitForExistence(timeout: 1)) XCTAssert(app.staticTexts["Assistant Message!"].waitForExistence(timeout: 1)) - try app.textViews["Message Input Textfield"].enter(value: "User Message!", dismissKeyboard: false) + try app.textFields["Message Input Textfield"].enter(value: "User Message!", dismissKeyboard: false) XCTAssert(app.buttons["Send Message"].waitForExistence(timeout: 5)) app.buttons["Send Message"].tap() @@ -60,7 +60,7 @@ class TestAppUITests: XCTestCase { // Entering dummy chat value XCTAssert(app.staticTexts["SpeziChat"].waitForExistence(timeout: 1)) - try app.textViews["Message Input Textfield"].enter(value: "User Message!", dismissKeyboard: false) + try app.textFields["Message Input Textfield"].enter(value: "User Message!", dismissKeyboard: false) XCTAssert(app.buttons["Send Message"].waitForExistence(timeout: 5)) app.buttons["Send Message"].tap() From 35a0c05d95e162b1ed2fbd542425ba57988b9352 Mon Sep 17 00:00:00 2001 From: Philipp Zagar Date: Wed, 11 Dec 2024 12:13:18 +0100 Subject: [PATCH 4/6] Maintenance --- Sources/SpeziChat/ChatView+SpeechButton.swift | 4 ++-- Sources/SpeziChat/ChatView+SpeechOutput.swift | 10 +++++----- Sources/SpeziChat/MessageInputView.swift | 2 +- Tests/UITests/UITests.xcodeproj/project.pbxproj | 5 +---- .../xcshareddata/xcschemes/TestApp.xcscheme | 2 +- 5 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Sources/SpeziChat/ChatView+SpeechButton.swift b/Sources/SpeziChat/ChatView+SpeechButton.swift index 130bce9..bfa58da 100644 --- a/Sources/SpeziChat/ChatView+SpeechButton.swift +++ b/Sources/SpeziChat/ChatView+SpeechButton.swift @@ -86,14 +86,14 @@ extension View { #if DEBUG #Preview { - @State var chat: Chat = .init( + @Previewable @State var chat: Chat = .init( [ ChatEntity(role: .user, content: "User Message!"), ChatEntity(role: .hidden(type: .unknown), content: "Hidden Message!"), ChatEntity(role: .assistant, content: "Assistant Message!") ] ) - @State var muted = true + @Previewable @State var muted = true return NavigationStack { diff --git a/Sources/SpeziChat/ChatView+SpeechOutput.swift b/Sources/SpeziChat/ChatView+SpeechOutput.swift index e700aa4..24f57b8 100644 --- a/Sources/SpeziChat/ChatView+SpeechOutput.swift +++ b/Sources/SpeziChat/ChatView+SpeechOutput.swift @@ -115,7 +115,7 @@ extension View { #if DEBUG #Preview("ChatView") { - @State var chat: Chat = .init( + @Previewable @State var chat: Chat = .init( [ ChatEntity(role: .user, content: "User Message!"), ChatEntity(role: .hidden(type: .unknown), content: "Hidden Message!"), @@ -129,12 +129,12 @@ extension View { } #Preview("ChatViewSpeechOutput") { - @State var chat: Chat = .init( + @Previewable @State var chat: Chat = .init( [ ChatEntity(role: .assistant, content: "Assistant Message!") ] ) - @State var muted = false + @Previewable @State var muted = false return NavigationStack { @@ -144,12 +144,12 @@ extension View { } #Preview("ChatViewSpeechOutputDisabled") { - @State var chat: Chat = .init( + @Previewable @State var chat: Chat = .init( [ ChatEntity(role: .assistant, content: "Assistant Message!") ] ) - @State var muted = true + @Previewable @State var muted = true return NavigationStack { diff --git a/Sources/SpeziChat/MessageInputView.swift b/Sources/SpeziChat/MessageInputView.swift index 1ac0ed7..96c1f88 100644 --- a/Sources/SpeziChat/MessageInputView.swift +++ b/Sources/SpeziChat/MessageInputView.swift @@ -225,7 +225,7 @@ public struct MessageInputView: View { #if DEBUG #Preview { - @State var chat = [ + @Previewable @State var chat = [ ChatEntity(role: .user, content: "User Message!"), ChatEntity(role: .hidden(type: .unknown), content: "Hidden Message!"), ChatEntity(role: .assistant, content: "Assistant Message!") diff --git a/Tests/UITests/UITests.xcodeproj/project.pbxproj b/Tests/UITests/UITests.xcodeproj/project.pbxproj index 2c1af92..809887a 100644 --- a/Tests/UITests/UITests.xcodeproj/project.pbxproj +++ b/Tests/UITests/UITests.xcodeproj/project.pbxproj @@ -170,7 +170,7 @@ attributes = { BuildIndependentTargetsInParallel = 1; LastSwiftUpdateCheck = 1500; - LastUpgradeCheck = 1500; + LastUpgradeCheck = 1610; TargetAttributes = { 2F6D139128F5F384007C25D6 = { CreatedOnToolsVersion = 14.1; @@ -445,7 +445,6 @@ 2F6D13BD28F5F386007C25D6 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 637867499T; @@ -470,7 +469,6 @@ 2F6D13BE28F5F386007C25D6 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 637867499T; @@ -594,7 +592,6 @@ 2FB07589299DDB6000C0B37F /* Test */ = { isa = XCBuildConfiguration; buildSettings = { - ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = 637867499T; diff --git a/Tests/UITests/UITests.xcodeproj/xcshareddata/xcschemes/TestApp.xcscheme b/Tests/UITests/UITests.xcodeproj/xcshareddata/xcschemes/TestApp.xcscheme index 25cc221..3c62dfb 100644 --- a/Tests/UITests/UITests.xcodeproj/xcshareddata/xcschemes/TestApp.xcscheme +++ b/Tests/UITests/UITests.xcodeproj/xcshareddata/xcschemes/TestApp.xcscheme @@ -1,6 +1,6 @@ Date: Wed, 11 Dec 2024 08:47:15 -0800 Subject: [PATCH 5/6] fix pr comments --- Sources/SpeziChat/MessageView.swift | 4 +- Sources/SpeziChat/MessagesView.swift | 2 + .../Models/ChatEntity+Alignment.swift | 8 +- .../SpeziChat/Resources/Localizable.xcstrings | 100 ++++++++++++++++-- Sources/SpeziChat/ToolInteractionView.swift | 4 +- Tests/UITests/TestApp/ChatTestView.swift | 12 ++- .../TestAppUITests/TestAppUITests.swift | 17 +++ 7 files changed, 132 insertions(+), 15 deletions(-) diff --git a/Sources/SpeziChat/MessageView.swift b/Sources/SpeziChat/MessageView.swift index a4792c1..c316360 100644 --- a/Sources/SpeziChat/MessageView.swift +++ b/Sources/SpeziChat/MessageView.swift @@ -58,9 +58,9 @@ public struct MessageView: View { private var isToolInteraction: Bool { switch chat.role { case .assistantToolCall, .assistantToolResponse: - return true + true default: - return false + false } } diff --git a/Sources/SpeziChat/MessagesView.swift b/Sources/SpeziChat/MessagesView.swift index 2ad2232..fb93941 100644 --- a/Sources/SpeziChat/MessagesView.swift +++ b/Sources/SpeziChat/MessagesView.swift @@ -175,6 +175,8 @@ public struct MessagesView: View { [ ChatEntity(role: .user, content: "User Message!"), ChatEntity(role: .hidden(type: .unknown), content: "Hidden Message (but still visible)!"), + ChatEntity(role: .assistantToolCall, content: "Assistant Message!"), + ChatEntity(role: .assistantToolResponse, content: "Assistant Message!f jiodsjfiods \n fudshfdusi"), ChatEntity(role: .assistant, content: "Assistant Message!") ], hideMessages: .custom(hiddenMessageTypes: []) diff --git a/Sources/SpeziChat/Models/ChatEntity+Alignment.swift b/Sources/SpeziChat/Models/ChatEntity+Alignment.swift index 7f2d454..cc32e1c 100644 --- a/Sources/SpeziChat/Models/ChatEntity+Alignment.swift +++ b/Sources/SpeziChat/Models/ChatEntity+Alignment.swift @@ -22,18 +22,18 @@ extension ChatEntity { var alignment: Alignment { switch self.role { case .user: - return .trailing + .trailing default: - return .leading + .leading } } var horziontalAlignment: HorizontalAlignment { switch self.alignment { case .leading: - return .leading + .leading case .trailing: - return .trailing + .trailing } } } diff --git a/Sources/SpeziChat/Resources/Localizable.xcstrings b/Sources/SpeziChat/Resources/Localizable.xcstrings index 55600af..ba9ae4b 100644 --- a/Sources/SpeziChat/Resources/Localizable.xcstrings +++ b/Sources/SpeziChat/Resources/Localizable.xcstrings @@ -21,21 +21,71 @@ } } }, - "Equal sign" : { - + "EQUAL_SIGN" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Gleichzeichen" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Equal Sign" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Signo igual" + } + } + } }, "EXPORT_CHAT_BUTTON" : { "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Chat exportieren" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Export the Chat" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Exportar el chat" + } } } }, - "Function F of X" : { - + "FUNCTION_F_OF_X" : { + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Funktion F von X" + } + }, + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "Function F of X" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Función F de X" + } + } + } }, "MESSAGE_INPUT_TEXTFIELD" : { "localizations" : { @@ -126,18 +176,56 @@ } }, "Text to speech is disabled, press to enable text to speech." : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Die Spracheingabe ist deaktiviert, tippe um die Eingabe zu aktivieren." + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La función de texto a voz está desactivada, pulse para activarla." + } + } + } }, "Text to speech is enabled, press to disable text to speech." : { - + "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Die Spracheingabe is aktiviert, tippe um die Eingabe zu dekativieren" + } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "La función de texto a voz está activada, pulse para desactivarla." + } + } + } }, "TYPING_INDICATOR" : { "localizations" : { + "de" : { + "stringUnit" : { + "state" : "translated", + "value" : "Schreibanzeige" + } + }, "en" : { "stringUnit" : { "state" : "translated", "value" : "Typing Indicator" } + }, + "es" : { + "stringUnit" : { + "state" : "translated", + "value" : "Indicador de mecanografía" + } } } } diff --git a/Sources/SpeziChat/ToolInteractionView.swift b/Sources/SpeziChat/ToolInteractionView.swift index d544002..f4f3844 100644 --- a/Sources/SpeziChat/ToolInteractionView.swift +++ b/Sources/SpeziChat/ToolInteractionView.swift @@ -27,7 +27,7 @@ struct ToolInteractionView: View { private func toolCallView(content: String) -> some View { HStack { Image(systemName: "function") - .accessibilityLabel("Function F of X") + .accessibilityLabel("FUNCTION_F_OF_X") .frame(width: 20) Text(content) .foregroundStyle(.secondary) @@ -46,7 +46,7 @@ struct ToolInteractionView: View { private func toolResponseView(content: String) -> some View { HStack { Image(systemName: "equal") - .accessibilityLabel("Equal sign") + .accessibilityLabel("EQUAL_SIGN") .frame(width: 20) Group { diff --git a/Tests/UITests/TestApp/ChatTestView.swift b/Tests/UITests/TestApp/ChatTestView.swift index 7530427..bf04b1e 100644 --- a/Tests/UITests/TestApp/ChatTestView.swift +++ b/Tests/UITests/TestApp/ChatTestView.swift @@ -32,7 +32,17 @@ struct ChatTestView: View { // Append a new assistant message to the chat after sleeping for 5 seconds. if newValue.last?.role == .user { Task { - try await Task.sleep(for: .seconds(5)) + try await Task.sleep(for: .seconds(3)) + + await MainActor.run { + chat.append(.init(role: .assistantToolCall, content: "call_test_func({ test: true })")) + } + try await Task.sleep(for: .seconds(1)) + + await MainActor.run { + chat.append(.init(role: .assistantToolResponse, content: "{ some: response }")) + } + try await Task.sleep(for: .seconds(1)) await MainActor.run { chat.append(.init(role: .assistant, content: "**Assistant** Message Response!")) diff --git a/Tests/UITests/TestAppUITests/TestAppUITests.swift b/Tests/UITests/TestAppUITests/TestAppUITests.swift index 5a0569d..cb6c181 100644 --- a/Tests/UITests/TestAppUITests/TestAppUITests.swift +++ b/Tests/UITests/TestAppUITests/TestAppUITests.swift @@ -154,4 +154,21 @@ class TestAppUITests: XCTestCase { XCTAssert(!app.buttons["Speaker strikethrough"].waitForExistence(timeout: 2)) XCTAssert(app.buttons["Speaker"].waitForExistence(timeout: 2)) } + + func testFunctionCallAndResponse() throws { + let app = XCUIApplication() + + XCTAssert(app.staticTexts["SpeziChat"].waitForExistence(timeout: 1)) + XCTAssert(app.staticTexts["Assistant Message!"].waitForExistence(timeout: 1)) + + try app.textFields["Message Input Textfield"].enter(value: "Call some function", dismissKeyboard: false) + XCTAssert(app.buttons["Send Message"].waitForExistence(timeout: 5)) + app.buttons["Send Message"].tap() + + sleep(5) + + XCTAssert(app.staticTexts["call_test_func({ test: true })"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["{ some: response }"].waitForExistence(timeout: 2)) + XCTAssert(app.staticTexts["Assistant Message Response!"].waitForExistence(timeout: 2)) + } } From 8d7101ad6b01ee43d7f405988f2f6a94cc2b965b Mon Sep 17 00:00:00 2001 From: Leon Nissen <> Date: Wed, 11 Dec 2024 10:50:35 -0800 Subject: [PATCH 6/6] add UITest --- Tests/UITests/TestApp/ChatTestView.swift | 18 ++++++++++-------- .../TestAppUITests/TestAppUITests.swift | 8 ++------ 2 files changed, 12 insertions(+), 14 deletions(-) diff --git a/Tests/UITests/TestApp/ChatTestView.swift b/Tests/UITests/TestApp/ChatTestView.swift index bf04b1e..224f678 100644 --- a/Tests/UITests/TestApp/ChatTestView.swift +++ b/Tests/UITests/TestApp/ChatTestView.swift @@ -34,15 +34,17 @@ struct ChatTestView: View { Task { try await Task.sleep(for: .seconds(3)) - await MainActor.run { - chat.append(.init(role: .assistantToolCall, content: "call_test_func({ test: true })")) - } - try await Task.sleep(for: .seconds(1)) - - await MainActor.run { - chat.append(.init(role: .assistantToolResponse, content: "{ some: response }")) + if newValue.last?.content == "Call some function" { + await MainActor.run { + chat.append(.init(role: .assistantToolCall, content: "call_test_func({ test: true })")) + } + try await Task.sleep(for: .seconds(1)) + + await MainActor.run { + chat.append(.init(role: .assistantToolResponse, content: "{ some: response }")) + } + try await Task.sleep(for: .seconds(1)) } - try await Task.sleep(for: .seconds(1)) await MainActor.run { chat.append(.init(role: .assistant, content: "**Assistant** Message Response!")) diff --git a/Tests/UITests/TestAppUITests/TestAppUITests.swift b/Tests/UITests/TestAppUITests/TestAppUITests.swift index cb6c181..26ddcf3 100644 --- a/Tests/UITests/TestAppUITests/TestAppUITests.swift +++ b/Tests/UITests/TestAppUITests/TestAppUITests.swift @@ -34,13 +34,9 @@ class TestAppUITests: XCTestCase { XCTAssert(app.staticTexts["User Message!"].waitForExistence(timeout: 5)) - sleep(1) + XCTAssert(app.otherElements["Typing Indicator"].waitForExistence(timeout: 3)) - XCTAssert(app.otherElements["Typing Indicator"].waitForExistence(timeout: 2)) - - sleep(4) - - XCTAssert(app.staticTexts["Assistant Message Response!"].waitForExistence(timeout: 5)) + XCTAssert(app.staticTexts["Assistant Message Response!"].waitForExistence(timeout: 9)) } func testChatExport() throws { // swiftlint:disable:this function_body_length