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)) + } }