@flamingo-stack/openframe-frontend-core 0.0.217 → 0.0.218
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-L6IBKPVM.js → chunk-EKBM4FHK.js} +2 -2
- package/dist/{chunk-SWZUZYWR.js → chunk-EWA2NFUR.js} +2 -2
- package/dist/{chunk-TYIBMDUZ.cjs → chunk-FZZBCRID.cjs} +7 -7
- package/dist/{chunk-TYIBMDUZ.cjs.map → chunk-FZZBCRID.cjs.map} +1 -1
- package/dist/{chunk-G2HHSZ3S.cjs → chunk-GE64T3JT.cjs} +9 -9
- package/dist/{chunk-G2HHSZ3S.cjs.map → chunk-GE64T3JT.cjs.map} +1 -1
- package/dist/{chunk-YWDC5BXM.cjs → chunk-L5RSJE2I.cjs} +1940 -915
- package/dist/chunk-L5RSJE2I.cjs.map +1 -0
- package/dist/{chunk-BVFRD34B.js → chunk-OHOUSDAY.js} +2 -2
- package/dist/{chunk-MVQ3OODK.cjs → chunk-S4SVD5JI.cjs} +9 -9
- package/dist/{chunk-MVQ3OODK.cjs.map → chunk-S4SVD5JI.cjs.map} +1 -1
- package/dist/{chunk-N5IKPYRL.js → chunk-SWIR5EB2.js} +2 -2
- package/dist/{chunk-6DCKL73F.cjs → chunk-TCJ5B2ZD.cjs} +24 -24
- package/dist/{chunk-6DCKL73F.cjs.map → chunk-TCJ5B2ZD.cjs.map} +1 -1
- package/dist/{chunk-ENBGG2K2.js → chunk-V5JY5RSY.js} +2954 -1929
- package/dist/chunk-V5JY5RSY.js.map +1 -0
- package/dist/components/chat/embeddable-chat.d.ts +13 -0
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +104 -10
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-slash-commands.d.ts +6 -0
- package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -1
- package/dist/components/chat/index.cjs +2 -2
- package/dist/components/chat/index.js +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +81 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
- package/dist/components/contact/index.cjs +3 -3
- package/dist/components/contact/index.js +2 -2
- package/dist/components/features/index.cjs +2 -2
- package/dist/components/features/index.js +1 -1
- package/dist/components/index.cjs +73 -51
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.js +26 -4
- package/dist/components/index.js.map +1 -1
- package/dist/components/navigation/app-header.d.ts +7 -0
- package/dist/components/navigation/app-header.d.ts.map +1 -1
- package/dist/components/navigation/app-layout-drawer.d.ts +65 -0
- package/dist/components/navigation/app-layout-drawer.d.ts.map +1 -0
- package/dist/components/navigation/app-layout.d.ts +9 -1
- package/dist/components/navigation/app-layout.d.ts.map +1 -1
- package/dist/components/navigation/header-mingo-button.d.ts +21 -0
- package/dist/components/navigation/header-mingo-button.d.ts.map +1 -0
- package/dist/components/navigation/index.cjs +24 -2
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.d.ts +5 -1
- package/dist/components/navigation/index.d.ts.map +1 -1
- package/dist/components/navigation/index.js +23 -1
- package/dist/components/onboarding-guides/index.cjs +18 -18
- package/dist/components/onboarding-guides/index.js +3 -3
- package/dist/components/tickets/hooks/use-ticket-engagements.d.ts.map +1 -1
- package/dist/components/tickets/index.cjs +80 -66
- package/dist/components/tickets/index.cjs.map +1 -1
- package/dist/components/tickets/index.js +20 -6
- package/dist/components/tickets/index.js.map +1 -1
- package/dist/components/ui/index.cjs +2 -2
- package/dist/components/ui/index.js +1 -1
- package/dist/index.cjs +26 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +25 -1
- package/dist/utils/embed-authed-fetch.d.ts +80 -0
- package/dist/utils/embed-authed-fetch.d.ts.map +1 -1
- package/dist/utils/index.cjs +70 -5
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -1
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +70 -6
- package/dist/utils/index.js.map +1 -1
- package/package.json +2 -2
- package/src/components/chat/embeddable-chat.tsx +154 -37
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +601 -23
- package/src/components/chat/hooks/use-slash-commands.ts +10 -1
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +45 -0
- package/src/components/chat/hooks/use-unified-chat.ts +59 -0
- package/src/components/chat/types/unified-chat-state.types.ts +116 -0
- package/src/components/navigation/app-header.tsx +23 -0
- package/src/components/navigation/app-layout-drawer.tsx +620 -0
- package/src/components/navigation/app-layout.tsx +65 -26
- package/src/components/navigation/header-mingo-button.tsx +58 -0
- package/src/components/navigation/index.ts +17 -1
- package/src/components/tickets/hooks/use-ticket-engagements.ts +24 -4
- package/src/stories/AppLayoutDrawer.stories.tsx +228 -0
- package/src/utils/.embed-authed-fetch.md +7 -0
- package/src/utils/__tests__/embed-authed-fetch.test.ts +103 -1
- package/src/utils/embed-authed-fetch.ts +247 -7
- package/src/utils/index.ts +5 -1
- package/dist/chunk-ENBGG2K2.js.map +0 -1
- package/dist/chunk-YWDC5BXM.cjs.map +0 -1
- /package/dist/{chunk-L6IBKPVM.js.map → chunk-EKBM4FHK.js.map} +0 -0
- /package/dist/{chunk-SWZUZYWR.js.map → chunk-EWA2NFUR.js.map} +0 -0
- /package/dist/{chunk-BVFRD34B.js.map → chunk-OHOUSDAY.js.map} +0 -0
- /package/dist/{chunk-N5IKPYRL.js.map → chunk-SWIR5EB2.js.map} +0 -0
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* `useChat({ mode })` can dispatch between them with zero shell-side
|
|
8
8
|
* branching.
|
|
9
9
|
*
|
|
10
|
-
* Composition (
|
|
10
|
+
* Composition layers (in order from low to high):
|
|
11
11
|
*
|
|
12
12
|
* ┌──────────────────────────────────────────────────────────────┐
|
|
13
13
|
* │ useNatsDialogSubscription live tail of agent events │
|
|
@@ -24,6 +24,29 @@
|
|
|
24
24
|
* │ config.publishUserMessage consumer-owned send (HTTP/NATS) │
|
|
25
25
|
* └──────────────────────────────────────────────────────────────┘
|
|
26
26
|
*
|
|
27
|
+
* Two operating modes, distinguished by the presence of `fetchDialogs`:
|
|
28
|
+
*
|
|
29
|
+
* 1. **Bare-transport mode** (current Tauri Fae Chat usage):
|
|
30
|
+
* consumer supplies `config.dialogId` explicitly and owns dialog
|
|
31
|
+
* management above the adapter. The adapter does no list/select
|
|
32
|
+
* bookkeeping — every dialog field on the return value falls back
|
|
33
|
+
* to an empty default. This is the original v0 API; preserved
|
|
34
|
+
* byte-compatible so existing pinned consumers keep working.
|
|
35
|
+
*
|
|
36
|
+
* 2. **Managed-dialog mode** (openframe-frontend, EmbeddableChat
|
|
37
|
+
* sidebar): consumer supplies `fetchDialogs`,
|
|
38
|
+
* `fetchDialogMessages`, `createDialog`, etc. and the adapter
|
|
39
|
+
* owns the dialog state machine. The active dialog id, the list,
|
|
40
|
+
* pagination cursors, token usage hydration, and history merge
|
|
41
|
+
* all live inside this hook. The host renders the sidebar via
|
|
42
|
+
* `EmbeddableChat`'s built-in `<ChatSidebar>` slot and calls
|
|
43
|
+
* `selectDialog` / `startNewDialog` from there.
|
|
44
|
+
*
|
|
45
|
+
* The two modes are NOT mutually exclusive — passing `config.dialogId`
|
|
46
|
+
* alongside `fetchDialogs` forces the adapter into controlled mode (the
|
|
47
|
+
* external id wins) while still using the host's list fetchers for the
|
|
48
|
+
* sidebar.
|
|
49
|
+
*
|
|
27
50
|
* Why publish is consumer-owned: the NATS module exposes a low-level
|
|
28
51
|
* `publishBytes/String/Json` but the actual user-message endpoint varies
|
|
29
52
|
* by deployment (REST POST in some, NATS subject in others). The adapter
|
|
@@ -46,39 +69,83 @@ import {
|
|
|
46
69
|
import { useNatsDialogSubscription } from './use-nats-dialog-subscription'
|
|
47
70
|
import { useRealtimeChunkProcessor } from './use-realtime-chunk-processor'
|
|
48
71
|
import { useChunkCatchup } from './use-chunk-catchup'
|
|
72
|
+
import { processHistoricalMessagesWithErrors } from '../utils/process-historical-messages'
|
|
49
73
|
import type {
|
|
50
74
|
ChunkData,
|
|
51
75
|
FetchChunksFunction,
|
|
76
|
+
HistoricalMessage,
|
|
52
77
|
MessageSegment,
|
|
53
78
|
NatsMessageType,
|
|
54
79
|
StreamingPhase,
|
|
80
|
+
ChatApprovalStatus,
|
|
55
81
|
} from '../types'
|
|
56
82
|
import type {
|
|
83
|
+
ChatConnectionState,
|
|
84
|
+
DialogTokenUsage,
|
|
57
85
|
UnifiedChatState,
|
|
58
86
|
UnifiedChatMessage,
|
|
59
87
|
UnifiedSendMessageOptions,
|
|
60
88
|
} from '../types/unified-chat-state.types'
|
|
89
|
+
import type { DialogItem } from '../types/component.types'
|
|
61
90
|
import type { ChatRef } from '../chat-ref.types'
|
|
62
91
|
|
|
63
92
|
// =============================================================================
|
|
64
93
|
// Config + options
|
|
65
94
|
// =============================================================================
|
|
66
95
|
|
|
96
|
+
/** Page-fetch parameters passed to `fetchDialogs`. The adapter owns the
|
|
97
|
+
* cursor — the host only resolves it against the backend. */
|
|
98
|
+
export interface FetchDialogsParams {
|
|
99
|
+
cursor?: string
|
|
100
|
+
limit?: number
|
|
101
|
+
search?: string
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Successful `fetchDialogs` response. `nextCursor: null` means "no more
|
|
105
|
+
* pages" — used to terminate the infinite-scroll observer in the
|
|
106
|
+
* sidebar. */
|
|
107
|
+
export interface FetchDialogsResult {
|
|
108
|
+
dialogs: DialogItem[]
|
|
109
|
+
nextCursor: string | null
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/** Page-fetch parameters passed to `fetchDialogMessages`. */
|
|
113
|
+
export interface FetchDialogMessagesParams {
|
|
114
|
+
dialogId: string
|
|
115
|
+
cursor?: string
|
|
116
|
+
limit?: number
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/** Successful `fetchDialogMessages` response. `tokenUsage` is optional —
|
|
120
|
+
* some backends only attach it to the dialog header query, in which case
|
|
121
|
+
* hosts can either include it on the first page only (will populate the
|
|
122
|
+
* ModelDisplay readout) or fold it in via a separate update path. */
|
|
123
|
+
export interface FetchDialogMessagesResult {
|
|
124
|
+
messages: HistoricalMessage[]
|
|
125
|
+
nextCursor: string | null
|
|
126
|
+
tokenUsage?: DialogTokenUsage | null
|
|
127
|
+
}
|
|
128
|
+
|
|
67
129
|
/**
|
|
68
130
|
* Consumer-supplied configuration for the NATS chat adapter.
|
|
69
131
|
*
|
|
70
|
-
* Every field
|
|
71
|
-
* backend protocol or auth scheme.
|
|
72
|
-
* own OpenFrame deployment.
|
|
132
|
+
* Every field except `getNatsWsUrl` + `publishUserMessage` is optional —
|
|
133
|
+
* the lib does not assume a particular backend protocol or auth scheme.
|
|
134
|
+
* Hosts wire these up against their own OpenFrame deployment.
|
|
73
135
|
*/
|
|
74
136
|
export interface UseNatsChatAdapterConfig {
|
|
75
137
|
/**
|
|
76
|
-
* Active conversation/dialog id. When `
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
*
|
|
138
|
+
* Active conversation/dialog id. When omitted (`undefined`) the
|
|
139
|
+
* adapter manages its own active dialog id internally — the host
|
|
140
|
+
* drives selection through `selectDialog` / `startNewDialog`. When
|
|
141
|
+
* explicitly set (including `null`) the adapter treats this as
|
|
142
|
+
* controlled mode and uses the value verbatim. v0 consumers (the
|
|
143
|
+
* Tauri Fae Chat client) pass an explicit id here and own the
|
|
144
|
+
* lifecycle externally; v1+ consumers (the openframe-frontend
|
|
145
|
+
* EmbeddableChat) leave it undefined and rely on `fetchDialogs`
|
|
146
|
+
* for sidebar-driven selection.
|
|
80
147
|
*/
|
|
81
|
-
dialogId
|
|
148
|
+
dialogId?: string | null
|
|
82
149
|
|
|
83
150
|
/**
|
|
84
151
|
* Build the NATS WebSocket URL. Returning `null` short-circuits the
|
|
@@ -136,6 +203,78 @@ export interface UseNatsChatAdapterConfig {
|
|
|
136
203
|
* `toolCalls[]`. Set `false` to fall back to legacy per-tool cards.
|
|
137
204
|
*/
|
|
138
205
|
batchApprovalsEnabled?: boolean
|
|
206
|
+
|
|
207
|
+
// ─── Managed-dialog mode (sidebar + history) ─────────────────────────────
|
|
208
|
+
|
|
209
|
+
/**
|
|
210
|
+
* Fetch a paginated page of dialogs for the sidebar. When provided,
|
|
211
|
+
* the adapter switches to managed-dialog mode: it owns the active
|
|
212
|
+
* dialog id, the dialog list, and pagination state. When omitted,
|
|
213
|
+
* the adapter operates in bare-transport mode (current Tauri Fae
|
|
214
|
+
* Chat usage) and the sidebar fields on the return value stay empty.
|
|
215
|
+
*/
|
|
216
|
+
fetchDialogs?: (params: FetchDialogsParams) => Promise<FetchDialogsResult>
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Fetch a page of historical messages for a dialog. Required for
|
|
220
|
+
* sidebar-driven dialog switching — when omitted, selecting a
|
|
221
|
+
* dialog brings up an empty thread until streaming starts.
|
|
222
|
+
*
|
|
223
|
+
* Messages must arrive in the same wire shape the openframe backend
|
|
224
|
+
* emits (HistoricalMessage with messageData[]); the adapter feeds
|
|
225
|
+
* them through `processHistoricalMessagesWithErrors` to produce
|
|
226
|
+
* accumulator-compatible segments.
|
|
227
|
+
*/
|
|
228
|
+
fetchDialogMessages?: (
|
|
229
|
+
params: FetchDialogMessagesParams,
|
|
230
|
+
) => Promise<FetchDialogMessagesResult>
|
|
231
|
+
|
|
232
|
+
/**
|
|
233
|
+
* Allocate a fresh dialog on the backend. Returns the new dialog id
|
|
234
|
+
* which the adapter sets as the active dialog. When omitted, the
|
|
235
|
+
* "Start new chat" affordance on the sidebar is hidden (or the host
|
|
236
|
+
* can implement its own).
|
|
237
|
+
*/
|
|
238
|
+
createDialog?: () => Promise<{ dialogId: string }>
|
|
239
|
+
|
|
240
|
+
/** Delete a dialog from history. When omitted, the sidebar item's
|
|
241
|
+
* delete affordance is hidden. */
|
|
242
|
+
deleteDialog?: (dialogId: string) => Promise<void>
|
|
243
|
+
|
|
244
|
+
/**
|
|
245
|
+
* Approve a pending tool-call request. Wired into the segment
|
|
246
|
+
* accumulator's `onApprove` so approval-card buttons fire this
|
|
247
|
+
* directly. When omitted, approval cards render disabled buttons.
|
|
248
|
+
*/
|
|
249
|
+
approveRequest?: (requestId: string) => Promise<void>
|
|
250
|
+
|
|
251
|
+
/** Reject counterpart of `approveRequest`. */
|
|
252
|
+
rejectRequest?: (requestId: string, reason?: string) => Promise<void>
|
|
253
|
+
|
|
254
|
+
/**
|
|
255
|
+
* Cancel in-flight assistant generation. Without this, `stopMessage`
|
|
256
|
+
* only flips the UI status — the backend continues until the agent
|
|
257
|
+
* finishes naturally. With this, the backend stops emitting chunks.
|
|
258
|
+
*/
|
|
259
|
+
stopGeneration?: (dialogId: string) => Promise<void>
|
|
260
|
+
|
|
261
|
+
/** Display name for the assistant in historical messages — defaults
|
|
262
|
+
* to `'Mingo'`. */
|
|
263
|
+
assistantName?: string
|
|
264
|
+
|
|
265
|
+
/**
|
|
266
|
+
* GraphQL `chatType` discriminator to filter historical messages by.
|
|
267
|
+
* When set, `processHistoricalMessagesWithErrors` skips messages
|
|
268
|
+
* whose `chatType` doesn't match. Openframe-frontend uses
|
|
269
|
+
* `'ADMIN_AI_CHAT'` here.
|
|
270
|
+
*/
|
|
271
|
+
chatTypeFilter?: string
|
|
272
|
+
|
|
273
|
+
/** Default page size for dialog list pagination. Defaults to 20. */
|
|
274
|
+
dialogsPageSize?: number
|
|
275
|
+
|
|
276
|
+
/** Default page size for message history pagination. Defaults to 50. */
|
|
277
|
+
messagesPageSize?: number
|
|
139
278
|
}
|
|
140
279
|
|
|
141
280
|
/**
|
|
@@ -192,6 +331,43 @@ function updateTrailingAssistant(
|
|
|
192
331
|
]
|
|
193
332
|
}
|
|
194
333
|
|
|
334
|
+
/**
|
|
335
|
+
* Map `ProcessedMessage` (lib's historical-message format) into
|
|
336
|
+
* `UnifiedChatMessage` (the unified-chat-state contract). Only `user`
|
|
337
|
+
* and `assistant` roles round-trip; `error` is dropped on the floor
|
|
338
|
+
* here because the unified contract surfaces errors as banners, not
|
|
339
|
+
* inline messages. (Hosts that need inline error bubbles can extend
|
|
340
|
+
* the contract later.)
|
|
341
|
+
*/
|
|
342
|
+
function mapProcessedToUnified(
|
|
343
|
+
processed: Array<{
|
|
344
|
+
id: string
|
|
345
|
+
role: 'user' | 'assistant' | 'error'
|
|
346
|
+
content: string | MessageSegment[]
|
|
347
|
+
name?: string
|
|
348
|
+
}>,
|
|
349
|
+
): UnifiedChatMessage[] {
|
|
350
|
+
const out: UnifiedChatMessage[] = []
|
|
351
|
+
for (const m of processed) {
|
|
352
|
+
if (m.role === 'error') continue
|
|
353
|
+
if (Array.isArray(m.content)) {
|
|
354
|
+
out.push({
|
|
355
|
+
id: m.id,
|
|
356
|
+
role: m.role,
|
|
357
|
+
content: '',
|
|
358
|
+
segments: m.content,
|
|
359
|
+
})
|
|
360
|
+
} else {
|
|
361
|
+
out.push({
|
|
362
|
+
id: m.id,
|
|
363
|
+
role: m.role,
|
|
364
|
+
content: m.content,
|
|
365
|
+
})
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
return out
|
|
369
|
+
}
|
|
370
|
+
|
|
195
371
|
// =============================================================================
|
|
196
372
|
// Hook
|
|
197
373
|
// =============================================================================
|
|
@@ -202,18 +378,141 @@ export function useNatsChatAdapter(
|
|
|
202
378
|
): UnifiedChatState {
|
|
203
379
|
const { active = true } = options
|
|
204
380
|
const {
|
|
205
|
-
dialogId,
|
|
381
|
+
dialogId: controlledDialogId,
|
|
206
382
|
getNatsWsUrl,
|
|
207
383
|
clientConfig,
|
|
208
384
|
publishUserMessage,
|
|
209
385
|
fetchChunks,
|
|
210
386
|
enableThinking,
|
|
211
387
|
batchApprovalsEnabled,
|
|
388
|
+
fetchDialogs,
|
|
389
|
+
fetchDialogMessages,
|
|
390
|
+
createDialog: createDialogCallback,
|
|
391
|
+
deleteDialog: deleteDialogCallback,
|
|
392
|
+
approveRequest: approveRequestCallback,
|
|
393
|
+
rejectRequest: rejectRequestCallback,
|
|
394
|
+
stopGeneration: stopGenerationCallback,
|
|
395
|
+
assistantName = 'Mingo',
|
|
396
|
+
chatTypeFilter,
|
|
397
|
+
dialogsPageSize = 20,
|
|
398
|
+
messagesPageSize = 50,
|
|
212
399
|
} = config
|
|
213
400
|
|
|
401
|
+
// ─── Active dialog id resolution ──────────────────────────────────────────
|
|
402
|
+
// The discriminator is *capability*, not *value*: when the host wires
|
|
403
|
+
// `fetchDialogs` we know they want the adapter to own the dialog list,
|
|
404
|
+
// active id, and sidebar wiring. Any `controlledDialogId` they pass in
|
|
405
|
+
// that mode is ignored (host shouldn't be fighting the adapter for
|
|
406
|
+
// selection). When `fetchDialogs` is absent (Tauri Fae Chat) the adapter
|
|
407
|
+
// is in bare-transport mode and the host's `dialogId` is the source of
|
|
408
|
+
// truth — including `null`, which means "no dialog selected, idle WS".
|
|
409
|
+
|
|
410
|
+
const isManagedMode = fetchDialogs !== undefined
|
|
411
|
+
const [internalDialogId, setInternalDialogId] = useState<string | null>(null)
|
|
412
|
+
const dialogId = isManagedMode
|
|
413
|
+
? internalDialogId
|
|
414
|
+
: controlledDialogId !== undefined
|
|
415
|
+
? controlledDialogId
|
|
416
|
+
: null
|
|
417
|
+
|
|
418
|
+
// ─── Message thread + streaming phase ─────────────────────────────────────
|
|
419
|
+
|
|
214
420
|
const [messages, setMessages] = useState<UnifiedChatMessage[]>([])
|
|
215
421
|
const [streamingPhase, setStreamingPhase] = useState<StreamingPhase>('idle')
|
|
216
422
|
|
|
423
|
+
// Approval status map. Used both to dedupe pending segments at render
|
|
424
|
+
// time and to feed `processHistoricalMessagesWithErrors` so previously
|
|
425
|
+
// resolved approvals don't re-render as actionable on dialog switch.
|
|
426
|
+
const [approvalStatuses, setApprovalStatuses] = useState<
|
|
427
|
+
Record<string, ChatApprovalStatus>
|
|
428
|
+
>({})
|
|
429
|
+
|
|
430
|
+
// ─── Dialog list state (managed-dialog mode only) ─────────────────────────
|
|
431
|
+
|
|
432
|
+
const [dialogs, setDialogs] = useState<DialogItem[]>([])
|
|
433
|
+
const [dialogsNextCursor, setDialogsNextCursor] = useState<string | null>(null)
|
|
434
|
+
const [isDialogsLoading, setIsDialogsLoading] = useState<boolean>(false)
|
|
435
|
+
const [isCreatingDialog, setIsCreatingDialog] = useState<boolean>(false)
|
|
436
|
+
|
|
437
|
+
// ─── Message-history pagination ───────────────────────────────────────────
|
|
438
|
+
|
|
439
|
+
const [messagesNextCursor, setMessagesNextCursor] = useState<string | null>(null)
|
|
440
|
+
const [isMessagesLoading, setIsMessagesLoading] = useState<boolean>(false)
|
|
441
|
+
|
|
442
|
+
// ─── Token usage (Mingo per-dialog cumulative) ────────────────────────────
|
|
443
|
+
|
|
444
|
+
const [dialogTokenUsage, setDialogTokenUsage] = useState<DialogTokenUsage | null>(
|
|
445
|
+
null,
|
|
446
|
+
)
|
|
447
|
+
|
|
448
|
+
// ─── Connection state ─────────────────────────────────────────────────────
|
|
449
|
+
|
|
450
|
+
const [connectionState, setConnectionState] =
|
|
451
|
+
useState<ChatConnectionState>('connecting')
|
|
452
|
+
|
|
453
|
+
// ─── Approval handlers ────────────────────────────────────────────────────
|
|
454
|
+
// Optimistically flip status before the network round-trip so the
|
|
455
|
+
// accumulator's render reflects the user's choice immediately.
|
|
456
|
+
|
|
457
|
+
const handleApprove = useCallback(
|
|
458
|
+
async (requestId: string) => {
|
|
459
|
+
if (!approveRequestCallback) return
|
|
460
|
+
setApprovalStatuses((prev) => ({ ...prev, [requestId]: 'approved' }))
|
|
461
|
+
try {
|
|
462
|
+
await approveRequestCallback(requestId)
|
|
463
|
+
} catch (err) {
|
|
464
|
+
// Revert the optimistic flip on failure so the user can retry.
|
|
465
|
+
setApprovalStatuses((prev) => {
|
|
466
|
+
const next = { ...prev }
|
|
467
|
+
delete next[requestId]
|
|
468
|
+
return next
|
|
469
|
+
})
|
|
470
|
+
console.error('[useNatsChatAdapter] approveRequest failed:', err)
|
|
471
|
+
}
|
|
472
|
+
},
|
|
473
|
+
[approveRequestCallback],
|
|
474
|
+
)
|
|
475
|
+
|
|
476
|
+
const handleReject = useCallback(
|
|
477
|
+
async (requestId: string, reason?: string) => {
|
|
478
|
+
if (!rejectRequestCallback) return
|
|
479
|
+
setApprovalStatuses((prev) => ({ ...prev, [requestId]: 'rejected' }))
|
|
480
|
+
try {
|
|
481
|
+
await rejectRequestCallback(requestId, reason)
|
|
482
|
+
} catch (err) {
|
|
483
|
+
setApprovalStatuses((prev) => {
|
|
484
|
+
const next = { ...prev }
|
|
485
|
+
delete next[requestId]
|
|
486
|
+
return next
|
|
487
|
+
})
|
|
488
|
+
console.error('[useNatsChatAdapter] rejectRequest failed:', err)
|
|
489
|
+
}
|
|
490
|
+
},
|
|
491
|
+
[rejectRequestCallback],
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
// Stable refs so the accumulator's callbacks don't churn on re-render.
|
|
495
|
+
const handleApproveRef = useRef(handleApprove)
|
|
496
|
+
handleApproveRef.current = handleApprove
|
|
497
|
+
const handleRejectRef = useRef(handleReject)
|
|
498
|
+
handleRejectRef.current = handleReject
|
|
499
|
+
|
|
500
|
+
// Accumulator-compatible adapters. The lib's accumulator contract is
|
|
501
|
+
// `(requestId?: string) => void | Promise<void>` — single optional arg,
|
|
502
|
+
// no rejection reason. Our public API surface widens that to require a
|
|
503
|
+
// string + accept a reason, so we narrow here at the boundary. Reason
|
|
504
|
+
// is unavailable from button-click callers (the accumulator doesn't
|
|
505
|
+
// pass it) so we only forward the requestId; consumers that need
|
|
506
|
+
// structured reject reasons can call `rejectRequest` directly.
|
|
507
|
+
const accumApprove = useCallback((requestId?: string): void | Promise<void> => {
|
|
508
|
+
if (!requestId) return
|
|
509
|
+
return handleApproveRef.current(requestId)
|
|
510
|
+
}, [])
|
|
511
|
+
const accumReject = useCallback((requestId?: string): void | Promise<void> => {
|
|
512
|
+
if (!requestId) return
|
|
513
|
+
return handleRejectRef.current(requestId)
|
|
514
|
+
}, [])
|
|
515
|
+
|
|
217
516
|
// Stable callback ref so `useRealtimeChunkProcessor`'s options object
|
|
218
517
|
// doesn't churn every render and tear down the accumulator state.
|
|
219
518
|
const callbacksRef: MutableRefObject<{
|
|
@@ -228,12 +527,16 @@ export function useNatsChatAdapter(
|
|
|
228
527
|
onStreamEnd: () => setStreamingPhase('idle'),
|
|
229
528
|
})
|
|
230
529
|
|
|
231
|
-
// Real-time chunk → segment processor.
|
|
530
|
+
// Real-time chunk → segment processor. Approval handlers route through
|
|
531
|
+
// the refs so a config change (e.g. callback identity churn from the
|
|
532
|
+
// host) doesn't tear down the accumulator.
|
|
232
533
|
const { processChunk, reset: resetAccumulator } = useRealtimeChunkProcessor({
|
|
233
534
|
callbacks: {
|
|
234
535
|
onSegmentsUpdate: (segments) => callbacksRef.current.onSegmentsUpdate(segments),
|
|
235
536
|
onStreamStart: () => callbacksRef.current.onStreamStart(),
|
|
236
537
|
onStreamEnd: () => callbacksRef.current.onStreamEnd(),
|
|
538
|
+
onApprove: accumApprove,
|
|
539
|
+
onReject: accumReject,
|
|
237
540
|
},
|
|
238
541
|
enableThinking,
|
|
239
542
|
batchApprovalsEnabled,
|
|
@@ -252,9 +555,111 @@ export function useNatsChatAdapter(
|
|
|
252
555
|
fetchChunks,
|
|
253
556
|
})
|
|
254
557
|
|
|
255
|
-
//
|
|
256
|
-
//
|
|
257
|
-
//
|
|
558
|
+
// ─── Historical message hydration ─────────────────────────────────────────
|
|
559
|
+
// When the active dialog changes AND a `fetchDialogMessages` callback
|
|
560
|
+
// is wired, load the first page of history, process it through the
|
|
561
|
+
// accumulator, and seed `messages`. Streaming chunks for the same
|
|
562
|
+
// dialog then append on top.
|
|
563
|
+
|
|
564
|
+
// Monotonic guard: each history load captures a sequence number; when a
|
|
565
|
+
// response resolves, it only applies if it's still the latest request. This
|
|
566
|
+
// prevents a slow response for a previously-selected dialog from clobbering
|
|
567
|
+
// the state of the dialog the user just switched to.
|
|
568
|
+
const historyLoadSeqRef = useRef(0)
|
|
569
|
+
|
|
570
|
+
const loadDialogHistory = useCallback(
|
|
571
|
+
async (id: string, cursor?: string): Promise<void> => {
|
|
572
|
+
if (!fetchDialogMessages) return
|
|
573
|
+
historyLoadSeqRef.current += 1
|
|
574
|
+
const seq = historyLoadSeqRef.current
|
|
575
|
+
setIsMessagesLoading(true)
|
|
576
|
+
try {
|
|
577
|
+
const result = await fetchDialogMessages({
|
|
578
|
+
dialogId: id,
|
|
579
|
+
cursor,
|
|
580
|
+
limit: messagesPageSize,
|
|
581
|
+
})
|
|
582
|
+
// Ignore stale responses superseded by a newer dialog selection.
|
|
583
|
+
if (seq !== historyLoadSeqRef.current) return
|
|
584
|
+
const { messages: rawProcessed } = processHistoricalMessagesWithErrors(
|
|
585
|
+
result.messages,
|
|
586
|
+
{
|
|
587
|
+
assistantName,
|
|
588
|
+
assistantType: 'mingo',
|
|
589
|
+
chatTypeFilter,
|
|
590
|
+
onApprove: accumApprove,
|
|
591
|
+
onReject: accumReject,
|
|
592
|
+
approvalStatuses,
|
|
593
|
+
batchApprovalsEnabled,
|
|
594
|
+
},
|
|
595
|
+
)
|
|
596
|
+
const unified = mapProcessedToUnified(rawProcessed)
|
|
597
|
+
if (cursor === undefined) {
|
|
598
|
+
// First page — replace.
|
|
599
|
+
setMessages(unified)
|
|
600
|
+
} else {
|
|
601
|
+
// Older page — prepend.
|
|
602
|
+
setMessages((prev) => [...unified, ...prev])
|
|
603
|
+
}
|
|
604
|
+
setMessagesNextCursor(result.nextCursor)
|
|
605
|
+
if (result.tokenUsage !== undefined) {
|
|
606
|
+
setDialogTokenUsage(result.tokenUsage)
|
|
607
|
+
}
|
|
608
|
+
} catch (err) {
|
|
609
|
+
console.error('[useNatsChatAdapter] fetchDialogMessages failed:', err)
|
|
610
|
+
} finally {
|
|
611
|
+
// Only the latest request owns the loading flag — a superseded one
|
|
612
|
+
// must not clear it while the newer load is still in flight.
|
|
613
|
+
if (seq === historyLoadSeqRef.current) {
|
|
614
|
+
setIsMessagesLoading(false)
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
},
|
|
618
|
+
[
|
|
619
|
+
fetchDialogMessages,
|
|
620
|
+
messagesPageSize,
|
|
621
|
+
assistantName,
|
|
622
|
+
chatTypeFilter,
|
|
623
|
+
approvalStatuses,
|
|
624
|
+
batchApprovalsEnabled,
|
|
625
|
+
accumApprove,
|
|
626
|
+
accumReject,
|
|
627
|
+
],
|
|
628
|
+
)
|
|
629
|
+
|
|
630
|
+
// Reset + reload when the active dialog changes.
|
|
631
|
+
const prevDialogIdRef = useRef<string | null | undefined>(undefined)
|
|
632
|
+
useEffect(() => {
|
|
633
|
+
if (prevDialogIdRef.current === dialogId) return
|
|
634
|
+
prevDialogIdRef.current = dialogId
|
|
635
|
+
|
|
636
|
+
// Invalidate any in-flight history load for the previous dialog. Bumping
|
|
637
|
+
// here (not only inside loadDialogHistory) covers the clear/delete path
|
|
638
|
+
// — switching to a null/empty dialog never starts a new load, so without
|
|
639
|
+
// this an in-flight response could repopulate the just-cleared state.
|
|
640
|
+
historyLoadSeqRef.current += 1
|
|
641
|
+
|
|
642
|
+
// Drop accumulator + message state for the previous dialog.
|
|
643
|
+
resetAccumulator()
|
|
644
|
+
setMessages([])
|
|
645
|
+
setMessagesNextCursor(null)
|
|
646
|
+
setDialogTokenUsage(null)
|
|
647
|
+
setStreamingPhase('idle')
|
|
648
|
+
// No load runs when there's no active dialog, so clear the flag here;
|
|
649
|
+
// otherwise the superseded load's guarded `finally` leaves it stuck on.
|
|
650
|
+
setIsMessagesLoading(false)
|
|
651
|
+
|
|
652
|
+
if (!active || !dialogId) return
|
|
653
|
+
|
|
654
|
+
// Hydrate history first; then catchup will fold in any chunks that
|
|
655
|
+
// landed between the history snapshot and the live tail.
|
|
656
|
+
void loadDialogHistory(dialogId)
|
|
657
|
+
}, [active, dialogId, loadDialogHistory, resetAccumulator])
|
|
658
|
+
|
|
659
|
+
// Trigger initial chunk backfill whenever a fresh dialog activates.
|
|
660
|
+
// Runs alongside history hydration — history seeds processed messages,
|
|
661
|
+
// catchup buffers raw chunks in case the WS came online after history
|
|
662
|
+
// was already snapshotted.
|
|
258
663
|
useEffect(() => {
|
|
259
664
|
if (!active || !dialogId) return
|
|
260
665
|
resetChunkTracking()
|
|
@@ -264,9 +669,10 @@ export function useNatsChatAdapter(
|
|
|
264
669
|
})
|
|
265
670
|
}, [active, dialogId, resetChunkTracking, startInitialBuffering, catchUpChunks])
|
|
266
671
|
|
|
267
|
-
// Live
|
|
268
|
-
// non-null dialogId so the consumer doesn't pay
|
|
269
|
-
// a conversation exists.
|
|
672
|
+
// ─── Live NATS subscription ───────────────────────────────────────────────
|
|
673
|
+
// Gated on `active` and a non-null dialogId so the consumer doesn't pay
|
|
674
|
+
// socket cost before a conversation exists.
|
|
675
|
+
|
|
270
676
|
useNatsDialogSubscription({
|
|
271
677
|
enabled: active && dialogId != null,
|
|
272
678
|
dialogId,
|
|
@@ -277,10 +683,59 @@ export function useNatsChatAdapter(
|
|
|
277
683
|
// with historical playback. `useChunkCatchup` itself forwards to
|
|
278
684
|
// `processChunk` once dedupe checks pass.
|
|
279
685
|
catchupProcessChunk(payload as ChunkData, messageType)
|
|
686
|
+
// First successful event marks the connection as up.
|
|
687
|
+
setConnectionState('connected')
|
|
280
688
|
},
|
|
281
689
|
})
|
|
282
690
|
|
|
283
|
-
//
|
|
691
|
+
// Without a finer hook signal we treat "active + dialog selected" as
|
|
692
|
+
// connecting until the first event lands, and "no dialog" as idle-
|
|
693
|
+
// connected (nothing to subscribe to anyway).
|
|
694
|
+
useEffect(() => {
|
|
695
|
+
if (!active || !dialogId) {
|
|
696
|
+
setConnectionState('connected')
|
|
697
|
+
return
|
|
698
|
+
}
|
|
699
|
+
setConnectionState('connecting')
|
|
700
|
+
}, [active, dialogId])
|
|
701
|
+
|
|
702
|
+
// ─── Dialog list management (managed-dialog mode) ─────────────────────────
|
|
703
|
+
|
|
704
|
+
const loadDialogsPage = useCallback(
|
|
705
|
+
async (cursor?: string): Promise<void> => {
|
|
706
|
+
if (!fetchDialogs) return
|
|
707
|
+
setIsDialogsLoading(true)
|
|
708
|
+
try {
|
|
709
|
+
const result = await fetchDialogs({
|
|
710
|
+
cursor,
|
|
711
|
+
limit: dialogsPageSize,
|
|
712
|
+
})
|
|
713
|
+
setDialogsNextCursor(result.nextCursor)
|
|
714
|
+
if (cursor === undefined) {
|
|
715
|
+
setDialogs(result.dialogs)
|
|
716
|
+
} else {
|
|
717
|
+
setDialogs((prev) => [...prev, ...result.dialogs])
|
|
718
|
+
}
|
|
719
|
+
} catch (err) {
|
|
720
|
+
console.error('[useNatsChatAdapter] fetchDialogs failed:', err)
|
|
721
|
+
} finally {
|
|
722
|
+
setIsDialogsLoading(false)
|
|
723
|
+
}
|
|
724
|
+
},
|
|
725
|
+
[fetchDialogs, dialogsPageSize],
|
|
726
|
+
)
|
|
727
|
+
|
|
728
|
+
// Initial dialog list load.
|
|
729
|
+
const initialDialogsLoadedRef = useRef(false)
|
|
730
|
+
useEffect(() => {
|
|
731
|
+
if (!fetchDialogs) return
|
|
732
|
+
if (!active) return
|
|
733
|
+
if (initialDialogsLoadedRef.current) return
|
|
734
|
+
initialDialogsLoadedRef.current = true
|
|
735
|
+
void loadDialogsPage()
|
|
736
|
+
}, [active, fetchDialogs, loadDialogsPage])
|
|
737
|
+
|
|
738
|
+
// ─── Public action handlers ───────────────────────────────────────────────
|
|
284
739
|
|
|
285
740
|
const sendMessage = useCallback(
|
|
286
741
|
async (
|
|
@@ -314,12 +769,15 @@ export function useNatsChatAdapter(
|
|
|
314
769
|
)
|
|
315
770
|
|
|
316
771
|
const stopMessage = useCallback(() => {
|
|
317
|
-
//
|
|
318
|
-
//
|
|
319
|
-
// For now we just drop the UI status — incoming chunks will still
|
|
320
|
-
// be accepted and rendered if the agent completes anyway.
|
|
772
|
+
// Best-effort UI flip. The actual backend cancellation is gated on
|
|
773
|
+
// the host-supplied callback.
|
|
321
774
|
setStreamingPhase('idle')
|
|
322
|
-
|
|
775
|
+
if (stopGenerationCallback && dialogId) {
|
|
776
|
+
void stopGenerationCallback(dialogId).catch((err) => {
|
|
777
|
+
console.error('[useNatsChatAdapter] stopGeneration failed:', err)
|
|
778
|
+
})
|
|
779
|
+
}
|
|
780
|
+
}, [stopGenerationCallback, dialogId])
|
|
323
781
|
|
|
324
782
|
const clearMessages = useCallback(() => {
|
|
325
783
|
setMessages([])
|
|
@@ -327,6 +785,89 @@ export function useNatsChatAdapter(
|
|
|
327
785
|
setStreamingPhase('idle')
|
|
328
786
|
}, [resetAccumulator])
|
|
329
787
|
|
|
788
|
+
const selectDialog = useCallback(
|
|
789
|
+
(id: string | null) => {
|
|
790
|
+
if (!isManagedMode) {
|
|
791
|
+
// Bare-transport mode: the host owns dialog id externally. No-op
|
|
792
|
+
// here so we don't compete with the host's own state machine.
|
|
793
|
+
// Hosts wanting EmbeddableChat-driven selection should wire
|
|
794
|
+
// `fetchDialogs` to flip the adapter into managed-dialog mode.
|
|
795
|
+
return
|
|
796
|
+
}
|
|
797
|
+
setInternalDialogId(id)
|
|
798
|
+
},
|
|
799
|
+
[isManagedMode],
|
|
800
|
+
)
|
|
801
|
+
|
|
802
|
+
const startNewDialog = useCallback(async (): Promise<string | null> => {
|
|
803
|
+
if (!createDialogCallback) return null
|
|
804
|
+
if (isCreatingDialog) return null
|
|
805
|
+
try {
|
|
806
|
+
setIsCreatingDialog(true)
|
|
807
|
+
const result = await createDialogCallback()
|
|
808
|
+
// Optimistically prepend a placeholder dialog so the sidebar shows
|
|
809
|
+
// the new conversation immediately. The next list refresh will
|
|
810
|
+
// replace it with the canonical entry.
|
|
811
|
+
setDialogs((prev) => [
|
|
812
|
+
{
|
|
813
|
+
id: result.dialogId,
|
|
814
|
+
title: 'New Chat',
|
|
815
|
+
timestamp: new Date(),
|
|
816
|
+
},
|
|
817
|
+
...prev.filter((d) => d.id !== result.dialogId),
|
|
818
|
+
])
|
|
819
|
+
if (isManagedMode) {
|
|
820
|
+
setInternalDialogId(result.dialogId)
|
|
821
|
+
}
|
|
822
|
+
return result.dialogId
|
|
823
|
+
} catch (err) {
|
|
824
|
+
console.error('[useNatsChatAdapter] createDialog failed:', err)
|
|
825
|
+
return null
|
|
826
|
+
} finally {
|
|
827
|
+
setIsCreatingDialog(false)
|
|
828
|
+
}
|
|
829
|
+
}, [createDialogCallback, isCreatingDialog, isManagedMode])
|
|
830
|
+
|
|
831
|
+
const deleteDialog = useCallback(
|
|
832
|
+
async (id: string): Promise<void> => {
|
|
833
|
+
if (!deleteDialogCallback) return
|
|
834
|
+
try {
|
|
835
|
+
await deleteDialogCallback(id)
|
|
836
|
+
setDialogs((prev) => prev.filter((d) => d.id !== id))
|
|
837
|
+
if (isManagedMode && internalDialogId === id) {
|
|
838
|
+
setInternalDialogId(null)
|
|
839
|
+
}
|
|
840
|
+
} catch (err) {
|
|
841
|
+
console.error('[useNatsChatAdapter] deleteDialog failed:', err)
|
|
842
|
+
}
|
|
843
|
+
},
|
|
844
|
+
[deleteDialogCallback, internalDialogId, isManagedMode],
|
|
845
|
+
)
|
|
846
|
+
|
|
847
|
+
const loadMoreDialogs = useCallback(async (): Promise<void> => {
|
|
848
|
+
if (!dialogsNextCursor) return
|
|
849
|
+
await loadDialogsPage(dialogsNextCursor)
|
|
850
|
+
}, [dialogsNextCursor, loadDialogsPage])
|
|
851
|
+
|
|
852
|
+
const loadMoreMessages = useCallback(async (): Promise<void> => {
|
|
853
|
+
if (!dialogId || !messagesNextCursor) return
|
|
854
|
+
await loadDialogHistory(dialogId, messagesNextCursor)
|
|
855
|
+
}, [dialogId, messagesNextCursor, loadDialogHistory])
|
|
856
|
+
|
|
857
|
+
const approveRequest = useCallback(
|
|
858
|
+
async (requestId: string) => {
|
|
859
|
+
await handleApproveRef.current(requestId)
|
|
860
|
+
},
|
|
861
|
+
[],
|
|
862
|
+
)
|
|
863
|
+
|
|
864
|
+
const rejectRequest = useCallback(
|
|
865
|
+
async (requestId: string, reason?: string) => {
|
|
866
|
+
await handleRejectRef.current(requestId, reason)
|
|
867
|
+
},
|
|
868
|
+
[],
|
|
869
|
+
)
|
|
870
|
+
|
|
330
871
|
// No-op refs — Mingo agent has no RAG entity-card affordances.
|
|
331
872
|
const discussRef = useCallback((_ref: ChatRef) => {
|
|
332
873
|
/* no-op in Mingo mode */
|
|
@@ -338,6 +879,8 @@ export function useNatsChatAdapter(
|
|
|
338
879
|
// ─── Return shape ─────────────────────────────────────────────────────────
|
|
339
880
|
|
|
340
881
|
const isLoading = streamingPhase !== 'idle'
|
|
882
|
+
const hasMoreDialogs = dialogsNextCursor != null
|
|
883
|
+
const hasMoreMessages = messagesNextCursor != null
|
|
341
884
|
|
|
342
885
|
return useMemo<UnifiedChatState>(
|
|
343
886
|
() => ({
|
|
@@ -357,6 +900,25 @@ export function useNatsChatAdapter(
|
|
|
357
900
|
currentOutputTokens: null,
|
|
358
901
|
currentCacheHitRatePct: null,
|
|
359
902
|
currentUsageBreakdown: null,
|
|
903
|
+
// Dialog management
|
|
904
|
+
dialogs,
|
|
905
|
+
activeDialogId: dialogId,
|
|
906
|
+
selectDialog,
|
|
907
|
+
startNewDialog,
|
|
908
|
+
deleteDialog,
|
|
909
|
+
isDialogsLoading: isDialogsLoading || isCreatingDialog,
|
|
910
|
+
isMessagesLoading,
|
|
911
|
+
hasMoreDialogs,
|
|
912
|
+
loadMoreDialogs,
|
|
913
|
+
hasMoreMessages,
|
|
914
|
+
loadMoreMessages,
|
|
915
|
+
// Approval mutations
|
|
916
|
+
approveRequest,
|
|
917
|
+
rejectRequest,
|
|
918
|
+
// Token usage
|
|
919
|
+
dialogTokenUsage,
|
|
920
|
+
// Connection state
|
|
921
|
+
connectionState,
|
|
360
922
|
}),
|
|
361
923
|
[
|
|
362
924
|
messages,
|
|
@@ -367,6 +929,22 @@ export function useNatsChatAdapter(
|
|
|
367
929
|
clearMessages,
|
|
368
930
|
discussRef,
|
|
369
931
|
displayRef,
|
|
932
|
+
dialogs,
|
|
933
|
+
dialogId,
|
|
934
|
+
selectDialog,
|
|
935
|
+
startNewDialog,
|
|
936
|
+
deleteDialog,
|
|
937
|
+
isDialogsLoading,
|
|
938
|
+
isCreatingDialog,
|
|
939
|
+
isMessagesLoading,
|
|
940
|
+
hasMoreDialogs,
|
|
941
|
+
loadMoreDialogs,
|
|
942
|
+
hasMoreMessages,
|
|
943
|
+
loadMoreMessages,
|
|
944
|
+
approveRequest,
|
|
945
|
+
rejectRequest,
|
|
946
|
+
dialogTokenUsage,
|
|
947
|
+
connectionState,
|
|
370
948
|
],
|
|
371
949
|
)
|
|
372
950
|
}
|