@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.
Files changed (93) hide show
  1. package/dist/{chunk-L6IBKPVM.js → chunk-EKBM4FHK.js} +2 -2
  2. package/dist/{chunk-SWZUZYWR.js → chunk-EWA2NFUR.js} +2 -2
  3. package/dist/{chunk-TYIBMDUZ.cjs → chunk-FZZBCRID.cjs} +7 -7
  4. package/dist/{chunk-TYIBMDUZ.cjs.map → chunk-FZZBCRID.cjs.map} +1 -1
  5. package/dist/{chunk-G2HHSZ3S.cjs → chunk-GE64T3JT.cjs} +9 -9
  6. package/dist/{chunk-G2HHSZ3S.cjs.map → chunk-GE64T3JT.cjs.map} +1 -1
  7. package/dist/{chunk-YWDC5BXM.cjs → chunk-L5RSJE2I.cjs} +1940 -915
  8. package/dist/chunk-L5RSJE2I.cjs.map +1 -0
  9. package/dist/{chunk-BVFRD34B.js → chunk-OHOUSDAY.js} +2 -2
  10. package/dist/{chunk-MVQ3OODK.cjs → chunk-S4SVD5JI.cjs} +9 -9
  11. package/dist/{chunk-MVQ3OODK.cjs.map → chunk-S4SVD5JI.cjs.map} +1 -1
  12. package/dist/{chunk-N5IKPYRL.js → chunk-SWIR5EB2.js} +2 -2
  13. package/dist/{chunk-6DCKL73F.cjs → chunk-TCJ5B2ZD.cjs} +24 -24
  14. package/dist/{chunk-6DCKL73F.cjs.map → chunk-TCJ5B2ZD.cjs.map} +1 -1
  15. package/dist/{chunk-ENBGG2K2.js → chunk-V5JY5RSY.js} +2954 -1929
  16. package/dist/chunk-V5JY5RSY.js.map +1 -0
  17. package/dist/components/chat/embeddable-chat.d.ts +13 -0
  18. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  19. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +104 -10
  20. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -1
  21. package/dist/components/chat/hooks/use-slash-commands.d.ts +6 -0
  22. package/dist/components/chat/hooks/use-slash-commands.d.ts.map +1 -1
  23. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -1
  24. package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -1
  25. package/dist/components/chat/index.cjs +2 -2
  26. package/dist/components/chat/index.js +1 -1
  27. package/dist/components/chat/types/unified-chat-state.types.d.ts +81 -0
  28. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -1
  29. package/dist/components/contact/index.cjs +3 -3
  30. package/dist/components/contact/index.js +2 -2
  31. package/dist/components/features/index.cjs +2 -2
  32. package/dist/components/features/index.js +1 -1
  33. package/dist/components/index.cjs +73 -51
  34. package/dist/components/index.cjs.map +1 -1
  35. package/dist/components/index.js +26 -4
  36. package/dist/components/index.js.map +1 -1
  37. package/dist/components/navigation/app-header.d.ts +7 -0
  38. package/dist/components/navigation/app-header.d.ts.map +1 -1
  39. package/dist/components/navigation/app-layout-drawer.d.ts +65 -0
  40. package/dist/components/navigation/app-layout-drawer.d.ts.map +1 -0
  41. package/dist/components/navigation/app-layout.d.ts +9 -1
  42. package/dist/components/navigation/app-layout.d.ts.map +1 -1
  43. package/dist/components/navigation/header-mingo-button.d.ts +21 -0
  44. package/dist/components/navigation/header-mingo-button.d.ts.map +1 -0
  45. package/dist/components/navigation/index.cjs +24 -2
  46. package/dist/components/navigation/index.cjs.map +1 -1
  47. package/dist/components/navigation/index.d.ts +5 -1
  48. package/dist/components/navigation/index.d.ts.map +1 -1
  49. package/dist/components/navigation/index.js +23 -1
  50. package/dist/components/onboarding-guides/index.cjs +18 -18
  51. package/dist/components/onboarding-guides/index.js +3 -3
  52. package/dist/components/tickets/hooks/use-ticket-engagements.d.ts.map +1 -1
  53. package/dist/components/tickets/index.cjs +80 -66
  54. package/dist/components/tickets/index.cjs.map +1 -1
  55. package/dist/components/tickets/index.js +20 -6
  56. package/dist/components/tickets/index.js.map +1 -1
  57. package/dist/components/ui/index.cjs +2 -2
  58. package/dist/components/ui/index.js +1 -1
  59. package/dist/index.cjs +26 -2
  60. package/dist/index.cjs.map +1 -1
  61. package/dist/index.js +25 -1
  62. package/dist/utils/embed-authed-fetch.d.ts +80 -0
  63. package/dist/utils/embed-authed-fetch.d.ts.map +1 -1
  64. package/dist/utils/index.cjs +70 -5
  65. package/dist/utils/index.cjs.map +1 -1
  66. package/dist/utils/index.d.ts +1 -1
  67. package/dist/utils/index.d.ts.map +1 -1
  68. package/dist/utils/index.js +70 -6
  69. package/dist/utils/index.js.map +1 -1
  70. package/package.json +2 -2
  71. package/src/components/chat/embeddable-chat.tsx +154 -37
  72. package/src/components/chat/hooks/use-nats-chat-adapter.ts +601 -23
  73. package/src/components/chat/hooks/use-slash-commands.ts +10 -1
  74. package/src/components/chat/hooks/use-sse-chat-adapter.ts +45 -0
  75. package/src/components/chat/hooks/use-unified-chat.ts +59 -0
  76. package/src/components/chat/types/unified-chat-state.types.ts +116 -0
  77. package/src/components/navigation/app-header.tsx +23 -0
  78. package/src/components/navigation/app-layout-drawer.tsx +620 -0
  79. package/src/components/navigation/app-layout.tsx +65 -26
  80. package/src/components/navigation/header-mingo-button.tsx +58 -0
  81. package/src/components/navigation/index.ts +17 -1
  82. package/src/components/tickets/hooks/use-ticket-engagements.ts +24 -4
  83. package/src/stories/AppLayoutDrawer.stories.tsx +228 -0
  84. package/src/utils/.embed-authed-fetch.md +7 -0
  85. package/src/utils/__tests__/embed-authed-fetch.test.ts +103 -1
  86. package/src/utils/embed-authed-fetch.ts +247 -7
  87. package/src/utils/index.ts +5 -1
  88. package/dist/chunk-ENBGG2K2.js.map +0 -1
  89. package/dist/chunk-YWDC5BXM.cjs.map +0 -1
  90. /package/dist/{chunk-L6IBKPVM.js.map → chunk-EKBM4FHK.js.map} +0 -0
  91. /package/dist/{chunk-SWZUZYWR.js.map → chunk-EWA2NFUR.js.map} +0 -0
  92. /package/dist/{chunk-BVFRD34B.js.map → chunk-OHOUSDAY.js.map} +0 -0
  93. /package/dist/{chunk-N5IKPYRL.js.map → chunk-SWIR5EB2.js.map} +0 -0
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  import { useEffect, useState } from "react";
17
+ import { embedAuthedFetch } from "../../../utils/embed-authed-fetch";
17
18
  import type {
18
19
  SlashCommandActionId,
19
20
  SlashCommandSummaryAction,
@@ -44,6 +45,12 @@ export type {
44
45
  * (e.g. `https://hub.openframe.ai/api/commands` → the base arg is
45
46
  * ignored).
46
47
  *
48
+ * Auth: rides on `embedAuthedFetch` (same bearer-act-as + 401 self-heal
49
+ * as the chat stream / identity / attachment calls) so a host that runs
50
+ * proxy-impersonation or a refresh-capable auth adapter gets its creds
51
+ * attached here too — no host-side `window.fetch` patch required. Hosts
52
+ * with no adapter fall through to cookie auth unchanged.
53
+ *
47
54
  * Returns the parsed `commands` array; on any non-2xx (auth,
48
55
  * rate-limit, chat-disabled) returns an empty array — the autocomplete
49
56
  * UI silently shows no suggestions rather than surfacing 401/403/429
@@ -56,7 +63,9 @@ export async function fetchSlashCommands(
56
63
  ): Promise<SlashCommandSummary[]> {
57
64
  const url = new URL(commandsUrl, window.location.origin);
58
65
  if (prefix) url.searchParams.set("q", prefix);
59
- const res = await fetch(url.toString(), { signal });
66
+ // `headers: {}` opts out of the default `Content-Type: application/json`
67
+ // — this is a bare GET with no body, so no content-type is needed.
68
+ const res = await embedAuthedFetch(url.toString(), { signal, headers: {} });
60
69
  if (!res.ok) return [];
61
70
  const data = (await res.json()) as { commands?: SlashCommandSummary[] };
62
71
  return data.commands ?? [];
@@ -57,6 +57,7 @@ import type {
57
57
  UnifiedSendMessageOptions,
58
58
  StreamingPhase,
59
59
  } from '../types/unified-chat-state.types'
60
+ import type { DialogItem } from '../types/component.types'
60
61
 
61
62
  // =============================================================================
62
63
  // Public types
@@ -1054,5 +1055,49 @@ export function useSseChatAdapter(
1054
1055
  /** Cross-call usage breakdown (Haiku rewriter/classifier/summarizer
1055
1056
  * token counts). null until the trailing usage frame lands. */
1056
1057
  currentUsageBreakdown: latestMeta?.breakdown ?? null,
1058
+ // ─── Dialog management — stubs for v1 ────────────────────────────────
1059
+ // Guide mode currently keeps its history in `localStorage` opaquely
1060
+ // under the hood (`runtime.source` namespaced key). Surfacing that
1061
+ // history as a structured dialog list is a follow-up; for now the
1062
+ // shape is satisfied with empty defaults so the unified contract
1063
+ // type-checks and EmbeddableChat hides sidebar affordances when
1064
+ // `dialogs.length === 0`.
1065
+ dialogs: SSE_EMPTY_DIALOGS,
1066
+ activeDialogId: null,
1067
+ selectDialog: noopSelectDialog,
1068
+ startNewDialog: noopStartNewDialog,
1069
+ deleteDialog: noopDeleteDialog,
1070
+ isDialogsLoading: false,
1071
+ isMessagesLoading: false,
1072
+ hasMoreDialogs: false,
1073
+ loadMoreDialogs: noopAsync,
1074
+ hasMoreMessages: false,
1075
+ loadMoreMessages: noopAsync,
1076
+ approveRequest: noopApproveRequest,
1077
+ rejectRequest: noopRejectRequest,
1078
+ dialogTokenUsage: null,
1079
+ connectionState: 'connected' as const,
1057
1080
  }
1058
1081
  }
1082
+
1083
+ // ─── Stable no-op references for the Guide-mode dialog-management stubs ──
1084
+ // Plain module-scope constants so the adapter's return identity stays
1085
+ // stable across renders — consumers that memo on these fields don't get
1086
+ // spurious re-runs.
1087
+ const SSE_EMPTY_DIALOGS: DialogItem[] = []
1088
+ const noopSelectDialog = (_id: string | null): void => {
1089
+ /* Guide mode has no managed dialog list yet */
1090
+ }
1091
+ const noopStartNewDialog = async (): Promise<string | null> => null
1092
+ const noopDeleteDialog = async (_id: string): Promise<void> => {
1093
+ /* no-op until Guide localStorage history is exposed */
1094
+ }
1095
+ const noopAsync = async (): Promise<void> => {
1096
+ /* no-op pagination stub */
1097
+ }
1098
+ const noopApproveRequest = async (_id: string): Promise<void> => {
1099
+ /* Guide mode has no tool-call approval workflow */
1100
+ }
1101
+ const noopRejectRequest = async (_id: string, _reason?: string): Promise<void> => {
1102
+ /* Guide mode has no tool-call approval workflow */
1103
+ }
@@ -141,6 +141,40 @@ export function useUnifiedChat(
141
141
  [activeState],
142
142
  )
143
143
 
144
+ // Dialog-management forwards — one thin wrapper per action so the
145
+ // returned identity stays stable as long as the active adapter's
146
+ // identity does. We don't recreate per-call to avoid spurious child
147
+ // re-renders downstream.
148
+ const selectDialog = useCallback(
149
+ (id: string | null) => activeState.selectDialog(id),
150
+ [activeState],
151
+ )
152
+ const startNewDialog = useCallback(
153
+ () => activeState.startNewDialog(),
154
+ [activeState],
155
+ )
156
+ const deleteDialog = useCallback(
157
+ (id: string) => activeState.deleteDialog(id),
158
+ [activeState],
159
+ )
160
+ const loadMoreDialogs = useCallback(
161
+ () => activeState.loadMoreDialogs(),
162
+ [activeState],
163
+ )
164
+ const loadMoreMessages = useCallback(
165
+ () => activeState.loadMoreMessages(),
166
+ [activeState],
167
+ )
168
+ const approveRequest = useCallback(
169
+ (requestId: string) => activeState.approveRequest(requestId),
170
+ [activeState],
171
+ )
172
+ const rejectRequest = useCallback(
173
+ (requestId: string, reason?: string) =>
174
+ activeState.rejectRequest(requestId, reason),
175
+ [activeState],
176
+ )
177
+
144
178
  return useMemo<UnifiedChatState>(
145
179
  () => ({
146
180
  messages: activeState.messages,
@@ -158,6 +192,24 @@ export function useUnifiedChat(
158
192
  currentOutputTokens: activeState.currentOutputTokens,
159
193
  currentCacheHitRatePct: activeState.currentCacheHitRatePct,
160
194
  currentUsageBreakdown: activeState.currentUsageBreakdown,
195
+ // Dialog management (forwarded from active adapter)
196
+ dialogs: activeState.dialogs,
197
+ activeDialogId: activeState.activeDialogId,
198
+ selectDialog,
199
+ startNewDialog,
200
+ deleteDialog,
201
+ isDialogsLoading: activeState.isDialogsLoading,
202
+ isMessagesLoading: activeState.isMessagesLoading,
203
+ hasMoreDialogs: activeState.hasMoreDialogs,
204
+ loadMoreDialogs,
205
+ hasMoreMessages: activeState.hasMoreMessages,
206
+ loadMoreMessages,
207
+ // Approvals
208
+ approveRequest,
209
+ rejectRequest,
210
+ // Token usage + connection
211
+ dialogTokenUsage: activeState.dialogTokenUsage,
212
+ connectionState: activeState.connectionState,
161
213
  }),
162
214
  [
163
215
  activeState,
@@ -166,6 +218,13 @@ export function useUnifiedChat(
166
218
  clearMessages,
167
219
  discussRef,
168
220
  displayRef,
221
+ selectDialog,
222
+ startNewDialog,
223
+ deleteDialog,
224
+ loadMoreDialogs,
225
+ loadMoreMessages,
226
+ approveRequest,
227
+ rejectRequest,
169
228
  ],
170
229
  )
171
230
  }
@@ -21,6 +21,44 @@ import type { ScrollAnchor } from './message.types'
21
21
  import type { ChatRef } from '../chat-ref.types'
22
22
  import type { ChatSource } from '../hooks/use-sse-chat-adapter'
23
23
  import type { ChatAttachment } from '../utils/chat-attachment-markdown'
24
+ import type { DialogItem } from './component.types'
25
+
26
+ // ─── Per-dialog token usage (Mingo backend telemetry) ────────────────────────
27
+
28
+ /**
29
+ * Token usage snapshot for the active Mingo dialog, hydrated from the
30
+ * backend's `dialog.tokenUsage` GraphQL field. Mirrors the shape returned
31
+ * by the openframe `dialogs` GraphQL endpoint so hosts can pass it through
32
+ * `fetchDialogMessages` without re-mapping.
33
+ *
34
+ * Null when no Mingo dialog is active, when the backend hasn't reported
35
+ * usage yet, or when the active mode is Guide (SSE telemetry surfaces via
36
+ * `currentInputTokens`/`currentOutputTokens` instead).
37
+ */
38
+ export interface DialogTokenUsage {
39
+ /** Backend chat-type discriminator, e.g. `ADMIN_AI_CHAT`. Free-form. */
40
+ chatType?: string
41
+ /** Total input tokens consumed so far in this dialog. */
42
+ inputTokensSize: number
43
+ /** Total output tokens emitted so far in this dialog. */
44
+ outputTokensSize: number
45
+ /** Sum of input + output. May be > input + output when the backend
46
+ * counts cache reads in a separate bucket — trust the backend value. */
47
+ totalTokensSize: number
48
+ /** Current LLM context-window occupancy in tokens — what the model
49
+ * has loaded right now, not the cumulative dialog sum. */
50
+ contextSize?: number
51
+ }
52
+
53
+ // ─── Connection state (transport-level) ──────────────────────────────────────
54
+
55
+ /**
56
+ * High-level transport connection status. Drives any UI affordance that
57
+ * needs to communicate "live tail is/isn't healthy" to the user. SSE/Guide
58
+ * adapter currently reports `'connected'` whenever a turn isn't streaming
59
+ * — the SSE side is request-response and has no long-lived socket.
60
+ */
61
+ export type ChatConnectionState = 'connected' | 'connecting' | 'disconnected'
24
62
 
25
63
  // ─── Streaming phase (unified across transports) ─────────────────────────────
26
64
 
@@ -212,4 +250,82 @@ export interface UnifiedChatState {
212
250
  * arrives. Always null in Mingo mode.
213
251
  */
214
252
  currentUsageBreakdown: UnifiedUsageBreakdown | null
253
+
254
+ // ─── Dialog management (Mingo: backend; Guide: localStorage) ──────────────
255
+ //
256
+ // All fields below are optional in practice: a transport adapter that
257
+ // doesn't manage multi-dialog history (older `useNatsChatAdapter`
258
+ // configs, the Guide adapter with localStorage disabled) returns the
259
+ // empty/null defaults so existing consumers keep working unchanged.
260
+ // Callers gate UI on `dialogs.length > 0` or `activeDialogId != null`.
261
+
262
+ /**
263
+ * History of dialogs available for the active mode. Mingo: paginated
264
+ * from the openframe backend. Guide: list of recent threads from
265
+ * localStorage. Empty when the active adapter doesn't expose dialog
266
+ * management.
267
+ */
268
+ dialogs: DialogItem[]
269
+
270
+ /** Currently-selected dialog id, or `null` when no dialog is active
271
+ * (draft state — "start a new conversation"). */
272
+ activeDialogId: string | null
273
+
274
+ /** Switch the panel to an existing dialog. Idempotent — selecting the
275
+ * active id is a no-op. Pass `null` to drop back to draft state. */
276
+ selectDialog: (id: string | null) => void
277
+
278
+ /** Allocate a fresh dialog on the backend (Mingo) or in localStorage
279
+ * (Guide) and switch to it. Returns the new dialog id. When the
280
+ * adapter doesn't support creation, resolves to `null`. */
281
+ startNewDialog: () => Promise<string | null>
282
+
283
+ /** Delete a dialog from history. No-op when the adapter doesn't
284
+ * expose `deleteDialog` (Guide localStorage always supports it;
285
+ * Mingo gates on the host-provided callback). */
286
+ deleteDialog: (id: string) => Promise<void>
287
+
288
+ /** True while the dialog list is being fetched for the first time. */
289
+ isDialogsLoading: boolean
290
+
291
+ /** True while message history for the active dialog is being fetched. */
292
+ isMessagesLoading: boolean
293
+
294
+ /** Whether more dialogs remain on the server (Mingo cursor pagination). */
295
+ hasMoreDialogs: boolean
296
+
297
+ /** Fetch the next page of dialogs. No-op when `hasMoreDialogs` is false. */
298
+ loadMoreDialogs: () => Promise<void>
299
+
300
+ /** Whether more historical messages remain in the active dialog. */
301
+ hasMoreMessages: boolean
302
+
303
+ /** Fetch the next page of historical messages for the active dialog. */
304
+ loadMoreMessages: () => Promise<void>
305
+
306
+ // ─── Approval mutations (Mingo agent tool-call workflow) ──────────────────
307
+
308
+ /** Approve an in-flight tool-call request. Errors surface via the
309
+ * host-supplied toast in the callback config (lib does not own UI). */
310
+ approveRequest: (requestId: string) => Promise<void>
311
+
312
+ /** Reject an in-flight tool-call request. Optional `reason` is
313
+ * forwarded to the backend when supported. */
314
+ rejectRequest: (requestId: string, reason?: string) => Promise<void>
315
+
316
+ // ─── Per-dialog token usage (Mingo only) ──────────────────────────────────
317
+
318
+ /** Cumulative token usage for the active Mingo dialog. Null in Guide
319
+ * mode or when the backend hasn't reported usage yet. Per-turn
320
+ * metadata (`currentInputTokens` etc.) is orthogonal — that fires on
321
+ * every SSE `message_start`, while this hydrates from the dialog
322
+ * fetch + live `TOKEN_USAGE` events. */
323
+ dialogTokenUsage: DialogTokenUsage | null
324
+
325
+ // ─── Connection state ─────────────────────────────────────────────────────
326
+
327
+ /** High-level transport connection state. Drives reconnect UI in
328
+ * Mingo mode; always `'connected'` in Guide mode (SSE is request-
329
+ * response and has no persistent socket to monitor). */
330
+ connectionState: ChatConnectionState
215
331
  }
@@ -10,6 +10,7 @@ import { Menu01Icon, SearchIcon, XmarkIcon } from '../icons-v2-generated'
10
10
  import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger, SquareAvatar } from '../ui'
11
11
  import { HeaderButton } from './header-button'
12
12
  import { HeaderGlobalSearch } from './header-global-search'
13
+ import { HeaderMingoButton } from './header-mingo-button'
13
14
  import { HeaderOrganizationFilter } from './header-organization-filter'
14
15
 
15
16
  export interface AppHeaderProps {
@@ -21,6 +22,13 @@ export interface AppHeaderProps {
21
22
  onOrgChange?: (id: string) => void
22
23
  showNotifications?: boolean
23
24
  unreadCount?: number
25
+ /** Render the "Mingo AI" launcher button (drawer-style trigger for an
26
+ * in-layout `AppLayoutDrawer` hosting the chat panel). Defaults to off. */
27
+ showMingoAI?: boolean
28
+ /** Click handler for the Mingo AI button — typically toggles the drawer. */
29
+ onMingoAI?: () => void
30
+ /** Whether the Mingo drawer is currently open (visually pressed state). */
31
+ isMingoAIActive?: boolean
24
32
  // User block
25
33
  showUser?: boolean
26
34
  userName?: string
@@ -49,6 +57,9 @@ export const AppHeader = React.memo(function AppHeader({
49
57
  onOrgChange,
50
58
  showNotifications,
51
59
  unreadCount = 0,
60
+ showMingoAI = false,
61
+ onMingoAI,
62
+ isMingoAIActive = false,
52
63
  showUser,
53
64
  userName,
54
65
  userEmail,
@@ -118,6 +129,18 @@ export const AppHeader = React.memo(function AppHeader({
118
129
  />
119
130
  )}
120
131
 
132
+ {/* Mingo AI launcher — placed to the LEFT of notifications so the user
133
+ menu cluster (notifications → avatar) stays anchored to the right. */}
134
+ {showMingoAI && (
135
+ <HeaderMingoButton
136
+ onClick={onMingoAI}
137
+ isActive={isMingoAIActive}
138
+ iconOnly={!isMdUp}
139
+ disabled={disabled || !onMingoAI}
140
+ className={dimmedClass}
141
+ />
142
+ )}
143
+
121
144
  {/* Notifications button */}
122
145
  {showNotifications && (
123
146
  <NotificationsHeaderButton