ComposeAI: マルチプラットフォーム対応ChatGPT類似アプリ、でもどうやって？その技術を大公開！

# はじめに

皆さん、ごきげんよう！れぶです！

今回の記事では、ChatGPTと類似した`ComposeAI`というオープンソースアプリの解説をしていきます。

ぶっちゃけこのアプリを参考にすることで、ChatGPTのようなアプリをAndroidとiOS両方で作れてしまいます。なので、この記事では**ComposeAIアプリの参考資料として技術的な概要を整理していきます**。

約7分以内で読めます。それでは、参りましょう！！

# この記事の対象者

以下二つに当てはまる方に特に読んで頂きたいです。

* ChatGPTのようなチャットボットアプリを、AndroidとiOS両方で同時に作りたい方
* `Kotlin`や`KMM`に一通り精通してる方

# Githubリポジトリ
https://github.com/EBfVince/ComposeAI

# 設計概要

* 対応OS
  * Android
  * iOS
* 言語
  * Kotlin
* アーキテクチャ
  * UI Layer/Data Layer
* 使用技術
  * UI：`Compose Multiplatform`
  * ロジック：`KMM`
  * デザイン：`Material 3`
  * OpenAI APIクライアント：`openai-kotlin`
  * ナビゲーション：`Voyager`
  * DI：`Koin`
  * SQLiteデータベース：`SQLDelight`
  * キーと値のデータ保持：`Multiplatform Settings`
  * 画像表示：`Im​​ageLoader`
  * リソース生成：`Libre`
  * ログ：`Napier`
  * BuildConfig：`BuildKonfig`
  * Firebase：`Analytics`/`Crashlytics`
  * 広告：`AdMob`
* 機能
  * オンボーディング画面の表示
  * プロンプトの入力
  * プロンプトに対する回答の生成
  * プロンプトに対する回答のコピー
  * プロンプトに対する回答のシェア
  * スレッドのタイトルの表示
  * スレッドの追加・選択・更新
  * Firebase連携
  * コインによる広告収益
* 画面
  * オンボーディング
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456250/665277ee-5292-fb6f-0e29-99ddea41e265.jpeg" width=50%>

  * チャット
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456250/84389b14-3b0b-2123-2ff8-d56b8f3a076d.jpeg" width=50%>

  * スレッド管理ドロワー
  <img src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456250/d3a76103-1295-4fb0-2bdd-4494f5f91176.jpeg" width=50%>

# フォルダ構造

今回はロジックやUIが共通化されている`shared/scr`フォルダ内（一部省く）に絞って説明します。

## commonMain

![スクリーンショット 2023-07-01 18.08.20.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456250/b4d26c3f-d22f-0114-b2f8-bdae319d831d.png)

### commonMain/kotlin

| フォルダ（ファイル）名 | 主な内容 |
|:--|:--|
| analytics  |Firebase Analytics関連の処理やデータを定義   | 
| data  | ・アプリ内で扱うローカルとリモートのデータ関連<br>・DB操作/API通信処理を行うRepositoryを定義 | 
| di  | Koinライブラリによるモジュール定義  | 
| expect  | expectを付与したメソッドやプロパティを定義  | 
| extendedspans  | 表示するテキストの特定の部分における描画や動作方法を定義  | 
| markdown  | 出力結果をマークダウン形式で表現するためのデータや表示方法を定義  | 
| model  | アプリで扱うデータ構造を定義| 
| ui  | オンボーディング画面/チャット画面を構成するUI(Composable関数)を定義  | 
| util  | expectを付与したユーティリティメソッドを定義  | 
| App.kt  | アプリのメインエントリーポイントとなるUI（Composable関数）を定義  | 
| AppScreenModel.kt  | ・画面（App.kt）の状態を管理<br>・ユーザーの選択に基づいて適切な画面を表示  | 

### commonMain/sqldelight
* `SQLDelight`ライブラリ関連
* チャットとチャットメッセージのデータを操作するSQLを定義

## androidMain/kotlin

![スクリーンショット 2023-07-01 18.08.52.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456250/5b8633fd-16e2-b681-8d93-ad95a54b7958.png)

* Android固有の実装を記述
* commonMain/kotlin/のフォルダと対応
* main.android.kt
  * commonMain/kotlin/App.ktで定義した`Composable関数`を呼び出し
  * システムUIの初期設定

## iosMain/kotlin

![スクリーンショット 2023-07-01 18.09.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2456250/5ecc5cbc-b6ef-48e6-0d43-976903387d0b.png)

* iOS固有の実装を記述
* commonMain/kotlin/のフォルダと対応
* main.ios.kt
  * commonMain/kotlin/App.ktで定義した`Composable関数`を呼び出し
  * `ComposeUIViewController`でUIKitやSwiftUI製のUIをComposeに変換
* NapierProxy.kt
  * iOS専用の`Napier`を使用したログ設定

# API処理

`openai-kotlin`を使用してAPIを叩くことで、AIモデルによる文章生成等を可能にしています。その応答結果を`SQLDelight`によってDB管理するのが、このチャットボットアプリの基本構造になっています。

例えば、以下のメソッドは指定されたチャット（スレッド）に対して適切なタイトルを生成します。`openAI.chatCompletion(request)`でAPIに対してチャットの完了リクエストを送信します。これにより、AIモデルからの応答が得られます。

```shared/src/commonMain/kotlin/data/repository/ChatMessageRepository.kt
suspend fun generateTitleFromChat(
        chatId: String,
    ): Result<String> = suspendRunCatching(defaultDispatcher) {
        val instruction = ChatMessage(
            role = ChatRole.System,
            content = "What would be a short and relevant title for this chat? You must strictly answer with only the title, no other text is allowed.",
        )

        val messages = chatMessageQueries.getChatMessagesWithChatId(chatId)
            .executeAsList()
            .map(ChatMessageEntity::asModel)

        val request = ChatCompletionRequest(
            model = ModelId("gpt-3.5-turbo"),
            messages = messages + instruction,
        )

        val response = openAI.chatCompletion(request)
        response.choices.first().message?.content ?: "?"
    }
```

また、以下の処理はユーザーが入力したメッセージをAPIに送信し、それに対するAIモデルからの応答を取得して、逐次データベースに保存しています。`chatCompletions()`を呼び出すことで、`Flow`型のデータを受け取ることができます。

```shared/src/commonMain/kotlin/data/repository/ChatMessageRepository.kt
// Create request to OpenAI
val request = ChatCompletionRequest(
    model = ModelId("gpt-3.5-turbo"),
    messages = messagesToSend,
)

// Get assistant response
var assistantMessage = ""

Try {
    // Sending request to OpenAI
    openAI.chatCompletions(request).collect { chunk ->
        chunk.choices.first().delta?.content?.let {
            assistantMessage += it
            chatMessageQueries.updateChatMessageContent(
                id = assistantMessageId,
                content = assistantMessage,
            )
        }
    }
    ...
}
```

# おわりに
今回は`ComposeAI`というChatGPT類似アプリに関して、技術的な概要を設計・フォルダ構成・API処理の観点からまとめていきました。

今回紹介した**アプリの設計やコードを参考にすることで、ChatGPTのようなアプリをAndroidとiOS両方に対応して作れます**。また、KotlinによるOpenAI APIの扱い方も参考になるかと思います。

この記事がAndroid開発者/iOS開発者にとって少しでも役立つと嬉しいです。ありがとうございました。

# 参考サイト
https://www.jetbrains.com/ja-jp/lp/compose-multiplatform/

https://qiita.com/y-mimura/items/9ffdce14dc4ed6110343

https://qiita.com/y-mimura/items/68456c9f625a4e481300?utm_campaign=popular_items&utm_medium=feed&utm_source=popular_items

https://github.com/aallam/openai-kotlin