ChatGPTとGASを用いたiOSアプリを作成してみた。(Qiita × Fast DOCTOR Health Tech Hackathonの備忘録)

「Qiita × Fast DOCTOR Health Tech Hackathon」に参加して、SwiftとGASでChatGPTを用いたiOSアプリを作成したのですが、ただ感想を書いてもつまらないので、
開発中に引っかかったポイント(SwiftとGASを用いたアプリ開発で気をつけたい点)と、作成したGASやChatGPTを用いたiOSアプリを作る上で、汎用性の高いSwiftの関数をご紹介します。

開発には、GASとXcodeを使用しました。

# 1. SwiftとGASを用いたアプリ開発で気をつけたい点
## 1.1. 文字の型に気をつける
swiftからGASにリクエストして、スプレッドシートなどを参照する際に、数字を扱う場合、swiftのリクエストは文字列型ですが、GASが取得してくる数字は数値型なので、GASがスプレッドシートなどから取得した値を文字列型に変換してからswiftのリクエストを処理する必要があります。

## 1.2. 日本語を扱う場合、エンコードとデコードが必要
swiftからGASに日本語を送って処理を行う場合、swiftで日本語をエンコードしてGASに送り、GASで受け取った日本語をデコードする必要があります。そうしないと空の文字列として処理されます。
GASからswiftに日本語を送る場合には、そのままで良いようです。

# 2. GASやChatGPTを用いたiOSアプリを作る上で、汎用性の高いswiftの関数
## 2.1. HTTP POSTリクエストを行う関数
以下の関数を用いる事で、HTTP POSTリクエストをiOSアプリから行えます。
```swift
struct HTTPRequest {
    static func post(url: String, params: [String: String], completion: @escaping (Data?, URLResponse?, Error?) -> Void) {
        guard let url = URL(string: url) else { return }

        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.httpBody = params
            .map { key, value in "\(key)=\(value)" }
            .joined(separator: "&")
            .data(using: .utf8)

        URLSession.shared.dataTask(with: request, completionHandler: completion).resume()
    }
}
```

## 2.2. ChatGPTでテキスト生成する関数
以下の関数を用いる事で、swiftからOpenAI APIにリクエストを行いChatGPTでテキスト生成が出来ます。
この関数には、ChatGPTに記憶を持たせるアシストを組み込んでいませんが、リクエスト部分に含めることで実装可能です。
```swift
struct GPT3Message: Codable {
    let role: String
    let content: String
}

struct GPT3Payload: Codable {
    let model: String
    let messages: [GPT3Message]
}

func chatWithGPT3(apiKey: String, system: String, text: String) async throws -> String {
    let url = URL(string: "https://api.openai.com/v1/chat/completions")!
    var request = URLRequest(url: url)
    request.httpMethod = "POST"
    request.addValue("Bearer \(apiKey)", forHTTPHeaderField: "Authorization")
    request.addValue("application/json", forHTTPHeaderField: "Content-Type")

    let systemMessage = GPT3Message(role: "system", content: system)
    let userMessage = GPT3Message(role: "user", content: text)
    let payload = GPT3Payload(model: "gpt-3.5-turbo", messages: [systemMessage, userMessage])
    
    do {
        let jsonData = try JSONEncoder().encode(payload)
        request.httpBody = jsonData
    } catch {
        print("Error: \(error)")
        throw error
    }
    
    let (data, response) = try await URLSession.shared.data(for: request)

    if let httpResponse = response as? HTTPURLResponse,
       httpResponse.statusCode != 200 {
        throw URLError(.badServerResponse)
    }
    
    do {
        let jsonObject = try JSONSerialization.jsonObject(with: data, options: [])
        if let jsonDict = jsonObject as? [String: Any],
           let choices = jsonDict["choices"] as? [[String: Any]],
           let firstChoice = choices.first,
           let message = firstChoice["message"] as? [String: Any],
           let content = message["content"] as? String {
            return content
        }
    } catch {
        print("Error: \(error)")
        throw error
    }

    return ""
}
```

# 最後に
Qiita × Fast DOCTOR Health Tech Hackathonはとても楽しいイベントでした。
良い人ばかりで、尚且つ、頭も良い方が集まっていたので、心地良かったです。都合が合えば、また参加させて頂きたいと思ってます。