Изменения вводят live-канал OnlineOutbox и фоновую синхронизацию диалогов.
This commit is contained in:
@@ -8,6 +8,7 @@ import pw.binom.agentik.proto.Agent
|
||||
import pw.binom.agentik.proto.Content
|
||||
import pw.binom.agentik.proto.Conversation
|
||||
import pw.binom.agentik.outbox.Event
|
||||
import pw.binom.agentik.outbox.OnlineEvent
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
import kotlin.time.Instant
|
||||
|
||||
@@ -34,6 +35,9 @@ internal class TuiBackend(
|
||||
/** Активная джоба подписки на [Conversation.events]. */
|
||||
private var eventsJob: Job? = null
|
||||
|
||||
/** Активная джоба подписки на онлайн-поток (стриминг ответа). */
|
||||
private var onlineJob: Job? = null
|
||||
|
||||
/** Последний виденный момент событий — для переподписки при reconnect. */
|
||||
private var lastSeenAt: Instant = Instant.DISTANT_PAST
|
||||
|
||||
@@ -92,41 +96,42 @@ internal class TuiBackend(
|
||||
}
|
||||
|
||||
/**
|
||||
* Подписывается на `outbox.conversationEvents(after, conv.id)` и перенаправляет их в [state].
|
||||
* Подписывается на durable-поток `outbox.conversationEvents(after, conv.id)`
|
||||
* и live-поток `onlineOutbox.onlineEvents(conv.id)`; оба перенаправляет в [state].
|
||||
*
|
||||
* Онлайн-поток live-only (без catchup), поэтому подписку открываем ДО [Conversation.send]
|
||||
* (см. [ensureConversation] → [onUserMessage]), чтобы не упустить начало хода.
|
||||
*/
|
||||
private fun subscribeEvents(conv: Conversation, from: Instant) {
|
||||
eventsJob?.cancel()
|
||||
eventsJob = scope.launch {
|
||||
agent.outbox.conversationEvents(from, conv.id).collect { ce -> dispatch(ce.event) }
|
||||
}
|
||||
onlineJob?.cancel()
|
||||
onlineJob = scope.launch {
|
||||
agent.onlineOutbox.onlineEvents(conv.id).collect { ev -> dispatchOnline(ev) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Маппинг [Event] → [AppState] (что показать в TUI).
|
||||
* Маппинг [Event] (durable) → [AppState] (что показать в TUI).
|
||||
*
|
||||
* - AppendText → дописывает в последний ассистентский чанк
|
||||
* - StartReasoning / StartResponse → новый streaming-чанк
|
||||
* - End → закрывает streaming
|
||||
* - Interrupted → закрывает streaming + системное сообщение
|
||||
* - ToolCall / ToolResult → сообщения в историю
|
||||
* - Error → системное сообщение
|
||||
*
|
||||
* Стриминг ответа (дельты текста/картинок) приходит отдельным потоком —
|
||||
* см. [dispatchOnline].
|
||||
*/
|
||||
private fun dispatch(ev: Event) {
|
||||
lastSeenAt = ev.date
|
||||
when (ev) {
|
||||
is Event.AppendText -> state.appendAssistant(ev.body)
|
||||
is Event.StartReasoning -> {
|
||||
state.postSystem("… думаю")
|
||||
}
|
||||
is Event.StartResponse -> state.setStreaming(true)
|
||||
is Event.End -> state.finishAssistant()
|
||||
is Event.Interrupted -> {
|
||||
state.finishAssistant()
|
||||
state.postSystem("прервано")
|
||||
}
|
||||
is Event.AppendImage -> {
|
||||
state.postSystem("[картинка: ${ev.mime}, ${ev.body.size} байт]")
|
||||
}
|
||||
is Event.ToolCall -> {
|
||||
state.postToolCall(toolName = ev.toolName, title = null, args = ev.toolArgs)
|
||||
}
|
||||
@@ -139,4 +144,20 @@ internal class TuiBackend(
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Маппинг [OnlineEvent] (стриминг ответа, live-only) → [AppState].
|
||||
*
|
||||
* - AppendText → дописывает в последний ассистентский чанк
|
||||
* - StartReasoning / StartResponse → новый streaming-чанк
|
||||
* - AppendImage → системное сообщение-заглушка
|
||||
*/
|
||||
private fun dispatchOnline(ev: OnlineEvent) {
|
||||
when (ev) {
|
||||
is OnlineEvent.AppendText -> state.appendAssistant(ev.body)
|
||||
is OnlineEvent.StartReasoning -> state.postSystem("… думаю")
|
||||
is OnlineEvent.StartResponse -> state.setStreaming(true)
|
||||
is OnlineEvent.AppendImage -> state.postSystem("[картинка: ${ev.mime}, ${ev.body.size} байт]")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user