
Fully-featured chat UI with streaming replies, model picker, voice input, image attachments/generation, web search and durable local history; protocol, SSE engine and analytics integrated.
Compose Multiplatform chat UI and client for chatbase — streaming replies, model picker, voice input, image attachments, image generation, web search and local history.
English | 简体中文
The server half is chatbase, an npm library that runs inside your own Cloudflare Worker. This library is the whole client side: the screen, the SSE engine, the Room-backed history and the wire protocol. Your app provides sign-in, the Pro decision and a few preferences through one interface; the SDK never sees a user or a subscription.
| Platform | Status |
|---|---|
| Android | In production (TrendingAI) |
| iOS | In production, consumed from a Kotlin Multiplatform host |
ChatScreen(onBack = ...) renders threads, the input bar, the model picker, Markdown with code highlighting, images and the quota / Pro gates. The host only paints the shell around it./api/chat/* endpoints that chatbase serves. The host never builds a request.chat.db) with versioned schema and migration tests.ChatHost asks the app whether the user is signed in and whether they are Pro, and asks it to open the sign-in and paywall screens. The SDK does not store tokens or know about products.ChatAiEvent; the host maps them into its own vocabulary.1. Add the dependency. The SDK brings its own Ktor engine per platform.
dependencies {
implementation("wang.harlon:chatbase-kmp:<version>")
}2. Implement ChatHost and install it before any chat UI is reached, in Application.onCreate on Android and in the MainViewController factory on iOS.
object MyChatHost : ChatHost {
override val apiBaseUrl = "https://api.example.com/api"
override val canSignIn = true
override fun isLoggedInNow() = auth.state.value is LoggedIn
override val isLoggedIn = auth.state.map { it is LoggedIn }
override fun signIn(source: String) = auth.signIn(source)
override fun openPaywall(source: String) = Paywall.open(source)
override fun reportGeneratedImage(imageUrl: String) = Feedback.open(imageUrl)
override fun currentIsPro() = settings.isPro
override val isPro = settings.isProFlow
override val chatModelChoice = settings.chatModelFlow
override fun currentChatModelChoice() = settings.chatModel
override fun pinChatModel(id: String) = settings.setChatModel(id)
override fun followServerDefault() = settings.setChatModel(FOLLOW_SERVER_DEFAULT)
override fun imagesMaxCount() = 9
override fun imagesPerImageJpegKb() = 300
override fun installId() = settings.installId
override suspend fun requestLang() = if (settings.language == "zh") "zh" else "en"
override fun configureHttpAuth(config: HttpClientConfig<*>) = config.installAuth(auth)
override fun onAiEvent(event: ChatAiEvent) = analytics.track(event)
override val proBadge: (@Composable () -> Unit)? = { ProBadge() }
}
fun installChatHost() { chatHost = MyChatHost }3. Warm the model catalog at startup and show the screen.
ChatModelsProvider.warmUp(appScope)
ChatScreen(
onBack = { navController.popBackStack() },
suggestions = listOf(ChatSuggestion(label = "What can you do?", prompt = "What can you do?")),
)A minimal host that runs without any backend is in sample/.
Documented with the server: chatbase docs/chat-protocol.md. Endpoints are relative to ChatHost.apiBaseUrl.
./gradlew :library:testAndroidHostTest runs the common and Android tests under Robolectric.library/native/build-cmark.sh builds the static libraries with CMake on first use; they are packed into the klib, so consumers need nothing extra.chatbase-kmp.dir=<path> to the app's local.properties if the app supports composite builds through gradle/composite-substitutions.MIT. cmark-gfm is bundled under its own license, see library/native/cmark-gfm-COPYING.
Compose Multiplatform chat UI and client for chatbase — streaming replies, model picker, voice input, image attachments, image generation, web search and local history.
English | 简体中文
The server half is chatbase, an npm library that runs inside your own Cloudflare Worker. This library is the whole client side: the screen, the SSE engine, the Room-backed history and the wire protocol. Your app provides sign-in, the Pro decision and a few preferences through one interface; the SDK never sees a user or a subscription.
| Platform | Status |
|---|---|
| Android | In production (TrendingAI) |
| iOS | In production, consumed from a Kotlin Multiplatform host |
ChatScreen(onBack = ...) renders threads, the input bar, the model picker, Markdown with code highlighting, images and the quota / Pro gates. The host only paints the shell around it./api/chat/* endpoints that chatbase serves. The host never builds a request.chat.db) with versioned schema and migration tests.ChatHost asks the app whether the user is signed in and whether they are Pro, and asks it to open the sign-in and paywall screens. The SDK does not store tokens or know about products.ChatAiEvent; the host maps them into its own vocabulary.1. Add the dependency. The SDK brings its own Ktor engine per platform.
dependencies {
implementation("wang.harlon:chatbase-kmp:<version>")
}2. Implement ChatHost and install it before any chat UI is reached, in Application.onCreate on Android and in the MainViewController factory on iOS.
object MyChatHost : ChatHost {
override val apiBaseUrl = "https://api.example.com/api"
override val canSignIn = true
override fun isLoggedInNow() = auth.state.value is LoggedIn
override val isLoggedIn = auth.state.map { it is LoggedIn }
override fun signIn(source: String) = auth.signIn(source)
override fun openPaywall(source: String) = Paywall.open(source)
override fun reportGeneratedImage(imageUrl: String) = Feedback.open(imageUrl)
override fun currentIsPro() = settings.isPro
override val isPro = settings.isProFlow
override val chatModelChoice = settings.chatModelFlow
override fun currentChatModelChoice() = settings.chatModel
override fun pinChatModel(id: String) = settings.setChatModel(id)
override fun followServerDefault() = settings.setChatModel(FOLLOW_SERVER_DEFAULT)
override fun imagesMaxCount() = 9
override fun imagesPerImageJpegKb() = 300
override fun installId() = settings.installId
override suspend fun requestLang() = if (settings.language == "zh") "zh" else "en"
override fun configureHttpAuth(config: HttpClientConfig<*>) = config.installAuth(auth)
override fun onAiEvent(event: ChatAiEvent) = analytics.track(event)
override val proBadge: (@Composable () -> Unit)? = { ProBadge() }
}
fun installChatHost() { chatHost = MyChatHost }3. Warm the model catalog at startup and show the screen.
ChatModelsProvider.warmUp(appScope)
ChatScreen(
onBack = { navController.popBackStack() },
suggestions = listOf(ChatSuggestion(label = "What can you do?", prompt = "What can you do?")),
)A minimal host that runs without any backend is in sample/.
Documented with the server: chatbase docs/chat-protocol.md. Endpoints are relative to ChatHost.apiBaseUrl.
./gradlew :library:testAndroidHostTest runs the common and Android tests under Robolectric.library/native/build-cmark.sh builds the static libraries with CMake on first use; they are packed into the klib, so consumers need nothing extra.chatbase-kmp.dir=<path> to the app's local.properties if the app supports composite builds through gradle/composite-substitutions.MIT. cmark-gfm is bundled under its own license, see library/native/cmark-gfm-COPYING.