From 74f6d8bac1e6ef15cd1a03024abdbb4ee61678d9 Mon Sep 17 00:00:00 2001 From: Morgan Chen Date: Wed, 8 May 2024 13:14:54 -0700 Subject: [PATCH 01/11] update doc comments (#166) --- Sources/GoogleAI/Chat.swift | 11 +++++++++-- Sources/GoogleAI/GenerativeAISwift.swift | 3 ++- Sources/GoogleAI/GenerativeModel.swift | 12 ++++++------ 3 files changed, 17 insertions(+), 9 deletions(-) diff --git a/Sources/GoogleAI/Chat.swift b/Sources/GoogleAI/Chat.swift index a83947e..6549df4 100644 --- a/Sources/GoogleAI/Chat.swift +++ b/Sources/GoogleAI/Chat.swift @@ -30,7 +30,11 @@ public class Chat { /// model. This will be provided to the model for each message sent as context for the discussion. public var history: [ModelContent] - /// See ``sendMessage(_:)-3ify5``. + /// Sends a message using the existing history of this chat as context. If successful, the message + /// and response will be added to the history. If unsuccessful, history will remain unchanged. + /// - Parameter parts: The new content to send as a single chat message. + /// - Returns: The model's response if no error occurred. + /// - Throws: A ``GenerateContentError`` if an error occurred. public func sendMessage(_ parts: any ThrowingPartsRepresentable...) async throws -> GenerateContentResponse { return try await sendMessage([ModelContent(parts: parts)]) @@ -76,7 +80,10 @@ public class Chat { return result } - /// See ``sendMessageStream(_:)-4abs3``. + /// Sends a message using the existing history of this chat as context. If successful, the message + /// and response will be added to the history. If unsuccessful, history will remain unchanged. + /// - Parameter parts: The new content to send as a single chat message. + /// - Returns: A stream containing the model's response or an error if an error occurred. @available(macOS 12.0, *) public func sendMessageStream(_ parts: any ThrowingPartsRepresentable...) -> AsyncThrowingStream { diff --git a/Sources/GoogleAI/GenerativeAISwift.swift b/Sources/GoogleAI/GenerativeAISwift.swift index 46c9102..65b7199 100644 --- a/Sources/GoogleAI/GenerativeAISwift.swift +++ b/Sources/GoogleAI/GenerativeAISwift.swift @@ -17,10 +17,11 @@ import Foundation #warning("Only iOS, macOS, and Catalyst targets are currently fully supported.") #endif -/// Constants associated with the GenerativeAISwift SDK +/// Constants associated with the GenerativeAISwift SDK. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *) public enum GenerativeAISwift { /// String value of the SDK version public static let version = "0.5.2" + /// The Google AI backend endpoint URL. static let baseURL = "https://generativelanguage.googleapis.com" } diff --git a/Sources/GoogleAI/GenerativeModel.swift b/Sources/GoogleAI/GenerativeModel.swift index 7ed2016..ed1aecd 100644 --- a/Sources/GoogleAI/GenerativeModel.swift +++ b/Sources/GoogleAI/GenerativeModel.swift @@ -48,14 +48,14 @@ public final class GenerativeModel { /// Initializes a new remote model with the given parameters. /// /// - Parameters: - /// - name: The name of the model to use, e.g., `"gemini-1.5-pro-latest"`; see + /// - name: The name of the model to use, for example `"gemini-1.5-pro-latest"`; see /// [Gemini models](https://ai.google.dev/models/gemini) for a list of supported model names. /// - apiKey: The API key for your project. /// - generationConfig: The content generation parameters your model should use. /// - safetySettings: A value describing what types of harmful content your model should allow. /// - tools: A list of ``Tool`` objects that the model may use to generate the next response. /// - systemInstruction: Instructions that direct the model to behave a certain way; currently - /// only text content is supported, e.g., + /// only text content is supported, for example /// `ModelContent(role: "system", parts: "You are a cat. Your name is Neko.")`. /// - toolConfig: Tool configuration for any `Tool` specified in the request. /// - requestOptions Configuration parameters for sending requests to the backend. @@ -154,7 +154,7 @@ public final class GenerativeModel { /// [zero-shot](https://developers.google.com/machine-learning/glossary/generative#zero-shot-prompting) /// or "direct" prompts. For /// [few-shot](https://developers.google.com/machine-learning/glossary/generative#few-shot-prompting) - /// prompts, see ``generateContent(_:)-58rm0``. + /// prompts, see `generateContent(_ content: @autoclosure () throws -> [ModelContent])`. /// /// - Parameter content: The input(s) given to the model as a prompt (see /// ``ThrowingPartsRepresentable`` @@ -213,7 +213,7 @@ public final class GenerativeModel { /// [zero-shot](https://developers.google.com/machine-learning/glossary/generative#zero-shot-prompting) /// or "direct" prompts. For /// [few-shot](https://developers.google.com/machine-learning/glossary/generative#few-shot-prompting) - /// prompts, see ``generateContent(_:)-58rm0``. + /// prompts, see `generateContent(_ content: @autoclosure () throws -> [ModelContent])`. /// /// - Parameter content: The input(s) given to the model as a prompt (see /// ``ThrowingPartsRepresentable`` @@ -302,7 +302,7 @@ public final class GenerativeModel { /// [zero-shot](https://developers.google.com/machine-learning/glossary/generative#zero-shot-prompting) /// or "direct" prompts. For /// [few-shot](https://developers.google.com/machine-learning/glossary/generative#few-shot-prompting) - /// input, see ``countTokens(_:)-9spwl``. + /// input, see `countTokens(_ content: @autoclosure () throws -> [ModelContent])`. /// /// - Parameter content: The input(s) given to the model as a prompt (see /// ``ThrowingPartsRepresentable`` @@ -360,7 +360,7 @@ public final class GenerativeModel { } } -/// See ``GenerativeModel/countTokens(_:)-9spwl``. +/// An error thrown in `GenerativeModel.countTokens(_:)`. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *) public enum CountTokensError: Error { case internalError(underlying: Error) From 7afcf897889b6e3e5c6bd51ad29048dc60539643 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 8 May 2024 16:31:59 -0400 Subject: [PATCH 02/11] Make `text` computed property handle mixed-parts responses (#165) --- .../GoogleAI/GenerateContentResponse.swift | 10 ++++- ...y-success-function-call-mixed-content.json | 37 +++++++++++++++++ ...-success-function-call-parallel-calls.json | 40 +++++++++++++++++++ .../GoogleAITests/GenerativeModelTests.swift | 34 ++++++++++++++++ 4 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 Tests/GoogleAITests/GenerateContentResponses/unary-success-function-call-mixed-content.json create mode 100644 Tests/GoogleAITests/GenerateContentResponses/unary-success-function-call-parallel-calls.json diff --git a/Sources/GoogleAI/GenerateContentResponse.swift b/Sources/GoogleAI/GenerateContentResponse.swift index 683df0c..04c41f7 100644 --- a/Sources/GoogleAI/GenerateContentResponse.swift +++ b/Sources/GoogleAI/GenerateContentResponse.swift @@ -45,11 +45,17 @@ public struct GenerateContentResponse { Logging.default.error("Could not get text from a response that had no candidates.") return nil } - guard let text = candidate.content.parts.first?.text else { + let textValues: [String] = candidate.content.parts.compactMap { part in + guard case let .text(text) = part else { + return nil + } + return text + } + guard textValues.count > 0 else { Logging.default.error("Could not get a text part from the first candidate.") return nil } - return text + return textValues.joined(separator: " ") } /// Returns function calls found in any `Part`s of the first candidate of the response, if any. diff --git a/Tests/GoogleAITests/GenerateContentResponses/unary-success-function-call-mixed-content.json b/Tests/GoogleAITests/GenerateContentResponses/unary-success-function-call-mixed-content.json new file mode 100644 index 0000000..6e7ce27 --- /dev/null +++ b/Tests/GoogleAITests/GenerateContentResponses/unary-success-function-call-mixed-content.json @@ -0,0 +1,37 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "text": "The sum of [1, 2," + }, + { + "functionCall": { + "name": "sum", + "args": { + "y": 1, + "x": 2 + } + } + }, + { + "text": "3] is" + }, + { + "functionCall": { + "name": "sum", + "args": { + "y": 3, + "x": 3 + } + } + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0 + } + ] +} diff --git a/Tests/GoogleAITests/GenerateContentResponses/unary-success-function-call-parallel-calls.json b/Tests/GoogleAITests/GenerateContentResponses/unary-success-function-call-parallel-calls.json new file mode 100644 index 0000000..d535f8e --- /dev/null +++ b/Tests/GoogleAITests/GenerateContentResponses/unary-success-function-call-parallel-calls.json @@ -0,0 +1,40 @@ +{ + "candidates": [ + { + "content": { + "parts": [ + { + "functionCall": { + "name": "sum", + "args": { + "y": 1, + "x": 2 + } + } + }, + { + "functionCall": { + "name": "sum", + "args": { + "y": 3, + "x": 4 + } + } + }, + { + "functionCall": { + "name": "sum", + "args": { + "y": 5, + "x": 6 + } + } + } + ], + "role": "model" + }, + "finishReason": "STOP", + "index": 0 + } + ] +} diff --git a/Tests/GoogleAITests/GenerativeModelTests.swift b/Tests/GoogleAITests/GenerativeModelTests.swift index 9ed3401..ccd8979 100644 --- a/Tests/GoogleAITests/GenerativeModelTests.swift +++ b/Tests/GoogleAITests/GenerativeModelTests.swift @@ -254,6 +254,40 @@ final class GenerativeModelTests: XCTestCase { XCTAssertEqual(response.functionCalls, [functionCall]) } + func testGenerateContent_success_functionCall_parallelCalls() async throws { + MockURLProtocol + .requestHandler = try httpRequestHandler( + forResource: "unary-success-function-call-parallel-calls", + withExtension: "json" + ) + + let response = try await model.generateContent(testPrompt) + + XCTAssertEqual(response.candidates.count, 1) + let candidate = try XCTUnwrap(response.candidates.first) + XCTAssertEqual(candidate.content.parts.count, 3) + let functionCalls = response.functionCalls + XCTAssertEqual(functionCalls.count, 3) + } + + func testGenerateContent_success_functionCall_mixedContent() async throws { + MockURLProtocol + .requestHandler = try httpRequestHandler( + forResource: "unary-success-function-call-mixed-content", + withExtension: "json" + ) + + let response = try await model.generateContent(testPrompt) + + XCTAssertEqual(response.candidates.count, 1) + let candidate = try XCTUnwrap(response.candidates.first) + XCTAssertEqual(candidate.content.parts.count, 4) + let functionCalls = response.functionCalls + XCTAssertEqual(functionCalls.count, 2) + let text = try XCTUnwrap(response.text) + XCTAssertEqual(text, "The sum of [1, 2, 3] is") + } + func testGenerateContent_usageMetadata() async throws { MockURLProtocol .requestHandler = try httpRequestHandler( From 5d750b80651da9721c37c5eb1fc0b6750d1884d3 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 8 May 2024 20:17:47 -0400 Subject: [PATCH 03/11] Increment SDK version to `0.5.3` (#167) --- Sources/GoogleAI/GenerativeAISwift.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/GoogleAI/GenerativeAISwift.swift b/Sources/GoogleAI/GenerativeAISwift.swift index 65b7199..0dc14fc 100644 --- a/Sources/GoogleAI/GenerativeAISwift.swift +++ b/Sources/GoogleAI/GenerativeAISwift.swift @@ -21,7 +21,7 @@ import Foundation @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *) public enum GenerativeAISwift { /// String value of the SDK version - public static let version = "0.5.2" + public static let version = "0.5.3" /// The Google AI backend endpoint URL. static let baseURL = "https://generativelanguage.googleapis.com" } From 6f0f6c9a36e7db3e1858695f5658e1032bb47034 Mon Sep 17 00:00:00 2001 From: Ryan Wilson Date: Mon, 13 May 2024 12:34:40 -0400 Subject: [PATCH 04/11] Delete repo specific Issue Template (#169) The org's issue templates will be used instead at github.com/google-gemini/.github --- .github/ISSUE_TEMPLATE/bug_report.yml | 23 ---------------------- .github/ISSUE_TEMPLATE/feature_request.yml | 23 ---------------------- 2 files changed, 46 deletions(-) delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/ISSUE_TEMPLATE/feature_request.yml diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index c076e8c..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Bug report -description: Use this template to report bugs -labels: ["type:bug", "component:swift sdk"] -body: - - type: markdown - attributes: - value: > - **Note:** If this is a support question (e.g. _How do I do XYZ?_), please visit the [Build with Google AI Forum](https://discuss.ai.google.dev/). This is a great place to interact with developers, and to learn, share, and support each other. - - type: textarea - id: description - attributes: - label: > - Description of the bug: - - type: textarea - id: behavior - attributes: - label: > - Actual vs expected behavior: - - type: textarea - id: info - attributes: - label: > - Any other information you'd like to share? diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index c32ae77..0000000 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Feature request -description: Use this template to suggest a new feature -labels: ["type:feature request", "component:swift sdk"] -body: - - type: markdown - attributes: - value: > - **Note:** If this is a support question (e.g. _How do I do XYZ?_), please visit the [Build with Google AI Forum](https://discuss.ai.google.dev/). This is a great place to interact with developers, and to learn, share, and support each other. - - type: textarea - id: description - attributes: - label: > - Description of the feature request: - - type: textarea - id: behavior - attributes: - label: > - What problem are you trying to solve with this feature? - - type: textarea - id: info - attributes: - label: > - Any other information you'd like to share? From 812a514a1963993046172b4756f6da704d156937 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Wed, 15 May 2024 18:04:05 -0400 Subject: [PATCH 05/11] Add default `RequestOptions.timeout` of 300 seconds (#170) --- Sources/GoogleAI/GenerativeAIRequest.swift | 12 ++--- Sources/GoogleAI/GenerativeAIService.swift | 5 +- .../GoogleAITests/GenerativeModelTests.swift | 51 ++++++++++++++++++- 3 files changed, 55 insertions(+), 13 deletions(-) diff --git a/Sources/GoogleAI/GenerativeAIRequest.swift b/Sources/GoogleAI/GenerativeAIRequest.swift index 1afd65d..d468576 100644 --- a/Sources/GoogleAI/GenerativeAIRequest.swift +++ b/Sources/GoogleAI/GenerativeAIRequest.swift @@ -26,9 +26,8 @@ protocol GenerativeAIRequest: Encodable { /// Configuration parameters for sending requests to the backend. @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *) public struct RequestOptions { - /// The request’s timeout interval in seconds; if not specified uses the default value for a - /// `URLRequest`. - let timeout: TimeInterval? + /// The request’s timeout interval in seconds. + let timeout: TimeInterval /// The API version to use in requests to the backend. let apiVersion: String @@ -36,10 +35,9 @@ public struct RequestOptions { /// Initializes a request options object. /// /// - Parameters: - /// - timeout The request’s timeout interval in seconds; if not specified uses the default value - /// for a `URLRequest`. - /// - apiVersion The API version to use in requests to the backend; defaults to "v1beta". - public init(timeout: TimeInterval? = nil, apiVersion: String = "v1beta") { + /// - timeout: The request’s timeout interval in seconds; defaults to 300 seconds (5 minutes). + /// - apiVersion: The API version to use in requests to the backend; defaults to "v1beta". + public init(timeout: TimeInterval = 300.0, apiVersion: String = "v1beta") { self.timeout = timeout self.apiVersion = apiVersion } diff --git a/Sources/GoogleAI/GenerativeAIService.swift b/Sources/GoogleAI/GenerativeAIService.swift index 8d90473..0f32d6a 100644 --- a/Sources/GoogleAI/GenerativeAIService.swift +++ b/Sources/GoogleAI/GenerativeAIService.swift @@ -156,10 +156,7 @@ struct GenerativeAIService { let encoder = JSONEncoder() encoder.keyEncodingStrategy = .convertToSnakeCase urlRequest.httpBody = try encoder.encode(request) - - if let timeoutInterval = request.options.timeout { - urlRequest.timeoutInterval = timeoutInterval - } + urlRequest.timeoutInterval = request.options.timeout return urlRequest } diff --git a/Tests/GoogleAITests/GenerativeModelTests.swift b/Tests/GoogleAITests/GenerativeModelTests.swift index ccd8979..5a20343 100644 --- a/Tests/GoogleAITests/GenerativeModelTests.swift +++ b/Tests/GoogleAITests/GenerativeModelTests.swift @@ -611,6 +611,20 @@ final class GenerativeModelTests: XCTestCase { XCTAssertEqual(response.candidates.count, 1) } + func testGenerateContent_requestOptions_defaultTimeout() async throws { + let expectedTimeout = 300.0 // Default in timeout in RequestOptions() + MockURLProtocol + .requestHandler = try httpRequestHandler( + forResource: "unary-success-basic-reply-short", + withExtension: "json", + timeout: expectedTimeout + ) + + let response = try await model.generateContent(testPrompt) + + XCTAssertEqual(response.candidates.count, 1) + } + // MARK: - Generate Content (Streaming) func testGenerateContentStream_failureInvalidAPIKey() async throws { @@ -967,6 +981,25 @@ final class GenerativeModelTests: XCTestCase { XCTAssertEqual(responses, 1) } + func testGenerateContentStream_requestOptions_defaultTimeout() async throws { + let expectedTimeout = 300.0 // Default in timeout in RequestOptions() + MockURLProtocol + .requestHandler = try httpRequestHandler( + forResource: "streaming-success-basic-reply-short", + withExtension: "txt", + timeout: expectedTimeout + ) + + var responses = 0 + let stream = model.generateContentStream(testPrompt) + for try await content in stream { + XCTAssertNotNil(content.text) + responses += 1 + } + + XCTAssertEqual(responses, 1) + } + // MARK: - Count Tokens func testCountTokens_succeeds() async throws { @@ -1019,6 +1052,20 @@ final class GenerativeModelTests: XCTestCase { XCTAssertEqual(response.totalTokens, 6) } + func testCountTokens_requestOptions_defaultTimeout() async throws { + let expectedTimeout = 300.0 + MockURLProtocol + .requestHandler = try httpRequestHandler( + forResource: "success-total-tokens", + withExtension: "json", + timeout: expectedTimeout + ) + + let response = try await model.countTokens(testPrompt) + + XCTAssertEqual(response.totalTokens, 6) + } + // MARK: - Model Resource Name func testModelResourceName_noPrefix() async throws { @@ -1067,8 +1114,8 @@ final class GenerativeModelTests: XCTestCase { private func httpRequestHandler(forResource name: String, withExtension ext: String, statusCode: Int = 200, - timeout: TimeInterval = URLRequest - .defaultTimeoutInterval()) throws -> ((URLRequest) throws -> ( + timeout: TimeInterval = RequestOptions() + .timeout) throws -> ((URLRequest) throws -> ( URLResponse, AsyncLineSequence? )) { From 29fdb7773454a90c0f24b1e0728f7febba368ee1 Mon Sep 17 00:00:00 2001 From: Andrew Heard Date: Thu, 16 May 2024 10:24:42 -0400 Subject: [PATCH 06/11] Increment SDK version to `0.5.4` (#171) --- Sources/GoogleAI/GenerativeAISwift.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/GoogleAI/GenerativeAISwift.swift b/Sources/GoogleAI/GenerativeAISwift.swift index 0dc14fc..0eb6253 100644 --- a/Sources/GoogleAI/GenerativeAISwift.swift +++ b/Sources/GoogleAI/GenerativeAISwift.swift @@ -21,7 +21,7 @@ import Foundation @available(iOS 15.0, macOS 11.0, macCatalyst 15.0, *) public enum GenerativeAISwift { /// String value of the SDK version - public static let version = "0.5.3" + public static let version = "0.5.4" /// The Google AI backend endpoint URL. static let baseURL = "https://generativelanguage.googleapis.com" } From e8bbb72338acae67d51461ae13c507981cc5661e Mon Sep 17 00:00:00 2001 From: MING <54872601+1998code@users.noreply.github.com> Date: Thu, 16 May 2024 22:57:11 +0800 Subject: [PATCH 07/11] Sync UI from VertexAI for Firebase (#172) --- .../Screens/ConversationScreen.swift | 2 ++ .../Screens/FunctionCallingScreen.swift | 3 +++ .../Screens/PhotoReasoningScreen.swift | 13 ++++++++++ .../Screens/SummarizeScreen.swift | 24 ++++++++++++------- .../GenerativeAIUIComponents/InputField.swift | 4 ++-- .../MultimodalInputField.swift | 4 ++-- 6 files changed, 37 insertions(+), 13 deletions(-) diff --git a/Examples/GenerativeAISample/ChatSample/Screens/ConversationScreen.swift b/Examples/GenerativeAISample/ChatSample/Screens/ConversationScreen.swift index d9c9b62..d0bcde3 100644 --- a/Examples/GenerativeAISample/ChatSample/Screens/ConversationScreen.swift +++ b/Examples/GenerativeAISample/ChatSample/Screens/ConversationScreen.swift @@ -94,6 +94,8 @@ struct ConversationScreen: View { } private func sendOrStop() { + focusedField = nil + if viewModel.busy { viewModel.stop() } else { diff --git a/Examples/GenerativeAISample/FunctionCallingSample/Screens/FunctionCallingScreen.swift b/Examples/GenerativeAISample/FunctionCallingSample/Screens/FunctionCallingScreen.swift index 4848ec5..c7f4dd5 100644 --- a/Examples/GenerativeAISample/FunctionCallingSample/Screens/FunctionCallingScreen.swift +++ b/Examples/GenerativeAISample/FunctionCallingSample/Screens/FunctionCallingScreen.swift @@ -65,6 +65,9 @@ struct FunctionCallingScreen: View { } } }) + .onTapGesture { + focusedField = nil + } } InputField("Message...", text: $userPrompt) { Image(systemName: viewModel.busy ? "stop.circle.fill" : "arrow.up.circle.fill") diff --git a/Examples/GenerativeAISample/GenerativeAIMultimodalSample/Screens/PhotoReasoningScreen.swift b/Examples/GenerativeAISample/GenerativeAIMultimodalSample/Screens/PhotoReasoningScreen.swift index 98f3275..9302147 100644 --- a/Examples/GenerativeAISample/GenerativeAIMultimodalSample/Screens/PhotoReasoningScreen.swift +++ b/Examples/GenerativeAISample/GenerativeAIMultimodalSample/Screens/PhotoReasoningScreen.swift @@ -20,9 +20,17 @@ import SwiftUI struct PhotoReasoningScreen: View { @StateObject var viewModel = PhotoReasoningViewModel() + enum FocusedField: Hashable { + case message + } + + @FocusState + var focusedField: FocusedField? + var body: some View { VStack { MultimodalInputField(text: $viewModel.userInput, selection: $viewModel.selectedItems) + .focused($focusedField, equals: .message) .onSubmit { onSendTapped() } @@ -47,11 +55,16 @@ struct PhotoReasoningScreen: View { } } .navigationTitle("Multimodal sample") + .onAppear { + focusedField = .message + } } // MARK: - Actions private func onSendTapped() { + focusedField = nil + Task { await viewModel.reason() } diff --git a/Examples/GenerativeAISample/GenerativeAITextSample/Screens/SummarizeScreen.swift b/Examples/GenerativeAISample/GenerativeAITextSample/Screens/SummarizeScreen.swift index 8fbb89f..748c1ad 100644 --- a/Examples/GenerativeAISample/GenerativeAITextSample/Screens/SummarizeScreen.swift +++ b/Examples/GenerativeAISample/GenerativeAITextSample/Screens/SummarizeScreen.swift @@ -28,19 +28,23 @@ struct SummarizeScreen: View { var body: some View { VStack { - Text("Enter some text, then tap on _Go_ to summarize it.") - HStack(alignment: .top) { - TextField("Enter text summarize", text: $userInput, axis: .vertical) - .textFieldStyle(.roundedBorder) - .onSubmit { + VStack(alignment: .leading) { + Text("Enter some text, then tap on _Go_ to summarize it.") + .padding(.horizontal, 6) + HStack(alignment: .top) { + TextField("Enter text summarize", text: $userInput, axis: .vertical) + .focused($focusedField, equals: .message) + .textFieldStyle(.roundedBorder) + .onSubmit { + onSummarizeTapped() + } + Button("Go") { onSummarizeTapped() } - Button("Go") { - onSummarizeTapped() + .padding(.top, 4) } - .padding(.top, 4) } - .padding([.horizontal, .bottom]) + .padding(.horizontal, 16) List { HStack(alignment: .top) { @@ -61,6 +65,8 @@ struct SummarizeScreen: View { } private func onSummarizeTapped() { + focusedField = nil + Task { await viewModel.summarize(inputText: userInput) } diff --git a/Examples/GenerativeAISample/GenerativeAIUIComponents/Sources/GenerativeAIUIComponents/InputField.swift b/Examples/GenerativeAISample/GenerativeAIUIComponents/Sources/GenerativeAIUIComponents/InputField.swift index 3f12ea6..67941c3 100644 --- a/Examples/GenerativeAISample/GenerativeAIUIComponents/Sources/GenerativeAIUIComponents/InputField.swift +++ b/Examples/GenerativeAISample/GenerativeAIUIComponents/Sources/GenerativeAIUIComponents/InputField.swift @@ -60,10 +60,10 @@ public struct InputField