Skip to content

Commit 6f4c23a

Browse files
committed
✨ [AI]: Add streaming mode with incremental parsing to useGenerateObject
1 parent 75bad75 commit 6f4c23a

5 files changed

Lines changed: 209 additions & 56 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,7 @@ implementation("xyz.junerver.compose:ai:<latest_release>")
162162
| hook name | description |
163163
| --------- | ----------- |
164164
| [useChat](https://github.com/junerver/ComposeHooks/blob/master/app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseChatExample.kt) | A hook for managing chat conversations with OpenAI-compatible APIs and Anthropic Messages API, supporting streaming responses with typewriter effect. |
165-
| [useGenerateObject](https://github.com/junerver/ComposeHooks/blob/master/app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseGenerateObjectExample.kt) | A hook for generating structured data objects from AI responses, supporting multimodal input (text + images). |
165+
| [useGenerateObject](https://github.com/junerver/ComposeHooks/blob/master/app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseGenerateObjectExample.kt) | A hook for generating structured data objects from AI responses, supports streaming and incremental object updates. |
166166

167167
**Features:**
168168
- Streaming responses (SSE) with real-time typewriter effect

README.zh-CN.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,7 @@ implementation("xyz.junerver.compose:hai:<latest_release>")
169169
| Hook 名称 | 描述 |
170170
| --------- | ---- |
171171
| [useChat](https://github.com/junerver/ComposeHooks/blob/master/app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseChatExample.kt) | 用于管理与 OpenAI 兼容 API 和 Anthropic Messages API 聊天对话的 Hook,支持流式响应的打字机效果。 |
172-
| [useGenerateObject](https://github.com/junerver/ComposeHooks/blob/master/app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseGenerateObjectExample.kt) | 用于从 AI 响应生成结构化数据对象的 Hook,支持多模态输入(文本 + 图片)|
172+
| [useGenerateObject](https://github.com/junerver/ComposeHooks/blob/master/app/src/commonMain/kotlin/xyz/junerver/composehooks/example/UseGenerateObjectExample.kt) | 用于从 AI 响应生成结构化数据对象的 Hook,支持流式与增量更新对象|
173173

174174
**功能特性:**
175175
- 流式响应 (SSE),实时打字机效果

ai/src/commonMain/kotlin/xyz/junerver/compose/ai/usegenerateobject/GenerateObjectOptions.kt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ typealias OnObjectFinishCallback<T> = (obj: T, usage: ChatUsage?) -> Unit
3434
* @property timeout Request timeout duration
3535
* @property headers Additional HTTP headers to send with requests
3636
* @property httpEngine Custom HTTP engine (null = use global default)
37+
* @property stream Whether to use streaming responses (default true)
38+
* @property enableIncrementalParsing Whether to parse partial JSON during streaming (default true)
3739
* @property enableJsonHealing Enable JSON repair mechanism (default true)
3840
* @property onResponse Callback when receiving an HTTP response
3941
* @property onFinish Callback when object generation is complete
@@ -49,6 +51,8 @@ data class GenerateObjectOptions<T> internal constructor(
4951
override var timeout: Duration = AIOptionsDefaults.DEFAULT_TIMEOUT,
5052
override var headers: Map<String, String> = AIOptionsDefaults.DEFAULT_HEADERS,
5153
override var httpEngine: HttpEngine? = null,
54+
var stream: Boolean = true,
55+
var enableIncrementalParsing: Boolean = true,
5256
/**
5357
* Enable JSON repair mechanism, default is true.
5458
*

ai/src/commonMain/kotlin/xyz/junerver/compose/ai/usegenerateobject/useGenerateObject.kt

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ import xyz.junerver.compose.ai.usechat.TextPart
1616
import xyz.junerver.compose.ai.usechat.UserContentPart
1717
import xyz.junerver.compose.ai.usechat.useChat
1818
import xyz.junerver.compose.hooks._useState
19+
import xyz.junerver.compose.hooks.useEffect
1920
import xyz.junerver.compose.hooks.useLatestRef
2021

2122
/*
@@ -232,6 +233,8 @@ fun <T : Any> useGenerateObject(
232233
maxTokens = optionsRef.current.maxTokens
233234
timeout = optionsRef.current.timeout
234235
headers = optionsRef.current.headers
236+
httpEngine = optionsRef.current.httpEngine
237+
stream = optionsRef.current.stream
235238
onResponse = optionsRef.current.onResponse
236239

237240
onFinish = { message, usage, _ ->
@@ -263,6 +266,22 @@ fun <T : Any> useGenerateObject(
263266
}
264267
}
265268

269+
useEffect(rawJsonState.value, chatHolder.isLoading.value) {
270+
val rawJson = rawJsonState.value
271+
if (rawJson.isBlank()) return@useEffect
272+
if (!chatHolder.isLoading.value) return@useEffect
273+
if (!optionsRef.current.stream) return@useEffect
274+
if (!optionsRef.current.enableIncrementalParsing) return@useEffect
275+
276+
try {
277+
val cleanJson = healJson(rawJson, optionsRef.current.enableJsonHealing)
278+
val obj = defaultJson.decodeFromString(serializerRef.current, cleanJson)
279+
parsedObject.value = obj
280+
} catch (_: Exception) {
281+
// Ignore incremental parse failures during streaming
282+
}
283+
}
284+
266285
// Combine chat error with parse error
267286
val combinedError = remember {
268287
derivedStateOf {

0 commit comments

Comments
 (0)