@flamingo-stack/openframe-frontend-core 0.0.207 → 0.0.208
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{chunk-Z3GQGR5E.js → chunk-2HMZSCJY.js} +3158 -2074
- package/dist/chunk-2HMZSCJY.js.map +1 -0
- package/dist/chunk-4XLJWX2N.js +12 -0
- package/dist/chunk-4XLJWX2N.js.map +1 -0
- package/dist/{chunk-APM6KBPU.cjs → chunk-C5EC5AZM.cjs} +1644 -560
- package/dist/chunk-C5EC5AZM.cjs.map +1 -0
- package/dist/chunk-VFKQMAUF.cjs +12 -0
- package/dist/chunk-VFKQMAUF.cjs.map +1 -0
- package/dist/components/chat/embeddable-chat.d.ts +35 -2
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/index.d.ts +3 -0
- package/dist/components/chat/hooks/index.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-embedded-chat.d.ts +10 -169
- package/dist/components/chat/hooks/use-embedded-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +85 -0
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +124 -0
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-unified-chat.d.ts +33 -0
- package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -0
- package/dist/components/chat/index.cjs +8 -2
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +11 -5
- package/dist/components/chat/types/index.d.ts +1 -0
- package/dist/components/chat/types/index.d.ts.map +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +185 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -0
- package/dist/components/features/index.cjs +3 -2
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +2 -1
- package/dist/components/index.cjs +26 -2
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +27 -3
- package/dist/components/navigation/index.cjs +3 -2
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.js +2 -1
- package/dist/components/shared/delivery/delivery-lists.d.ts +16 -0
- package/dist/components/shared/delivery/delivery-lists.d.ts.map +1 -0
- package/dist/components/shared/delivery/delivery-table.d.ts +12 -0
- package/dist/components/shared/delivery/delivery-table.d.ts.map +1 -0
- package/dist/components/shared/delivery/index.d.ts +3 -0
- package/dist/components/shared/delivery/index.d.ts.map +1 -0
- package/dist/components/shared/dev-section/dev-section-page.d.ts +31 -0
- package/dist/components/shared/dev-section/dev-section-page.d.ts.map +1 -0
- package/dist/components/shared/dev-section/dev-section-view.d.ts +34 -0
- package/dist/components/shared/dev-section/dev-section-view.d.ts.map +1 -0
- package/dist/components/shared/dev-section/index.d.ts +3 -0
- package/dist/components/shared/dev-section/index.d.ts.map +1 -0
- package/dist/components/shared/legal-document/index.d.ts +10 -0
- package/dist/components/shared/legal-document/index.d.ts.map +1 -0
- package/dist/components/shared/legal-document/legal-document-page.d.ts +66 -0
- package/dist/components/shared/legal-document/legal-document-page.d.ts.map +1 -0
- package/dist/components/shared/legal-document/use-legal-docs.d.ts +40 -0
- package/dist/components/shared/legal-document/use-legal-docs.d.ts.map +1 -0
- package/dist/components/shared/product-release/index.d.ts +2 -1
- package/dist/components/shared/product-release/index.d.ts.map +1 -1
- package/dist/components/shared/product-release/release-detail-page.d.ts +11 -7
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/shared/roadmap/index.d.ts +18 -0
- package/dist/components/shared/roadmap/index.d.ts.map +1 -0
- package/dist/components/shared/roadmap/roadmap-grid-skeleton.d.ts +24 -0
- package/dist/components/shared/roadmap/roadmap-grid-skeleton.d.ts.map +1 -0
- package/dist/components/shared/roadmap/roadmap-grid.d.ts +18 -0
- package/dist/components/shared/roadmap/roadmap-grid.d.ts.map +1 -0
- package/dist/components/shared/roadmap/use-roadmap-voting.d.ts +25 -0
- package/dist/components/shared/roadmap/use-roadmap-voting.d.ts.map +1 -0
- package/dist/components/ui/index.cjs +8 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +11 -5
- package/dist/components/ui/release-changelog-section.d.ts +13 -2
- package/dist/components/ui/release-changelog-section.d.ts.map +1 -1
- package/dist/embed-shims/index.cjs +1 -6
- package/dist/embed-shims/index.cjs.map +1 -1
- package/dist/embed-shims/index.js +1 -6
- package/dist/embed-shims/index.js.map +1 -1
- package/dist/index.cjs +18 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +19 -3
- package/dist/types/delivery.d.ts +49 -0
- package/dist/types/delivery.d.ts.map +1 -0
- package/dist/types/index.cjs +13 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +12 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/dev-sections/index.d.ts +11 -0
- package/dist/utils/dev-sections/index.d.ts.map +1 -0
- package/dist/utils/dev-sections/openframe-dev-sections.d.ts +209 -0
- package/dist/utils/dev-sections/openframe-dev-sections.d.ts.map +1 -0
- package/dist/utils/index.cjs +82 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +81 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/embeddable-chat.tsx +123 -8
- package/src/components/chat/hooks/index.ts +9 -2
- package/src/components/chat/hooks/use-embedded-chat.ts +18 -1016
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +372 -0
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +1058 -0
- package/src/components/chat/hooks/use-unified-chat.ts +171 -0
- package/src/components/chat/types/index.ts +1 -0
- package/src/components/chat/types/unified-chat-state.types.ts +215 -0
- package/src/components/index.ts +8 -0
- package/src/components/shared/delivery/delivery-lists.tsx +199 -0
- package/src/components/shared/delivery/delivery-table.tsx +174 -0
- package/src/components/shared/delivery/index.ts +9 -0
- package/src/components/shared/dev-section/dev-section-page.tsx +72 -0
- package/src/components/shared/dev-section/dev-section-view.tsx +129 -0
- package/src/components/shared/dev-section/index.ts +2 -0
- package/src/components/shared/legal-document/index.ts +19 -0
- package/src/components/shared/legal-document/legal-document-page.tsx +178 -0
- package/src/components/shared/legal-document/use-legal-docs.ts +123 -0
- package/src/components/shared/product-release/index.ts +14 -3
- package/src/components/shared/product-release/release-detail-page.tsx +45 -7
- package/src/components/shared/roadmap/index.ts +23 -0
- package/src/components/shared/roadmap/roadmap-grid-skeleton.tsx +74 -0
- package/src/components/shared/roadmap/roadmap-grid.tsx +106 -0
- package/src/components/shared/roadmap/use-roadmap-voting.ts +163 -0
- package/src/components/ui/release-changelog-section.tsx +113 -32
- package/src/stories/EmbeddableChat.stories.tsx +186 -0
- package/src/types/delivery.ts +54 -0
- package/src/types/index.ts +1 -0
- package/src/utils/dev-sections/index.ts +17 -0
- package/src/utils/dev-sections/openframe-dev-sections.ts +148 -0
- package/src/utils/index.ts +6 -1
- package/dist/chunk-APM6KBPU.cjs.map +0 -1
- package/dist/chunk-Z3GQGR5E.js.map +0 -1
|
@@ -0,0 +1,372 @@
|
|
|
1
|
+
'use client'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* useNatsChatAdapter — the NATS/Mingo-mode transport adapter for the
|
|
5
|
+
* unified chat surface. Companion of `useSseChatAdapter` (Guide mode);
|
|
6
|
+
* both implement the same `UnifiedChatState` contract so the public
|
|
7
|
+
* `useChat({ mode })` can dispatch between them with zero shell-side
|
|
8
|
+
* branching.
|
|
9
|
+
*
|
|
10
|
+
* Composition (no new logic — all the pieces already exist in the lib):
|
|
11
|
+
*
|
|
12
|
+
* ┌──────────────────────────────────────────────────────────────┐
|
|
13
|
+
* │ useNatsDialogSubscription live tail of agent events │
|
|
14
|
+
* │ ↓ │
|
|
15
|
+
* │ onEvent → processChunk │
|
|
16
|
+
* │ ↓ │
|
|
17
|
+
* │ useRealtimeChunkProcessor chunk → MessageSegment[] │
|
|
18
|
+
* │ ↓ │
|
|
19
|
+
* │ onSegmentsUpdate → updates assistant message in state │
|
|
20
|
+
* │ │
|
|
21
|
+
* │ useChunkCatchup back-fill missed chunks after │
|
|
22
|
+
* │ activation / reconnect │
|
|
23
|
+
* │ │
|
|
24
|
+
* │ config.publishUserMessage consumer-owned send (HTTP/NATS) │
|
|
25
|
+
* └──────────────────────────────────────────────────────────────┘
|
|
26
|
+
*
|
|
27
|
+
* Why publish is consumer-owned: the NATS module exposes a low-level
|
|
28
|
+
* `publishBytes/String/Json` but the actual user-message endpoint varies
|
|
29
|
+
* by deployment (REST POST in some, NATS subject in others). The adapter
|
|
30
|
+
* stays agnostic — caller wires up "user typed something, do X" via
|
|
31
|
+
* the `publishUserMessage` callback.
|
|
32
|
+
*
|
|
33
|
+
* The `active` option gates the live subscription so the unified chat
|
|
34
|
+
* shell can keep both adapters mounted while only paying network cost
|
|
35
|
+
* for the currently-displayed mode.
|
|
36
|
+
*/
|
|
37
|
+
|
|
38
|
+
import {
|
|
39
|
+
useCallback,
|
|
40
|
+
useEffect,
|
|
41
|
+
useMemo,
|
|
42
|
+
useRef,
|
|
43
|
+
useState,
|
|
44
|
+
type MutableRefObject,
|
|
45
|
+
} from 'react'
|
|
46
|
+
import { useNatsDialogSubscription } from './use-nats-dialog-subscription'
|
|
47
|
+
import { useRealtimeChunkProcessor } from './use-realtime-chunk-processor'
|
|
48
|
+
import { useChunkCatchup } from './use-chunk-catchup'
|
|
49
|
+
import type {
|
|
50
|
+
ChunkData,
|
|
51
|
+
FetchChunksFunction,
|
|
52
|
+
MessageSegment,
|
|
53
|
+
NatsMessageType,
|
|
54
|
+
StreamingPhase,
|
|
55
|
+
} from '../types'
|
|
56
|
+
import type {
|
|
57
|
+
UnifiedChatState,
|
|
58
|
+
UnifiedChatMessage,
|
|
59
|
+
UnifiedSendMessageOptions,
|
|
60
|
+
} from '../types/unified-chat-state.types'
|
|
61
|
+
import type { ChatRef } from '../chat-ref.types'
|
|
62
|
+
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// Config + options
|
|
65
|
+
// =============================================================================
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Consumer-supplied configuration for the NATS chat adapter.
|
|
69
|
+
*
|
|
70
|
+
* Every field is consumer-owned — the lib does not assume a particular
|
|
71
|
+
* backend protocol or auth scheme. Hosts wire these up against their
|
|
72
|
+
* own OpenFrame deployment.
|
|
73
|
+
*/
|
|
74
|
+
export interface UseNatsChatAdapterConfig {
|
|
75
|
+
/**
|
|
76
|
+
* Active conversation/dialog id. When `null` the adapter stays
|
|
77
|
+
* subscription-idle (no NATS connection, no catchup fetch). Set this
|
|
78
|
+
* once the consumer's "open new conversation" flow has allocated an
|
|
79
|
+
* id from the backend.
|
|
80
|
+
*/
|
|
81
|
+
dialogId: string | null
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Build the NATS WebSocket URL. Returning `null` short-circuits the
|
|
85
|
+
* subscription — same contract as `useNatsDialogSubscription`.
|
|
86
|
+
*/
|
|
87
|
+
getNatsWsUrl: () => string | null
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Optional NATS client auth.
|
|
91
|
+
*/
|
|
92
|
+
clientConfig?: {
|
|
93
|
+
name?: string
|
|
94
|
+
user?: string
|
|
95
|
+
pass?: string
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Send a user message upstream. Consumer-owned: typically an
|
|
100
|
+
* authenticated HTTP POST to the OpenFrame chat endpoint, or a
|
|
101
|
+
* direct NATS publish to a dedicated subject.
|
|
102
|
+
*
|
|
103
|
+
* The adapter does NOT couple to the wire format — it only:
|
|
104
|
+
* 1. appends the user message to local state for immediate render
|
|
105
|
+
* 2. flips streamingPhase to 'thinking' so the input UI shows status
|
|
106
|
+
* 3. calls this callback
|
|
107
|
+
*
|
|
108
|
+
* Reply arrives asynchronously as NATS chunks via the live tail and
|
|
109
|
+
* is accumulated into the trailing assistant message.
|
|
110
|
+
*/
|
|
111
|
+
publishUserMessage: (
|
|
112
|
+
text: string,
|
|
113
|
+
options: { hidden?: boolean; dialogId: string | null },
|
|
114
|
+
) => Promise<void> | void
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Historical-chunk fetcher used by `useChunkCatchup` to back-fill
|
|
118
|
+
* events that happened while the user was in another mode or before
|
|
119
|
+
* the websocket came online. Consumer-owned: typically a REST GET
|
|
120
|
+
* against the OpenFrame chat-history endpoint.
|
|
121
|
+
*
|
|
122
|
+
* When omitted, `useChunkCatchup` falls back to its own default
|
|
123
|
+
* fetch implementation — see hook docs for the contract.
|
|
124
|
+
*/
|
|
125
|
+
fetchChunks?: FetchChunksFunction
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Whether THINKING chunks are surfaced as segments. Default `false`
|
|
129
|
+
* (parity with the existing `useRealtimeChunkProcessor` default).
|
|
130
|
+
*/
|
|
131
|
+
enableThinking?: boolean
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Mirrors `UseRealtimeChunkProcessorOptions.batchApprovalsEnabled`.
|
|
135
|
+
* Default `true` — single batch card per APPROVAL_REQUEST with
|
|
136
|
+
* `toolCalls[]`. Set `false` to fall back to legacy per-tool cards.
|
|
137
|
+
*/
|
|
138
|
+
batchApprovalsEnabled?: boolean
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/**
|
|
142
|
+
* Per-call options for `useNatsChatAdapter`. Carries only the
|
|
143
|
+
* activation gate — config travels through the config object so it
|
|
144
|
+
* survives mode swaps without re-mounting.
|
|
145
|
+
*/
|
|
146
|
+
export interface UseNatsChatAdapterOptions {
|
|
147
|
+
/**
|
|
148
|
+
* When `false` the adapter goes idle: no NATS subscription, no
|
|
149
|
+
* catchup fetch, no publish. Local message state is preserved so
|
|
150
|
+
* the user sees their history when the mode flips back to active.
|
|
151
|
+
* Default `true`.
|
|
152
|
+
*/
|
|
153
|
+
active?: boolean
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// =============================================================================
|
|
157
|
+
// Internal helpers
|
|
158
|
+
// =============================================================================
|
|
159
|
+
|
|
160
|
+
function nextId(role: 'user' | 'assistant'): string {
|
|
161
|
+
// Date.now() + counter sliver keeps ids monotonic even when two
|
|
162
|
+
// messages are produced inside the same ms tick (user + assistant
|
|
163
|
+
// placeholder fire back-to-back from a single sendMessage call).
|
|
164
|
+
return `${role}-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Replace (or append) the trailing assistant message with the latest
|
|
169
|
+
* accumulated segments. Mirrors the use-chat.ts pattern so render
|
|
170
|
+
* behaviour matches the SSE adapter exactly.
|
|
171
|
+
*/
|
|
172
|
+
function updateTrailingAssistant(
|
|
173
|
+
prev: UnifiedChatMessage[],
|
|
174
|
+
segments: MessageSegment[],
|
|
175
|
+
): UnifiedChatMessage[] {
|
|
176
|
+
const last = prev[prev.length - 1]
|
|
177
|
+
if (!last || last.role !== 'assistant') {
|
|
178
|
+
// No placeholder exists — append a fresh assistant message.
|
|
179
|
+
return [
|
|
180
|
+
...prev,
|
|
181
|
+
{
|
|
182
|
+
id: nextId('assistant'),
|
|
183
|
+
role: 'assistant',
|
|
184
|
+
content: '',
|
|
185
|
+
segments,
|
|
186
|
+
},
|
|
187
|
+
]
|
|
188
|
+
}
|
|
189
|
+
return [
|
|
190
|
+
...prev.slice(0, -1),
|
|
191
|
+
{ ...last, segments },
|
|
192
|
+
]
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// =============================================================================
|
|
196
|
+
// Hook
|
|
197
|
+
// =============================================================================
|
|
198
|
+
|
|
199
|
+
export function useNatsChatAdapter(
|
|
200
|
+
config: UseNatsChatAdapterConfig,
|
|
201
|
+
options: UseNatsChatAdapterOptions = {},
|
|
202
|
+
): UnifiedChatState {
|
|
203
|
+
const { active = true } = options
|
|
204
|
+
const {
|
|
205
|
+
dialogId,
|
|
206
|
+
getNatsWsUrl,
|
|
207
|
+
clientConfig,
|
|
208
|
+
publishUserMessage,
|
|
209
|
+
fetchChunks,
|
|
210
|
+
enableThinking,
|
|
211
|
+
batchApprovalsEnabled,
|
|
212
|
+
} = config
|
|
213
|
+
|
|
214
|
+
const [messages, setMessages] = useState<UnifiedChatMessage[]>([])
|
|
215
|
+
const [streamingPhase, setStreamingPhase] = useState<StreamingPhase>('idle')
|
|
216
|
+
|
|
217
|
+
// Stable callback ref so `useRealtimeChunkProcessor`'s options object
|
|
218
|
+
// doesn't churn every render and tear down the accumulator state.
|
|
219
|
+
const callbacksRef: MutableRefObject<{
|
|
220
|
+
onSegmentsUpdate: (segments: MessageSegment[]) => void
|
|
221
|
+
onStreamStart: () => void
|
|
222
|
+
onStreamEnd: () => void
|
|
223
|
+
}> = useRef({
|
|
224
|
+
onSegmentsUpdate: (segments: MessageSegment[]) => {
|
|
225
|
+
setMessages((prev) => updateTrailingAssistant(prev, segments))
|
|
226
|
+
},
|
|
227
|
+
onStreamStart: () => setStreamingPhase('streaming'),
|
|
228
|
+
onStreamEnd: () => setStreamingPhase('idle'),
|
|
229
|
+
})
|
|
230
|
+
|
|
231
|
+
// Real-time chunk → segment processor.
|
|
232
|
+
const { processChunk, reset: resetAccumulator } = useRealtimeChunkProcessor({
|
|
233
|
+
callbacks: {
|
|
234
|
+
onSegmentsUpdate: (segments) => callbacksRef.current.onSegmentsUpdate(segments),
|
|
235
|
+
onStreamStart: () => callbacksRef.current.onStreamStart(),
|
|
236
|
+
onStreamEnd: () => callbacksRef.current.onStreamEnd(),
|
|
237
|
+
},
|
|
238
|
+
enableThinking,
|
|
239
|
+
batchApprovalsEnabled,
|
|
240
|
+
})
|
|
241
|
+
|
|
242
|
+
// History catchup — back-fills chunks emitted while the adapter was
|
|
243
|
+
// inactive or before the WS came online.
|
|
244
|
+
const {
|
|
245
|
+
processChunk: catchupProcessChunk,
|
|
246
|
+
catchUpChunks,
|
|
247
|
+
startInitialBuffering,
|
|
248
|
+
resetChunkTracking,
|
|
249
|
+
} = useChunkCatchup({
|
|
250
|
+
dialogId: active ? dialogId : null,
|
|
251
|
+
onChunkReceived: (chunk: ChunkData) => processChunk(chunk),
|
|
252
|
+
fetchChunks,
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
// Trigger initial backfill whenever a fresh dialog activates. Mirrors the
|
|
256
|
+
// pattern in `.use-chunk-catchup.md`: enable buffering first so realtime
|
|
257
|
+
// chunks queue behind the historical fetch, then flush in order.
|
|
258
|
+
useEffect(() => {
|
|
259
|
+
if (!active || !dialogId) return
|
|
260
|
+
resetChunkTracking()
|
|
261
|
+
startInitialBuffering()
|
|
262
|
+
catchUpChunks().catch((err) => {
|
|
263
|
+
console.error('[useNatsChatAdapter] initial catchup failed:', err)
|
|
264
|
+
})
|
|
265
|
+
}, [active, dialogId, resetChunkTracking, startInitialBuffering, catchUpChunks])
|
|
266
|
+
|
|
267
|
+
// Live tail subscription. `enabled` is gated on both `active` and a
|
|
268
|
+
// non-null dialogId so the consumer doesn't pay socket cost before
|
|
269
|
+
// a conversation exists.
|
|
270
|
+
useNatsDialogSubscription({
|
|
271
|
+
enabled: active && dialogId != null,
|
|
272
|
+
dialogId,
|
|
273
|
+
getNatsWsUrl,
|
|
274
|
+
clientConfig,
|
|
275
|
+
onEvent: (payload: unknown, messageType: NatsMessageType) => {
|
|
276
|
+
// Route via catchup so the buffer/dedupe logic stays consistent
|
|
277
|
+
// with historical playback. `useChunkCatchup` itself forwards to
|
|
278
|
+
// `processChunk` once dedupe checks pass.
|
|
279
|
+
catchupProcessChunk(payload as ChunkData, messageType)
|
|
280
|
+
},
|
|
281
|
+
})
|
|
282
|
+
|
|
283
|
+
// ─── Public API ───────────────────────────────────────────────────────────
|
|
284
|
+
|
|
285
|
+
const sendMessage = useCallback(
|
|
286
|
+
async (
|
|
287
|
+
text: string,
|
|
288
|
+
sendOptions?: UnifiedSendMessageOptions,
|
|
289
|
+
): Promise<void> => {
|
|
290
|
+
const hidden = sendOptions?.hidden ?? false
|
|
291
|
+
|
|
292
|
+
// Optimistically append the user bubble + an empty assistant
|
|
293
|
+
// placeholder. The assistant body fills in as NATS chunks land.
|
|
294
|
+
setMessages((prev) => [
|
|
295
|
+
...prev,
|
|
296
|
+
{
|
|
297
|
+
id: nextId('user'),
|
|
298
|
+
role: 'user',
|
|
299
|
+
content: text,
|
|
300
|
+
...(hidden ? { hidden: true } : {}),
|
|
301
|
+
},
|
|
302
|
+
{
|
|
303
|
+
id: nextId('assistant'),
|
|
304
|
+
role: 'assistant',
|
|
305
|
+
content: '',
|
|
306
|
+
segments: [],
|
|
307
|
+
},
|
|
308
|
+
])
|
|
309
|
+
setStreamingPhase('thinking')
|
|
310
|
+
|
|
311
|
+
await publishUserMessage(text, { hidden, dialogId })
|
|
312
|
+
},
|
|
313
|
+
[publishUserMessage, dialogId],
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
const stopMessage = useCallback(() => {
|
|
317
|
+
// NATS streams are driven server-side; the client can't really
|
|
318
|
+
// "cancel" an in-flight agent task without backend cooperation.
|
|
319
|
+
// For now we just drop the UI status — incoming chunks will still
|
|
320
|
+
// be accepted and rendered if the agent completes anyway.
|
|
321
|
+
setStreamingPhase('idle')
|
|
322
|
+
}, [])
|
|
323
|
+
|
|
324
|
+
const clearMessages = useCallback(() => {
|
|
325
|
+
setMessages([])
|
|
326
|
+
resetAccumulator()
|
|
327
|
+
setStreamingPhase('idle')
|
|
328
|
+
}, [resetAccumulator])
|
|
329
|
+
|
|
330
|
+
// No-op refs — Mingo agent has no RAG entity-card affordances.
|
|
331
|
+
const discussRef = useCallback((_ref: ChatRef) => {
|
|
332
|
+
/* no-op in Mingo mode */
|
|
333
|
+
}, [])
|
|
334
|
+
const displayRef = useCallback((_ref: ChatRef) => {
|
|
335
|
+
/* no-op in Mingo mode */
|
|
336
|
+
}, [])
|
|
337
|
+
|
|
338
|
+
// ─── Return shape ─────────────────────────────────────────────────────────
|
|
339
|
+
|
|
340
|
+
const isLoading = streamingPhase !== 'idle'
|
|
341
|
+
|
|
342
|
+
return useMemo<UnifiedChatState>(
|
|
343
|
+
() => ({
|
|
344
|
+
messages,
|
|
345
|
+
isLoading,
|
|
346
|
+
streamingPhase,
|
|
347
|
+
sendMessage,
|
|
348
|
+
stopMessage,
|
|
349
|
+
clearMessages,
|
|
350
|
+
discussRef,
|
|
351
|
+
displayRef,
|
|
352
|
+
// SSE-only telemetry — null in NATS mode.
|
|
353
|
+
currentProvider: null,
|
|
354
|
+
currentModelLabel: null,
|
|
355
|
+
currentContextWindowMaxTokens: null,
|
|
356
|
+
currentInputTokens: null,
|
|
357
|
+
currentOutputTokens: null,
|
|
358
|
+
currentCacheHitRatePct: null,
|
|
359
|
+
currentUsageBreakdown: null,
|
|
360
|
+
}),
|
|
361
|
+
[
|
|
362
|
+
messages,
|
|
363
|
+
isLoading,
|
|
364
|
+
streamingPhase,
|
|
365
|
+
sendMessage,
|
|
366
|
+
stopMessage,
|
|
367
|
+
clearMessages,
|
|
368
|
+
discussRef,
|
|
369
|
+
displayRef,
|
|
370
|
+
],
|
|
371
|
+
)
|
|
372
|
+
}
|