@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.
Files changed (132) hide show
  1. package/dist/{chunk-Z3GQGR5E.js → chunk-2HMZSCJY.js} +3158 -2074
  2. package/dist/chunk-2HMZSCJY.js.map +1 -0
  3. package/dist/chunk-4XLJWX2N.js +12 -0
  4. package/dist/chunk-4XLJWX2N.js.map +1 -0
  5. package/dist/{chunk-APM6KBPU.cjs → chunk-C5EC5AZM.cjs} +1644 -560
  6. package/dist/chunk-C5EC5AZM.cjs.map +1 -0
  7. package/dist/chunk-VFKQMAUF.cjs +12 -0
  8. package/dist/chunk-VFKQMAUF.cjs.map +1 -0
  9. package/dist/components/chat/embeddable-chat.d.ts +35 -2
  10. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  11. package/dist/components/chat/hooks/index.d.ts +3 -0
  12. package/dist/components/chat/hooks/index.d.ts.map +1 -1
  13. package/dist/components/chat/hooks/use-embedded-chat.d.ts +10 -169
  14. package/dist/components/chat/hooks/use-embedded-chat.d.ts.map +1 -1
  15. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +85 -0
  16. package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -0
  17. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +124 -0
  18. package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -0
  19. package/dist/components/chat/hooks/use-unified-chat.d.ts +33 -0
  20. package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -0
  21. package/dist/components/chat/index.cjs +8 -2
  22. package/dist/components/chat/index.cjs.map +1 -1
  23. package/dist/components/chat/index.js +11 -5
  24. package/dist/components/chat/types/index.d.ts +1 -0
  25. package/dist/components/chat/types/index.d.ts.map +1 -1
  26. package/dist/components/chat/types/unified-chat-state.types.d.ts +185 -0
  27. package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -0
  28. package/dist/components/features/index.cjs +3 -2
  29. package/dist/components/features/index.cjs.map +1 -1
  30. package/dist/components/features/index.js +2 -1
  31. package/dist/components/index.cjs +26 -2
  32. package/dist/components/index.cjs.map +1 -1
  33. package/dist/components/index.d.ts +4 -0
  34. package/dist/components/index.d.ts.map +1 -1
  35. package/dist/components/index.js +27 -3
  36. package/dist/components/navigation/index.cjs +3 -2
  37. package/dist/components/navigation/index.cjs.map +1 -1
  38. package/dist/components/navigation/index.js +2 -1
  39. package/dist/components/shared/delivery/delivery-lists.d.ts +16 -0
  40. package/dist/components/shared/delivery/delivery-lists.d.ts.map +1 -0
  41. package/dist/components/shared/delivery/delivery-table.d.ts +12 -0
  42. package/dist/components/shared/delivery/delivery-table.d.ts.map +1 -0
  43. package/dist/components/shared/delivery/index.d.ts +3 -0
  44. package/dist/components/shared/delivery/index.d.ts.map +1 -0
  45. package/dist/components/shared/dev-section/dev-section-page.d.ts +31 -0
  46. package/dist/components/shared/dev-section/dev-section-page.d.ts.map +1 -0
  47. package/dist/components/shared/dev-section/dev-section-view.d.ts +34 -0
  48. package/dist/components/shared/dev-section/dev-section-view.d.ts.map +1 -0
  49. package/dist/components/shared/dev-section/index.d.ts +3 -0
  50. package/dist/components/shared/dev-section/index.d.ts.map +1 -0
  51. package/dist/components/shared/legal-document/index.d.ts +10 -0
  52. package/dist/components/shared/legal-document/index.d.ts.map +1 -0
  53. package/dist/components/shared/legal-document/legal-document-page.d.ts +66 -0
  54. package/dist/components/shared/legal-document/legal-document-page.d.ts.map +1 -0
  55. package/dist/components/shared/legal-document/use-legal-docs.d.ts +40 -0
  56. package/dist/components/shared/legal-document/use-legal-docs.d.ts.map +1 -0
  57. package/dist/components/shared/product-release/index.d.ts +2 -1
  58. package/dist/components/shared/product-release/index.d.ts.map +1 -1
  59. package/dist/components/shared/product-release/release-detail-page.d.ts +11 -7
  60. package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
  61. package/dist/components/shared/roadmap/index.d.ts +18 -0
  62. package/dist/components/shared/roadmap/index.d.ts.map +1 -0
  63. package/dist/components/shared/roadmap/roadmap-grid-skeleton.d.ts +24 -0
  64. package/dist/components/shared/roadmap/roadmap-grid-skeleton.d.ts.map +1 -0
  65. package/dist/components/shared/roadmap/roadmap-grid.d.ts +18 -0
  66. package/dist/components/shared/roadmap/roadmap-grid.d.ts.map +1 -0
  67. package/dist/components/shared/roadmap/use-roadmap-voting.d.ts +25 -0
  68. package/dist/components/shared/roadmap/use-roadmap-voting.d.ts.map +1 -0
  69. package/dist/components/ui/index.cjs +8 -2
  70. package/dist/components/ui/index.cjs.map +1 -1
  71. package/dist/components/ui/index.js +11 -5
  72. package/dist/components/ui/release-changelog-section.d.ts +13 -2
  73. package/dist/components/ui/release-changelog-section.d.ts.map +1 -1
  74. package/dist/embed-shims/index.cjs +1 -6
  75. package/dist/embed-shims/index.cjs.map +1 -1
  76. package/dist/embed-shims/index.js +1 -6
  77. package/dist/embed-shims/index.js.map +1 -1
  78. package/dist/index.cjs +18 -2
  79. package/dist/index.cjs.map +1 -1
  80. package/dist/index.js +19 -3
  81. package/dist/types/delivery.d.ts +49 -0
  82. package/dist/types/delivery.d.ts.map +1 -0
  83. package/dist/types/index.cjs +13 -0
  84. package/dist/types/index.cjs.map +1 -1
  85. package/dist/types/index.d.ts +1 -0
  86. package/dist/types/index.d.ts.map +1 -1
  87. package/dist/types/index.js +12 -1
  88. package/dist/types/index.js.map +1 -1
  89. package/dist/utils/dev-sections/index.d.ts +11 -0
  90. package/dist/utils/dev-sections/index.d.ts.map +1 -0
  91. package/dist/utils/dev-sections/openframe-dev-sections.d.ts +209 -0
  92. package/dist/utils/dev-sections/openframe-dev-sections.d.ts.map +1 -0
  93. package/dist/utils/index.cjs +82 -0
  94. package/dist/utils/index.cjs.map +1 -1
  95. package/dist/utils/index.d.ts +1 -0
  96. package/dist/utils/index.d.ts.map +1 -1
  97. package/dist/utils/index.js +81 -2
  98. package/dist/utils/index.js.map +1 -1
  99. package/package.json +1 -1
  100. package/src/components/chat/embeddable-chat.tsx +123 -8
  101. package/src/components/chat/hooks/index.ts +9 -2
  102. package/src/components/chat/hooks/use-embedded-chat.ts +18 -1016
  103. package/src/components/chat/hooks/use-nats-chat-adapter.ts +372 -0
  104. package/src/components/chat/hooks/use-sse-chat-adapter.ts +1058 -0
  105. package/src/components/chat/hooks/use-unified-chat.ts +171 -0
  106. package/src/components/chat/types/index.ts +1 -0
  107. package/src/components/chat/types/unified-chat-state.types.ts +215 -0
  108. package/src/components/index.ts +8 -0
  109. package/src/components/shared/delivery/delivery-lists.tsx +199 -0
  110. package/src/components/shared/delivery/delivery-table.tsx +174 -0
  111. package/src/components/shared/delivery/index.ts +9 -0
  112. package/src/components/shared/dev-section/dev-section-page.tsx +72 -0
  113. package/src/components/shared/dev-section/dev-section-view.tsx +129 -0
  114. package/src/components/shared/dev-section/index.ts +2 -0
  115. package/src/components/shared/legal-document/index.ts +19 -0
  116. package/src/components/shared/legal-document/legal-document-page.tsx +178 -0
  117. package/src/components/shared/legal-document/use-legal-docs.ts +123 -0
  118. package/src/components/shared/product-release/index.ts +14 -3
  119. package/src/components/shared/product-release/release-detail-page.tsx +45 -7
  120. package/src/components/shared/roadmap/index.ts +23 -0
  121. package/src/components/shared/roadmap/roadmap-grid-skeleton.tsx +74 -0
  122. package/src/components/shared/roadmap/roadmap-grid.tsx +106 -0
  123. package/src/components/shared/roadmap/use-roadmap-voting.ts +163 -0
  124. package/src/components/ui/release-changelog-section.tsx +113 -32
  125. package/src/stories/EmbeddableChat.stories.tsx +186 -0
  126. package/src/types/delivery.ts +54 -0
  127. package/src/types/index.ts +1 -0
  128. package/src/utils/dev-sections/index.ts +17 -0
  129. package/src/utils/dev-sections/openframe-dev-sections.ts +148 -0
  130. package/src/utils/index.ts +6 -1
  131. package/dist/chunk-APM6KBPU.cjs.map +0 -1
  132. package/dist/chunk-Z3GQGR5E.js.map +0 -1
@@ -0,0 +1,171 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * useUnifiedChat — the single public entry point for the unified chat
5
+ * surface. Dispatches between the SSE/Guide and NATS/Mingo transport
6
+ * adapters based on the active mode. Always calls both underlying
7
+ * hooks (React rules of hooks) but only one is "live": the inactive
8
+ * one stays idle (no streams, no subscriptions) while preserving its
9
+ * local message buffer so a mode-flip restores the user to exactly
10
+ * where they left off.
11
+ *
12
+ * Consumers that only need one mode pass a single `modes` entry —
13
+ * the inactive adapter's config slot stays empty and the hook
14
+ * supplies a no-op default so React-hooks-rules are satisfied
15
+ * without forcing consumers to write throwaway stubs.
16
+ *
17
+ * The mode-toggle UI affordance lives in the `<MingoChat>` shell that
18
+ * consumes this hook, NOT here. This module is pure state plumbing.
19
+ */
20
+
21
+ import { useCallback, useMemo, useRef } from 'react'
22
+ import { useSseChatAdapter, type UseSseChatAdapterOptions } from './use-sse-chat-adapter'
23
+ import {
24
+ useNatsChatAdapter,
25
+ type UseNatsChatAdapterConfig,
26
+ } from './use-nats-chat-adapter'
27
+ import type { UnifiedChatState } from '../types/unified-chat-state.types'
28
+
29
+ // =============================================================================
30
+ // Modes
31
+ // =============================================================================
32
+
33
+ /** Discriminator for the active transport mode. */
34
+ export type ChatMode = 'guide' | 'mingo'
35
+
36
+ /**
37
+ * Per-mode configuration. Each slot is optional — consumers that only
38
+ * need one mode leave the other undefined. The active mode MUST have
39
+ * its config populated; passing `activeMode: 'mingo'` while `modes.mingo`
40
+ * is undefined is a programming error and surfaces as a thrown
41
+ * `sendMessage`.
42
+ */
43
+ export interface UseUnifiedChatModes {
44
+ /**
45
+ * Guide mode (SSE → hub). Currently `useSseChatAdapter` reads its
46
+ * config from the ambient `ChatRuntimeContext` rather than props,
47
+ * so this slot is just an opt-in flag plus its adapter options.
48
+ * The presence of the key signals "guide mode is configured".
49
+ */
50
+ guide?: UseSseChatAdapterOptions
51
+
52
+ /**
53
+ * Mingo mode (NATS → openframe). All wiring is explicit: dialog id,
54
+ * NATS URL builder, publish callback, optional catchup fetcher.
55
+ * See `UseNatsChatAdapterConfig` for the field-by-field contract.
56
+ */
57
+ mingo?: UseNatsChatAdapterConfig
58
+ }
59
+
60
+ export interface UseUnifiedChatOptions {
61
+ modes: UseUnifiedChatModes
62
+ activeMode: ChatMode
63
+ }
64
+
65
+ // =============================================================================
66
+ // Defaults — fill the inactive slot so both hooks run safely
67
+ // =============================================================================
68
+
69
+ const EMPTY_SSE_OPTIONS: UseSseChatAdapterOptions = {}
70
+
71
+ function createDisabledNatsConfig(): UseNatsChatAdapterConfig {
72
+ return {
73
+ dialogId: null,
74
+ getNatsWsUrl: () => null,
75
+ publishUserMessage: () => {
76
+ throw new Error(
77
+ '[useUnifiedChat] publishUserMessage invoked but mingo mode is not configured. ' +
78
+ 'Pass `modes.mingo` to enable Mingo agent transport.',
79
+ )
80
+ },
81
+ }
82
+ }
83
+
84
+ // =============================================================================
85
+ // Hook
86
+ // =============================================================================
87
+
88
+ export function useUnifiedChat(
89
+ options: UseUnifiedChatOptions,
90
+ ): UnifiedChatState {
91
+ const { modes, activeMode } = options
92
+
93
+ // The mingo config object identity matters — `useNatsChatAdapter`
94
+ // wires its `dialogId`/url/publish into deps. Stabilise the disabled
95
+ // fallback so a guide-only consumer doesn't churn NATS hook state
96
+ // every render.
97
+ const disabledNatsRef = useRef<UseNatsChatAdapterConfig | null>(null)
98
+ if (disabledNatsRef.current === null) {
99
+ disabledNatsRef.current = createDisabledNatsConfig()
100
+ }
101
+
102
+ // Each adapter receives the user's config OR the disabled fallback,
103
+ // plus an `active` flag that gates its live network work. Both
104
+ // hooks are always called — only one is doing real work.
105
+ const sseActive = activeMode === 'guide' && modes.guide !== undefined
106
+ const natsActive = activeMode === 'mingo' && modes.mingo !== undefined
107
+
108
+ const sseState = useSseChatAdapter(modes.guide ?? EMPTY_SSE_OPTIONS)
109
+ const natsState = useNatsChatAdapter(
110
+ modes.mingo ?? disabledNatsRef.current,
111
+ { active: natsActive },
112
+ )
113
+
114
+ // Mark `sseActive` as used — SSE adapter is request-response so it
115
+ // doesn't currently consume an `active` gate. Keeping the flag here
116
+ // documents intent + leaves the door open for future SSE backgrounding.
117
+ void sseActive
118
+
119
+ const activeState = activeMode === 'guide' ? sseState : natsState
120
+
121
+ // Re-wrap so the returned identity is stable for the active state.
122
+ // Consumers shouldn't see the inactive adapter's state at all.
123
+ const stopMessage = useCallback(() => activeState.stopMessage(), [activeState])
124
+ const clearMessages = useCallback(
125
+ () => activeState.clearMessages(),
126
+ [activeState],
127
+ )
128
+ const sendMessage = useCallback(
129
+ (text: string, opts?: Parameters<UnifiedChatState['sendMessage']>[1]) =>
130
+ activeState.sendMessage(text, opts),
131
+ [activeState],
132
+ )
133
+ const discussRef = useCallback(
134
+ (ref: Parameters<UnifiedChatState['discussRef']>[0]) =>
135
+ activeState.discussRef(ref),
136
+ [activeState],
137
+ )
138
+ const displayRef = useCallback(
139
+ (ref: Parameters<UnifiedChatState['displayRef']>[0]) =>
140
+ activeState.displayRef(ref),
141
+ [activeState],
142
+ )
143
+
144
+ return useMemo<UnifiedChatState>(
145
+ () => ({
146
+ messages: activeState.messages,
147
+ isLoading: activeState.isLoading,
148
+ streamingPhase: activeState.streamingPhase,
149
+ sendMessage,
150
+ stopMessage,
151
+ clearMessages,
152
+ discussRef,
153
+ displayRef,
154
+ currentProvider: activeState.currentProvider,
155
+ currentModelLabel: activeState.currentModelLabel,
156
+ currentContextWindowMaxTokens: activeState.currentContextWindowMaxTokens,
157
+ currentInputTokens: activeState.currentInputTokens,
158
+ currentOutputTokens: activeState.currentOutputTokens,
159
+ currentCacheHitRatePct: activeState.currentCacheHitRatePct,
160
+ currentUsageBreakdown: activeState.currentUsageBreakdown,
161
+ }),
162
+ [
163
+ activeState,
164
+ sendMessage,
165
+ stopMessage,
166
+ clearMessages,
167
+ discussRef,
168
+ displayRef,
169
+ ],
170
+ )
171
+ }
@@ -5,3 +5,4 @@ export * from './network.types'
5
5
  export * from './processing.types'
6
6
  export * from './api.types'
7
7
  export * from './entities'
8
+ export * from './unified-chat-state.types'
@@ -0,0 +1,215 @@
1
+ 'use client'
2
+
3
+ /**
4
+ * UnifiedChatState — the return shape that BOTH transport hooks must satisfy.
5
+ *
6
+ * Two transport implementations live behind this contract:
7
+ * - SSE (Guide mode) — talks to the hub backend
8
+ * - NATS (Mingo mode) — talks to the OpenFrame backend
9
+ *
10
+ * The public `useChat({ mode })` hook picks one based on `mode.transport` and
11
+ * returns this shape regardless. Consumers (MingoChat shell, advanced custom
12
+ * embedders) work against this contract and never see which transport is live.
13
+ *
14
+ * Fields that only one transport can populate are typed as nullable (per-turn
15
+ * LLM metadata is SSE-only) or optional (Guide-only message sub-fields like
16
+ * `sources` and `chatRefs`). In the off-mode they're null/undefined.
17
+ */
18
+
19
+ import type { MessageSegment } from './message.types'
20
+ import type { ScrollAnchor } from './message.types'
21
+ import type { ChatRef } from '../chat-ref.types'
22
+ import type { ChatSource } from '../hooks/use-sse-chat-adapter'
23
+ import type { ChatAttachment } from '../utils/chat-attachment-markdown'
24
+
25
+ // ─── Streaming phase (unified across transports) ─────────────────────────────
26
+
27
+ /**
28
+ * Granular streaming-status enum the chat shell uses to render the
29
+ * "Thinking..."/"Streaming..." indicator above the input.
30
+ *
31
+ * - 'idle' → no in-flight turn
32
+ * - 'thinking' → request fired; no streamed text/segments yet
33
+ * - 'streaming' → first text/segment chunk arrived; output in progress
34
+ *
35
+ * Common to both Guide (SSE) and Mingo (NATS) modes — the phase
36
+ * machine fires identically in either transport, just driven by
37
+ * different chunk boundaries.
38
+ */
39
+ export type StreamingPhase = 'idle' | 'thinking' | 'streaming'
40
+
41
+ // ─── Usage breakdown (SSE-only telemetry) ────────────────────────────────────
42
+
43
+ /**
44
+ * Cross-call token-usage breakdown extracted from the SSE trailing usage
45
+ * frame. Surfaces sub-call costs for the Haiku rewriter/classifier/summarizer
46
+ * pipeline plus the routing decision for the main answer.
47
+ *
48
+ * Lives on `UnifiedChatState.currentUsageBreakdown` as `null` until the
49
+ * server's trailing usage frame lands. Always `null` in Mingo mode — NATS
50
+ * agent doesn't surface this telemetry.
51
+ */
52
+ export interface UnifiedUsageBreakdown {
53
+ haikuRewriter?: { input: number; output: number }
54
+ haikuClassifier?: { input: number; output: number }
55
+ haikuSummarizer?: { input: number; output: number }
56
+ routedAnswer?: { model: string; complexity: string; thinkingBudget: number }
57
+ }
58
+
59
+ // ─── Message ─────────────────────────────────────────────────────────────────
60
+
61
+ /**
62
+ * Generic chat message shape — unified between Guide (SSE) and Mingo (NATS).
63
+ *
64
+ * Guide-mode messages populate `sources` (RAG document citations) and
65
+ * `chatRefs` (inline entity-card references). Mingo-mode messages leave
66
+ * those undefined. Tool-execution and approval events ride inside
67
+ * `segments` and are common to both modes.
68
+ */
69
+ export interface UnifiedChatMessage {
70
+ id: string
71
+ role: 'user' | 'assistant'
72
+
73
+ /**
74
+ * Flat string form for legacy/simple callers. The structured `segments`
75
+ * form is preferred — it carries thinking blocks, tool calls, approval
76
+ * cards, etc., which `<ChatMessageEnhanced>` renders.
77
+ */
78
+ content: string
79
+ segments?: MessageSegment[]
80
+
81
+ /** Guide/SSE-only: document citations. Undefined in Mingo mode. */
82
+ sources?: ChatSource[]
83
+
84
+ /**
85
+ * Guide/SSE-only: per-row refs for inline entity-card rendering.
86
+ * Keyed by `<documentType>:<primaryKey>`. Undefined in Mingo mode.
87
+ */
88
+ chatRefs?: Record<string, ChatRef>
89
+
90
+ /**
91
+ * Per-message viewport-positioning hint. Common to both modes; the
92
+ * message-list reads it to override default tail-on-stream behaviour
93
+ * for a single message (e.g. long display-action answers anchor to
94
+ * the top so the reader starts at the lede).
95
+ */
96
+ scrollAnchor?: ScrollAnchor
97
+
98
+ /**
99
+ * When true the message is part of the conversation history but is
100
+ * NOT rendered in the chat UI. Used for post-approval auto-continuation
101
+ * turns where we don't want synthetic prompts polluting the visible
102
+ * thread.
103
+ */
104
+ hidden?: boolean
105
+ }
106
+
107
+ // ─── sendMessage options ─────────────────────────────────────────────────────
108
+
109
+ /**
110
+ * Options passed to `sendMessage`. Kept narrow on purpose — transport-specific
111
+ * extras stay inside the transport implementation, not on the public surface.
112
+ * Add fields here only when BOTH transports honour them.
113
+ */
114
+ export interface UnifiedSendMessageOptions {
115
+ /**
116
+ * Treat the user message as background context only — added to the LLM
117
+ * history so the model sees it, but NOT rendered in the UI. Used by hosts
118
+ * to fire post-approval auto-continuation turns. The assistant response
119
+ * still renders normally.
120
+ */
121
+ hidden?: boolean
122
+
123
+ /**
124
+ * Image attachments uploaded via `useChatAttachments` to ride alongside
125
+ * the user message. Each entry carries `{ storagePath, viewToken }` —
126
+ * server-side the transport adapter replaces embedded view-URL markdown
127
+ * lines with provider-native image content blocks.
128
+ *
129
+ * Guide/SSE mode: forwarded to the hub's `/api/docs/chat` endpoint.
130
+ * Mingo/NATS mode: ignored (agent backend does not currently accept
131
+ * image attachments — adapter will silently drop).
132
+ */
133
+ attachments?: ChatAttachment[]
134
+ }
135
+
136
+ // ─── Return shape ────────────────────────────────────────────────────────────
137
+
138
+ /**
139
+ * The contract every transport hook returns. Stable across SSE and NATS —
140
+ * consumers depend on this shape and never branch on `mode` themselves.
141
+ *
142
+ * Mode-specific telemetry (provider, token counts, cache-hit %) is SSE-only.
143
+ * In Mingo mode those fields are `null` because NATS-agent doesn't surface
144
+ * per-turn LLM metadata the same way.
145
+ */
146
+ export interface UnifiedChatState {
147
+ // ─── Message thread ───────────────────────────────────────────────────────
148
+ messages: UnifiedChatMessage[]
149
+
150
+ /**
151
+ * True while a user turn is in progress — server-side thinking, streaming,
152
+ * or both. Driven by `streamingPhase` under the hood; exposed as a flat
153
+ * boolean for the chat-input "submit disabled" affordance.
154
+ */
155
+ isLoading: boolean
156
+
157
+ /** Granular phase for the "Thinking..."/"Streaming..." status row above input. */
158
+ streamingPhase: StreamingPhase
159
+
160
+ // ─── Actions ──────────────────────────────────────────────────────────────
161
+ sendMessage: (
162
+ text: string,
163
+ options?: UnifiedSendMessageOptions,
164
+ ) => Promise<void>
165
+
166
+ /** Abort the in-flight stream. No-op when idle. */
167
+ stopMessage: () => void
168
+
169
+ /** Wipe the local message buffer. Does not touch server-side history. */
170
+ clearMessages: () => void
171
+
172
+ /**
173
+ * Trigger the chat's "Discuss this row" affordance — opens a focused
174
+ * thread scoped to a specific RAG entity. Guide-mode only; in Mingo mode
175
+ * this is a no-op (the agent has no entity-id-filtered retrieval).
176
+ */
177
+ discussRef: (ref: ChatRef) => void
178
+
179
+ /**
180
+ * Trigger the chat's "Display this row" affordance — same as `discussRef`
181
+ * but for read-only card display rather than a question. Guide-mode only.
182
+ */
183
+ displayRef: (ref: ChatRef) => void
184
+
185
+ // ─── Per-turn LLM metadata (Guide/SSE only) ───────────────────────────────
186
+ /**
187
+ * Provider key recognised by `<ModelDisplay>` for icon selection.
188
+ * Values: 'anthropic' | 'openai' | 'google' (case-insensitive).
189
+ * Null in Mingo mode and during the brief window before the server's
190
+ * `message_start` frame arrives.
191
+ */
192
+ currentProvider: string | null
193
+
194
+ /** Display label for the active model, e.g. "Sonnet 4.6". Null in Mingo. */
195
+ currentModelLabel: string | null
196
+
197
+ /** Model's context-window cap in tokens. Null in Mingo. */
198
+ currentContextWindowMaxTokens: number | null
199
+
200
+ /** Input tokens for the current turn (from `message_start`). Null in Mingo. */
201
+ currentInputTokens: number | null
202
+
203
+ /** Output tokens for the current turn (only known after stream end). */
204
+ currentOutputTokens: number | null
205
+
206
+ /** Cache-hit % (read / total-input × 100). Only known after stream end. */
207
+ currentCacheHitRatePct: number | null
208
+
209
+ /**
210
+ * Cross-call token-usage breakdown (Haiku rewriter/classifier/summarizer
211
+ * + routed-answer telemetry). Null until the SSE trailing usage frame
212
+ * arrives. Always null in Mingo mode.
213
+ */
214
+ currentUsageBreakdown: UnifiedUsageBreakdown | null
215
+ }
@@ -65,6 +65,14 @@ export * from './shared/onboarding'
65
65
  // Product Release components
66
66
  export * from './shared/product-release'
67
67
 
68
+ // Dev-center shared components (Roadmap / Delivery / DevSectionView chrome)
69
+ export * from './shared/dev-section'
70
+ export * from './shared/roadmap'
71
+ export * from './shared/delivery'
72
+
73
+ // Legal-document shared component (privacy policy, terms of service)
74
+ export * from './shared/legal-document'
75
+
68
76
  // Detail Page Skeleton
69
77
  export { DetailPageSkeleton, type DetailPageSkeletonProps } from './shared/detail-page-skeleton'
70
78
 
@@ -0,0 +1,199 @@
1
+ 'use client';
2
+
3
+ /**
4
+ * DeliveryLists — the delivery section body (two tables: recently
5
+ * completed + active). Reads `search` and `task_type` URL params
6
+ * written by the shared `<DevSectionView>` chrome and refetches on
7
+ * change.
8
+ *
9
+ * Endpoint configuration:
10
+ * - `completedApiEndpoint` / `inProgressApiEndpoint` are the two
11
+ * per-bucket GET endpoints. Defaults match the hub's pre-migration
12
+ * routes (`/api/delivery/completed`, `/api/delivery/in-progress`).
13
+ *
14
+ * Coupling constraint — `searchParamKey` / `taskTypeParamKey`:
15
+ * These props serve TWO purposes:
16
+ * 1. URL READS — keys this component reads via `useSearchParams()`.
17
+ * MUST match the consuming chrome's `section.search.paramKey` /
18
+ * `section.filter.paramKey` (the chrome WRITES the URL params).
19
+ * 2. API WRITES — keys this component sends as query params on the
20
+ * outbound fetch to `{completedApiEndpoint,inProgressApiEndpoint}`.
21
+ * The hub API contract uses `'search'` / `'task_type'`; embedders
22
+ * reverse-proxying those routes must preserve the same names OR
23
+ * rewrite the inbound query string on the proxy side.
24
+ *
25
+ * Defaults align with `OPENFRAME_DEV_SECTIONS.delivery.{search.paramKey,filter.paramKey}`
26
+ * AND the hub API contract, so the OpenFrame zero-config case "just
27
+ * works". Custom chrome overriding the param keys must override BOTH
28
+ * ends consistently AND ensure the backend reads the same names.
29
+ */
30
+
31
+ import { useEffect, useState } from 'react';
32
+ import { useSearchParams, useRouter, usePathname } from '../../../embed-shims';
33
+ import type { DeliveryResponse } from '../../../types/delivery';
34
+ import { DeliveryTable } from './delivery-table';
35
+ import { EmptyState } from '../../empty-state';
36
+ import { LoadError } from '../../ui/error-state';
37
+
38
+ const DEFAULT_COMPLETED_ENDPOINT = '/api/delivery/completed';
39
+ const DEFAULT_IN_PROGRESS_ENDPOINT = '/api/delivery/in-progress';
40
+ const DEFAULT_SEARCH_PARAM_KEY = 'search';
41
+ const DEFAULT_TASK_TYPE_PARAM_KEY = 'task_type';
42
+
43
+ export interface DeliveryListsProps {
44
+ /** GET endpoint for the "Recently Completed" bucket. Default
45
+ * `/api/delivery/completed`. */
46
+ completedApiEndpoint?: string;
47
+ /** GET endpoint for the "Active Tasks" bucket. Default
48
+ * `/api/delivery/in-progress`. */
49
+ inProgressApiEndpoint?: string;
50
+ /** URL param key for the search input. MUST match the consuming
51
+ * chrome's `section.search.paramKey`. Default `'search'`. */
52
+ searchParamKey?: string;
53
+ /** URL param key for the task-type filter. MUST match the consuming
54
+ * chrome's `section.filter.paramKey`. Default `'task_type'`. */
55
+ taskTypeParamKey?: string;
56
+ }
57
+
58
+ export function DeliveryLists({
59
+ completedApiEndpoint = DEFAULT_COMPLETED_ENDPOINT,
60
+ inProgressApiEndpoint = DEFAULT_IN_PROGRESS_ENDPOINT,
61
+ searchParamKey = DEFAULT_SEARCH_PARAM_KEY,
62
+ taskTypeParamKey = DEFAULT_TASK_TYPE_PARAM_KEY,
63
+ }: DeliveryListsProps = {}) {
64
+ const searchParams = useSearchParams();
65
+ const router = useRouter();
66
+ const pathname = usePathname();
67
+
68
+ const [data, setData] = useState<DeliveryResponse | null>(null);
69
+ const [isLoading, setIsLoading] = useState(true);
70
+ const [error, setError] = useState<string | null>(null);
71
+
72
+ // Get filter state from URL
73
+ const searchQuery = searchParams.get(searchParamKey) || '';
74
+ const taskTypeFilter = searchParams.get(taskTypeParamKey) || 'all';
75
+
76
+ useEffect(() => {
77
+ async function fetchDeliveryData() {
78
+ try {
79
+ setIsLoading(true);
80
+ setError(null);
81
+
82
+ // Build query parameters for filtering. The outbound key names
83
+ // mirror the inbound URL-param keys — see "Coupling constraint"
84
+ // in the file docblock for why.
85
+ const params = new URLSearchParams();
86
+ if (searchQuery) {
87
+ params.set(searchParamKey, searchQuery);
88
+ }
89
+ if (taskTypeFilter && taskTypeFilter !== 'all') {
90
+ params.set(taskTypeParamKey, taskTypeFilter);
91
+ }
92
+ const queryString = params.toString();
93
+ const queryParam = queryString ? `?${queryString}` : '';
94
+
95
+ // Fetch completed and in-progress tasks separately with filters
96
+ const [completedResponse, inProgressResponse] = await Promise.all([
97
+ fetch(`${completedApiEndpoint}${queryParam}`),
98
+ fetch(`${inProgressApiEndpoint}${queryParam}`),
99
+ ]);
100
+
101
+ if (!completedResponse.ok || !inProgressResponse.ok) {
102
+ throw new Error('Failed to fetch delivery items');
103
+ }
104
+
105
+ const [completedResult, inProgressResult] = await Promise.all([
106
+ completedResponse.json(),
107
+ inProgressResponse.json(),
108
+ ]);
109
+
110
+ setData({
111
+ completed: completedResult.items || [],
112
+ inProgress: inProgressResult.items || [],
113
+ });
114
+ } catch (err) {
115
+ console.error('Error fetching delivery items:', err);
116
+ setError('Failed to load delivery items. Please try again later.');
117
+ } finally {
118
+ setIsLoading(false);
119
+ }
120
+ }
121
+
122
+ fetchDeliveryData();
123
+ }, [searchQuery, taskTypeFilter, completedApiEndpoint, inProgressApiEndpoint, searchParamKey, taskTypeParamKey]);
124
+
125
+ const filteredCompleted = data?.completed || [];
126
+ const filteredInProgress = data?.inProgress || [];
127
+
128
+ const showCompleted = true;
129
+ const showInProgress = true;
130
+
131
+ const hasActiveFilters = searchQuery !== '' || taskTypeFilter !== 'all';
132
+ const hasResults = (showCompleted && filteredCompleted.length > 0) || (showInProgress && filteredInProgress.length > 0);
133
+
134
+ // Error state — consume lib's canonical LoadError so ODS tokens +
135
+ // retry affordance stay in lockstep with every other surface.
136
+ if (error) {
137
+ return (
138
+ <div className="w-full">
139
+ <LoadError message={error} onRetry={() => window.location.reload()} />
140
+ </div>
141
+ );
142
+ }
143
+
144
+ return (
145
+ <div className="w-full flex flex-col gap-[40px]">
146
+ {/* Empty state if no results after filtering */}
147
+ {!isLoading && !hasResults && (
148
+ hasActiveFilters ? (
149
+ <EmptyState
150
+ type="search"
151
+ title="No tasks found"
152
+ description="No tasks match your current filters. Try adjusting your search or status filter."
153
+ showCTA={true}
154
+ ctaText="Reset Filters"
155
+ onCtaClick={() => {
156
+ const params = new URLSearchParams(searchParams.toString());
157
+ params.delete(searchParamKey);
158
+ params.delete(taskTypeParamKey);
159
+ router.replace(`${pathname}?${params.toString()}`, { scroll: false });
160
+ }}
161
+ />
162
+ ) : (
163
+ <EmptyState
164
+ type="generic"
165
+ title="No tasks available"
166
+ description="Check back soon for upcoming tasks!"
167
+ showCTA={false}
168
+ />
169
+ )
170
+ )}
171
+
172
+ {/* Completed Tasks Table */}
173
+ {showCompleted && (hasResults || isLoading) && (
174
+ <div className="w-full">
175
+ <h3 className="text-h2 text-ods-text-primary tracking-[-0.48px] md:tracking-[-0.56px] lg:tracking-[-0.64px] mb-4">
176
+ Recently Completed<span className="text-ods-accent">:</span>
177
+ </h3>
178
+ <DeliveryTable
179
+ items={filteredCompleted}
180
+ isLoading={isLoading}
181
+ />
182
+ </div>
183
+ )}
184
+
185
+ {/* In Progress Tasks Table */}
186
+ {showInProgress && (hasResults || isLoading) && (
187
+ <div className="w-full">
188
+ <h3 className="text-h2 text-ods-text-primary tracking-[-0.48px] md:tracking-[-0.56px] lg:tracking-[-0.64px] mb-4">
189
+ Active Tasks<span className="text-ods-accent">:</span>
190
+ </h3>
191
+ <DeliveryTable
192
+ items={filteredInProgress}
193
+ isLoading={isLoading}
194
+ />
195
+ </div>
196
+ )}
197
+ </div>
198
+ );
199
+ }