@flamingo-stack/openframe-frontend-core 0.0.206 → 0.0.207-snapshot.20260526154403
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-OLTGB32E.js → chunk-2HMZSCJY.js} +3179 -2078
- package/dist/chunk-2HMZSCJY.js.map +1 -0
- package/dist/chunk-4XLJWX2N.js +12 -0
- package/dist/chunk-4XLJWX2N.js.map +1 -0
- package/dist/{chunk-YGOJIDL5.cjs → chunk-C5EC5AZM.cjs} +1660 -559
- package/dist/chunk-C5EC5AZM.cjs.map +1 -0
- package/dist/chunk-VFKQMAUF.cjs +12 -0
- package/dist/chunk-VFKQMAUF.cjs.map +1 -0
- package/dist/components/chat/embeddable-chat.d.ts +35 -2
- package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/index.d.ts +3 -0
- package/dist/components/chat/hooks/index.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-embedded-chat.d.ts +10 -169
- package/dist/components/chat/hooks/use-embedded-chat.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts +85 -0
- package/dist/components/chat/hooks/use-nats-chat-adapter.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-realtime-chunk-processor.d.ts.map +1 -1
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts +124 -0
- package/dist/components/chat/hooks/use-sse-chat-adapter.d.ts.map +1 -0
- package/dist/components/chat/hooks/use-unified-chat.d.ts +33 -0
- package/dist/components/chat/hooks/use-unified-chat.d.ts.map +1 -0
- package/dist/components/chat/index.cjs +8 -2
- package/dist/components/chat/index.cjs.map +1 -1
- package/dist/components/chat/index.js +11 -5
- package/dist/components/chat/types/api.types.d.ts +17 -1
- package/dist/components/chat/types/api.types.d.ts.map +1 -1
- package/dist/components/chat/types/index.d.ts +1 -0
- package/dist/components/chat/types/index.d.ts.map +1 -1
- package/dist/components/chat/types/unified-chat-state.types.d.ts +185 -0
- package/dist/components/chat/types/unified-chat-state.types.d.ts.map +1 -0
- package/dist/components/features/index.cjs +3 -2
- package/dist/components/features/index.cjs.map +1 -1
- package/dist/components/features/index.js +2 -1
- package/dist/components/index.cjs +26 -2
- package/dist/components/index.cjs.map +1 -1
- package/dist/components/index.d.ts +4 -0
- package/dist/components/index.d.ts.map +1 -1
- package/dist/components/index.js +27 -3
- package/dist/components/navigation/index.cjs +3 -2
- package/dist/components/navigation/index.cjs.map +1 -1
- package/dist/components/navigation/index.js +2 -1
- package/dist/components/shared/delivery/delivery-lists.d.ts +16 -0
- package/dist/components/shared/delivery/delivery-lists.d.ts.map +1 -0
- package/dist/components/shared/delivery/delivery-table.d.ts +12 -0
- package/dist/components/shared/delivery/delivery-table.d.ts.map +1 -0
- package/dist/components/shared/delivery/index.d.ts +3 -0
- package/dist/components/shared/delivery/index.d.ts.map +1 -0
- package/dist/components/shared/dev-section/dev-section-page.d.ts +31 -0
- package/dist/components/shared/dev-section/dev-section-page.d.ts.map +1 -0
- package/dist/components/shared/dev-section/dev-section-view.d.ts +34 -0
- package/dist/components/shared/dev-section/dev-section-view.d.ts.map +1 -0
- package/dist/components/shared/dev-section/index.d.ts +3 -0
- package/dist/components/shared/dev-section/index.d.ts.map +1 -0
- package/dist/components/shared/legal-document/index.d.ts +10 -0
- package/dist/components/shared/legal-document/index.d.ts.map +1 -0
- package/dist/components/shared/legal-document/legal-document-page.d.ts +66 -0
- package/dist/components/shared/legal-document/legal-document-page.d.ts.map +1 -0
- package/dist/components/shared/legal-document/use-legal-docs.d.ts +40 -0
- package/dist/components/shared/legal-document/use-legal-docs.d.ts.map +1 -0
- package/dist/components/shared/product-release/index.d.ts +2 -1
- package/dist/components/shared/product-release/index.d.ts.map +1 -1
- package/dist/components/shared/product-release/release-detail-page.d.ts +11 -7
- package/dist/components/shared/product-release/release-detail-page.d.ts.map +1 -1
- package/dist/components/shared/roadmap/index.d.ts +18 -0
- package/dist/components/shared/roadmap/index.d.ts.map +1 -0
- package/dist/components/shared/roadmap/roadmap-grid-skeleton.d.ts +24 -0
- package/dist/components/shared/roadmap/roadmap-grid-skeleton.d.ts.map +1 -0
- package/dist/components/shared/roadmap/roadmap-grid.d.ts +18 -0
- package/dist/components/shared/roadmap/roadmap-grid.d.ts.map +1 -0
- package/dist/components/shared/roadmap/use-roadmap-voting.d.ts +25 -0
- package/dist/components/shared/roadmap/use-roadmap-voting.d.ts.map +1 -0
- package/dist/components/ui/index.cjs +8 -2
- package/dist/components/ui/index.cjs.map +1 -1
- package/dist/components/ui/index.js +11 -5
- package/dist/components/ui/release-changelog-section.d.ts +13 -2
- package/dist/components/ui/release-changelog-section.d.ts.map +1 -1
- package/dist/embed-shims/index.cjs +1 -6
- package/dist/embed-shims/index.cjs.map +1 -1
- package/dist/embed-shims/index.js +1 -6
- package/dist/embed-shims/index.js.map +1 -1
- package/dist/index.cjs +18 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +19 -3
- package/dist/types/delivery.d.ts +49 -0
- package/dist/types/delivery.d.ts.map +1 -0
- package/dist/types/index.cjs +13 -0
- package/dist/types/index.cjs.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +12 -1
- package/dist/types/index.js.map +1 -1
- package/dist/utils/dev-sections/index.d.ts +11 -0
- package/dist/utils/dev-sections/index.d.ts.map +1 -0
- package/dist/utils/dev-sections/openframe-dev-sections.d.ts +209 -0
- package/dist/utils/dev-sections/openframe-dev-sections.d.ts.map +1 -0
- package/dist/utils/index.cjs +82 -0
- package/dist/utils/index.cjs.map +1 -1
- package/dist/utils/index.d.ts +1 -0
- package/dist/utils/index.d.ts.map +1 -1
- package/dist/utils/index.js +81 -2
- package/dist/utils/index.js.map +1 -1
- package/package.json +1 -1
- package/src/components/chat/embeddable-chat.tsx +123 -8
- package/src/components/chat/hooks/index.ts +9 -2
- package/src/components/chat/hooks/use-embedded-chat.ts +18 -1016
- package/src/components/chat/hooks/use-nats-chat-adapter.ts +372 -0
- package/src/components/chat/hooks/use-realtime-chunk-processor.ts +53 -6
- package/src/components/chat/hooks/use-sse-chat-adapter.ts +1058 -0
- package/src/components/chat/hooks/use-unified-chat.ts +171 -0
- package/src/components/chat/types/api.types.ts +23 -1
- package/src/components/chat/types/index.ts +1 -0
- package/src/components/chat/types/unified-chat-state.types.ts +215 -0
- package/src/components/index.ts +8 -0
- package/src/components/shared/delivery/delivery-lists.tsx +199 -0
- package/src/components/shared/delivery/delivery-table.tsx +174 -0
- package/src/components/shared/delivery/index.ts +9 -0
- package/src/components/shared/dev-section/dev-section-page.tsx +72 -0
- package/src/components/shared/dev-section/dev-section-view.tsx +129 -0
- package/src/components/shared/dev-section/index.ts +2 -0
- package/src/components/shared/legal-document/index.ts +19 -0
- package/src/components/shared/legal-document/legal-document-page.tsx +178 -0
- package/src/components/shared/legal-document/use-legal-docs.ts +123 -0
- package/src/components/shared/product-release/index.ts +14 -3
- package/src/components/shared/product-release/release-detail-page.tsx +45 -7
- package/src/components/shared/roadmap/index.ts +23 -0
- package/src/components/shared/roadmap/roadmap-grid-skeleton.tsx +74 -0
- package/src/components/shared/roadmap/roadmap-grid.tsx +106 -0
- package/src/components/shared/roadmap/use-roadmap-voting.ts +163 -0
- package/src/components/ui/release-changelog-section.tsx +113 -32
- package/src/stories/EmbeddableChat.stories.tsx +186 -0
- package/src/types/delivery.ts +54 -0
- package/src/types/index.ts +1 -0
- package/src/utils/dev-sections/index.ts +17 -0
- package/src/utils/dev-sections/openframe-dev-sections.ts +148 -0
- package/src/utils/index.ts +6 -1
- package/dist/chunk-OLTGB32E.js.map +0 -1
- package/dist/chunk-YGOJIDL5.cjs.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,7 +5,13 @@
|
|
|
5
5
|
|
|
6
6
|
import type { ChunkData, NatsMessageType, FetchChunksFunction } from './network.types'
|
|
7
7
|
import type { ChatType, ChatApprovalStatus } from './chat.types'
|
|
8
|
-
import type {
|
|
8
|
+
import type {
|
|
9
|
+
MessageSegment,
|
|
10
|
+
PendingToolCallData,
|
|
11
|
+
TokenUsageData,
|
|
12
|
+
ExecutingToolState,
|
|
13
|
+
ToolExecutionSegment,
|
|
14
|
+
} from './message.types'
|
|
9
15
|
|
|
10
16
|
// ========== Hook Options ==========
|
|
11
17
|
|
|
@@ -169,6 +175,22 @@ export interface RealtimeChunkCallbacks {
|
|
|
169
175
|
onEscalatedApproval?: (requestId: string, data: { command: string; explanation?: string; approvalType: string }) => void
|
|
170
176
|
/** Called when an escalated approval result is received */
|
|
171
177
|
onEscalatedApprovalResult?: (requestId: string, approved: boolean, data: { command: string; explanation?: string; approvalType: string }) => void
|
|
178
|
+
/**
|
|
179
|
+
* Called whenever an `APPROVAL_RESULT` chunk is processed. Fires in addition
|
|
180
|
+
* to the accumulator's in-message status flip so consumers can find the
|
|
181
|
+
* matching `approval_request` / `approval_batch` segment in an *earlier*
|
|
182
|
+
* message bubble (e.g. when a user-interrupted approval is resolved while
|
|
183
|
+
* a new assistant message is streaming). Idempotent — safe to no-op if no
|
|
184
|
+
* matching segment is found dialog-wide.
|
|
185
|
+
*/
|
|
186
|
+
onApprovalResolved?: (requestId: string, status: ChatApprovalStatus, approvalType: string) => void
|
|
187
|
+
/**
|
|
188
|
+
* Called whenever an `EXECUTED_TOOL` chunk is processed. Lets consumers
|
|
189
|
+
* merge the result into the originating `EXECUTING_TOOL` (or batch
|
|
190
|
+
* `executions[execId]`) segment in an earlier message bubble when the tool
|
|
191
|
+
* outlived its message scope. Idempotent.
|
|
192
|
+
*/
|
|
193
|
+
onToolExecuted?: (segment: ToolExecutionSegment) => void
|
|
172
194
|
/** Called when a DIALOG_CLOSED chunk is received */
|
|
173
195
|
onDialogClosed?: () => void
|
|
174
196
|
}
|
|
@@ -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
|
+
}
|
package/src/components/index.ts
CHANGED
|
@@ -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
|
+
}
|