@neuronection/assistant-ui 0.50.1 → 0.51.0
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/CHANGELOG.md +6 -0
- package/dist/chat-core.d.ts +18 -6
- package/dist/chat-core.js +1 -1
- package/dist/{chunk-6GNLY2EB.js → chunk-O3OLATVE.js} +7 -4
- package/dist/chunk-O3OLATVE.js.map +1 -0
- package/dist/index.d.ts +18 -6
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-6GNLY2EB.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
# @neuronection/assistant-ui
|
|
2
2
|
|
|
3
|
+
## 0.51.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [`fabdc5d`](https://github.com/neuronection/assistant-ui/commit/fabdc5da389b64b96152acdd9092aa6c7455b356) Thanks [@constLiakos](https://github.com/constLiakos)! - **chat-core**: new terminal `flow_interrupted` stream event — `{ event: 'flow_interrupted'; reason?: 'user' | 'server'; partial?: boolean }` (plan 24 §2, additive). The server broadcasts it when a turn is cancelled so every consumer terminalizes — not just the one that issued the stop (a stop in one tab reaches the others). The reducer maps it to `status: 'interrupted'`, `stopped: true`, with `finishedAt` — the same terminality class as `flow_finished`/`flow_failed`, and distinct from the resumable HITL `interrupt` pause. `useChatStream` treats it as terminal (late events ignored, watchdog cleared, `reset()` re-arms).
|
|
8
|
+
|
|
3
9
|
## 0.50.1
|
|
4
10
|
|
|
5
11
|
### Patch Changes
|
package/dist/chat-core.d.ts
CHANGED
|
@@ -8,9 +8,12 @@ interface FlowStepInfo {
|
|
|
8
8
|
}
|
|
9
9
|
/**
|
|
10
10
|
* Family streaming vocabulary (guidelines/ai-features.md §5), extended with
|
|
11
|
-
* the `tool_call` observation event
|
|
12
|
-
*
|
|
13
|
-
*
|
|
11
|
+
* the `tool_call` observation event and the terminal `flow_interrupted`
|
|
12
|
+
* stop. `interrupt` is the resumable HITL pause; `flow_interrupted` is the
|
|
13
|
+
* terminal cancel the server broadcasts so every consumer of the turn
|
|
14
|
+
* learns it was stopped. Transports map their wire format (SSE named
|
|
15
|
+
* events, WS topic payloads) onto these shapes app-side; the reducer
|
|
16
|
+
* tolerates unknown `event` values by ignoring them.
|
|
14
17
|
*/
|
|
15
18
|
type ChatStreamEvent = {
|
|
16
19
|
event: 'flow_started';
|
|
@@ -58,6 +61,10 @@ type ChatStreamEvent = {
|
|
|
58
61
|
message: string;
|
|
59
62
|
retryable: boolean;
|
|
60
63
|
run_id?: string;
|
|
64
|
+
} | {
|
|
65
|
+
event: 'flow_interrupted';
|
|
66
|
+
reason?: 'user' | 'server';
|
|
67
|
+
partial?: boolean;
|
|
61
68
|
};
|
|
62
69
|
|
|
63
70
|
type LiveTurnStatus = 'idle' | 'pending' | 'streaming' | 'interrupted' | 'done' | 'error';
|
|
@@ -77,9 +84,14 @@ interface LiveToolCall {
|
|
|
77
84
|
}
|
|
78
85
|
/**
|
|
79
86
|
* Ephemeral state of the in-flight assistant turn. `text`/`reasoning` are
|
|
80
|
-
* `null` until the first delta (study's "not streaming yet" sentinel)
|
|
81
|
-
* `interrupted`
|
|
82
|
-
*
|
|
87
|
+
* `null` until the first delta (study's "not streaming yet" sentinel).
|
|
88
|
+
* `status: 'interrupted'` has two distinct readings: the HITL `interrupt`
|
|
89
|
+
* event is a *resumable pause* — it carries `interruptPayload` and the
|
|
90
|
+
* next delta streams again — while a *stop* is terminal for event intake:
|
|
91
|
+
* the local `stop` action (the user cancel this consumer issued) or the
|
|
92
|
+
* server-broadcast `flow_interrupted` event (how a consumer that did NOT
|
|
93
|
+
* issue the stop learns the turn was cancelled, e.g. another tab). Both
|
|
94
|
+
* stop paths set `stopped: true`; `stopped`/`done`/`error` close intake.
|
|
83
95
|
*/
|
|
84
96
|
interface LiveTurnState {
|
|
85
97
|
status: LiveTurnStatus;
|
package/dist/chat-core.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export { initialLiveTurnState, liveTurnReducer, useChatStream } from './chunk-
|
|
1
|
+
export { initialLiveTurnState, liveTurnReducer, useChatStream } from './chunk-O3OLATVE.js';
|
|
2
2
|
export { activePathSet, buildBranchTree, linearTree, variantInfo, walkActivePath } from './chunk-Z4IBQ53C.js';
|
|
3
3
|
//# sourceMappingURL=chat-core.js.map
|
|
4
4
|
//# sourceMappingURL=chat-core.js.map
|
|
@@ -70,7 +70,8 @@ function liveTurnReducer(state, action) {
|
|
|
70
70
|
if (state.status === "idle" || isTerminal(state)) {
|
|
71
71
|
return state;
|
|
72
72
|
}
|
|
73
|
-
|
|
73
|
+
const eventRunId = "run_id" in event ? event.run_id : void 0;
|
|
74
|
+
if (eventRunId !== void 0 && state.runId !== null && eventRunId !== state.runId) {
|
|
74
75
|
return state;
|
|
75
76
|
}
|
|
76
77
|
switch (event.event) {
|
|
@@ -104,6 +105,8 @@ function liveTurnReducer(state, action) {
|
|
|
104
105
|
error: { code: event.code, message: event.message, retryable: event.retryable },
|
|
105
106
|
finishedAt: at
|
|
106
107
|
};
|
|
108
|
+
case "flow_interrupted":
|
|
109
|
+
return { ...state, status: "interrupted", stopped: true, finishedAt: at };
|
|
107
110
|
default:
|
|
108
111
|
return state;
|
|
109
112
|
}
|
|
@@ -178,7 +181,7 @@ function useChatStream(options) {
|
|
|
178
181
|
return;
|
|
179
182
|
}
|
|
180
183
|
flush();
|
|
181
|
-
if (event.event === "flow_finished" || event.event === "flow_failed") {
|
|
184
|
+
if (event.event === "flow_finished" || event.event === "flow_failed" || event.event === "flow_interrupted") {
|
|
182
185
|
terminalRef.current = true;
|
|
183
186
|
clearTimeoutTimer();
|
|
184
187
|
}
|
|
@@ -268,5 +271,5 @@ function useChatStream(options) {
|
|
|
268
271
|
}
|
|
269
272
|
|
|
270
273
|
export { initialLiveTurnState, liveTurnReducer, useChatStream };
|
|
271
|
-
//# sourceMappingURL=chunk-
|
|
272
|
-
//# sourceMappingURL=chunk-
|
|
274
|
+
//# sourceMappingURL=chunk-O3OLATVE.js.map
|
|
275
|
+
//# sourceMappingURL=chunk-O3OLATVE.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/chat-core/liveTurnReducer.ts","../src/components/chat-core/useChatStream.ts"],"names":[],"mappings":"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","file":"chunk-O3OLATVE.js","sourcesContent":["import type { ChatStreamEvent } from './events'\nimport type { ChatError } from './types'\n\nexport type LiveTurnStatus = 'idle' | 'pending' | 'streaming' | 'interrupted' | 'done' | 'error'\n\nexport interface LiveNodeState {\n id: string\n label?: string\n status: 'pending' | 'running' | 'done' | 'failed' | 'interrupted'\n}\n\nexport interface LiveToolCall {\n id: string\n name: string\n title?: string\n status: 'running' | 'done' | 'failed'\n args?: string\n result?: string\n durationMs?: number\n}\n\n/**\n * Ephemeral state of the in-flight assistant turn. `text`/`reasoning` are\n * `null` until the first delta (study's \"not streaming yet\" sentinel).\n * `status: 'interrupted'` has two distinct readings: the HITL `interrupt`\n * event is a *resumable pause* — it carries `interruptPayload` and the\n * next delta streams again — while a *stop* is terminal for event intake:\n * the local `stop` action (the user cancel this consumer issued) or the\n * server-broadcast `flow_interrupted` event (how a consumer that did NOT\n * issue the stop learns the turn was cancelled, e.g. another tab). Both\n * stop paths set `stopped: true`; `stopped`/`done`/`error` close intake.\n */\nexport interface LiveTurnState {\n status: LiveTurnStatus\n text: string | null\n reasoning: string | null\n toolCalls: LiveToolCall[]\n nodes: LiveNodeState[]\n error: ChatError | null\n interruptPayload: unknown | null\n runId: string | null\n stopped: boolean\n startedAt: number | null\n finishedAt: number | null\n}\n\nexport type LiveTurnAction =\n | { type: 'send'; at: number }\n | { type: 'event'; event: ChatStreamEvent; at?: number }\n | { type: 'stop'; at: number }\n | { type: 'reset' }\n\nexport const initialLiveTurnState: LiveTurnState = {\n status: 'idle',\n text: null,\n reasoning: null,\n toolCalls: [],\n nodes: [],\n error: null,\n interruptPayload: null,\n runId: null,\n stopped: false,\n startedAt: null,\n finishedAt: null,\n}\n\nfunction isTerminal(state: LiveTurnState): boolean {\n return state.status === 'done' || state.status === 'error' || state.stopped\n}\n\nfunction upsertNode(nodes: LiveNodeState[], id: string, label: string | undefined, status: LiveNodeState['status']): LiveNodeState[] {\n const index = nodes.findIndex((node) => node.id === id)\n if (index === -1) {\n return [...nodes, { id, label, status }]\n }\n const next = nodes.slice()\n const current = next[index]!\n next[index] = { ...current, label: label ?? current.label, status }\n return next\n}\n\nfunction upsertToolCall(state: LiveTurnState, event: Extract<ChatStreamEvent, { event: 'tool_call' }>): LiveToolCall[] {\n const index = state.toolCalls.findIndex((call) => call.id === event.id)\n const merged = {\n id: event.id,\n name: event.name,\n title: event.title,\n status: event.status,\n args: event.args,\n result: event.result,\n durationMs: event.durationMs,\n }\n if (index === -1) {\n return [...state.toolCalls, merged]\n }\n const next = state.toolCalls.slice()\n const current = next[index]!\n next[index] = {\n ...current,\n ...merged,\n title: merged.title ?? current.title,\n args: merged.args ?? current.args,\n result: merged.result ?? current.result,\n durationMs: merged.durationMs ?? current.durationMs,\n }\n return next\n}\n\n/**\n * Pure state machine for one streamed assistant turn — the fixture-testable\n * core behind `useChatStream` (same role career's `chatFlow.ts` plays for\n * `FlowStatusCard`). Unknown event names are ignored (additive contract).\n */\nexport function liveTurnReducer(state: LiveTurnState, action: LiveTurnAction): LiveTurnState {\n switch (action.type) {\n case 'send':\n return { ...initialLiveTurnState, status: 'pending', startedAt: action.at }\n case 'stop': {\n if (state.status !== 'pending' && state.status !== 'streaming') {\n return state\n }\n return { ...state, status: 'interrupted', stopped: true, finishedAt: action.at }\n }\n case 'reset':\n return initialLiveTurnState\n case 'event': {\n const { event, at = Date.now() } = action\n if (state.status === 'idle' || isTerminal(state)) {\n return state\n }\n const eventRunId = 'run_id' in event ? event.run_id : undefined\n if (eventRunId !== undefined && state.runId !== null && eventRunId !== state.runId) {\n return state\n }\n switch (event.event) {\n case 'flow_started':\n return {\n ...state,\n runId: event.run_id ?? state.runId,\n nodes:\n event.steps !== undefined && event.steps.length > 0\n ? event.steps.map((step): LiveNodeState => ({ id: step.id, label: step.label, status: 'pending' }))\n : state.nodes,\n }\n case 'node_started':\n return { ...state, nodes: upsertNode(state.nodes, event.node, event.label, 'running') }\n case 'node_finished':\n return { ...state, nodes: upsertNode(state.nodes, event.node, event.label, event.outcome) }\n case 'delta': {\n const streaming = { ...state, status: 'streaming' as const }\n if (event.kind === 'reasoning') {\n return { ...streaming, reasoning: (state.reasoning ?? '') + event.text }\n }\n return { ...streaming, text: (state.text ?? '') + event.text }\n }\n case 'tool_call':\n return { ...state, toolCalls: upsertToolCall(state, event) }\n case 'interrupt':\n return { ...state, status: 'interrupted', interruptPayload: event.payload, finishedAt: at }\n case 'flow_finished':\n return { ...state, status: 'done', finishedAt: at }\n case 'flow_failed':\n return {\n ...state,\n status: 'error',\n error: { code: event.code, message: event.message, retryable: event.retryable },\n finishedAt: at,\n }\n case 'flow_interrupted':\n return { ...state, status: 'interrupted', stopped: true, finishedAt: at }\n default:\n return state\n }\n }\n default:\n return state\n }\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\n\nimport type { ChatStreamEvent } from './events'\nimport { initialLiveTurnState, liveTurnReducer, type LiveTurnAction, type LiveTurnState } from './liveTurnReducer'\n\nexport interface ChatStreamTransport {\n /** Enqueue/send a user turn. Resolves when accepted; errors surface as a failed turn. */\n send(input: { text: string }): Promise<void>\n /** Subscribe to the session's stream events; returns an unsubscribe function. */\n subscribe(handlers: { onEvent: (event: ChatStreamEvent) => void }): () => void\n /** Best-effort cancel of the in-flight turn (backend stop endpoint). */\n stop?(): Promise<void>\n}\n\nexport interface UseChatStreamOptions {\n transport: ChatStreamTransport\n /** Delta coalescing window. Default 33 ms (study's rAF-scale budget). */\n flushMs?: number\n /** Watchdog for a turn that never terminates. Default 90 000 ms. */\n timeoutMs?: number\n}\n\nexport interface UseChatStreamResult extends LiveTurnState {\n /** The live turn, or `null` when idle — hand to the transcript's `live` slot. */\n live: LiveTurnState | null\n /** Returns false and does nothing for blank input or while a turn is active. */\n send(text: string): Promise<boolean>\n stop(): Promise<void>\n reset(): void\n}\n\n/**\n * Transport-injected live-turn state machine for chat surfaces: send →\n * pending → streaming (coalesced text/reasoning deltas, tool calls, node\n * progress) → done / error / interrupted. Persists nothing — the app owns\n * sessions, messages and the branching endpoints; after a terminal state\n * the app refetches and calls `reset()`.\n */\nexport function useChatStream(options: UseChatStreamOptions): UseChatStreamResult {\n const { transport, flushMs = 33, timeoutMs = 90_000 } = options\n const [state, setState] = useState<LiveTurnState>(initialLiveTurnState)\n const stateRef = useRef(state)\n const dispatch = useCallback((action: LiveTurnAction) => {\n const next = liveTurnReducer(stateRef.current, action)\n stateRef.current = next\n setState(next)\n }, [])\n\n const transportRef = useRef(transport)\n transportRef.current = transport\n\n const textBufferRef = useRef('')\n const reasoningBufferRef = useRef('')\n const flushTimerRef = useRef<number | null>(null)\n const timeoutTimerRef = useRef<number | null>(null)\n const terminalRef = useRef(false)\n\n const cancelFlush = useCallback(() => {\n if (flushTimerRef.current !== null) {\n window.clearTimeout(flushTimerRef.current)\n flushTimerRef.current = null\n }\n }, [])\n\n const flush = useCallback(() => {\n cancelFlush()\n const text = textBufferRef.current\n const reasoning = reasoningBufferRef.current\n textBufferRef.current = ''\n reasoningBufferRef.current = ''\n if (text !== '') {\n dispatch({ type: 'event', event: { event: 'delta', text, kind: 'text' } })\n }\n if (reasoning !== '') {\n dispatch({ type: 'event', event: { event: 'delta', text: reasoning, kind: 'reasoning' } })\n }\n }, [cancelFlush, dispatch])\n\n const scheduleFlush = useCallback(() => {\n if (flushTimerRef.current !== null) {\n return\n }\n flushTimerRef.current = window.setTimeout(() => {\n flushTimerRef.current = null\n flush()\n }, flushMs)\n }, [flush, flushMs])\n\n const clearTimeoutTimer = useCallback(() => {\n if (timeoutTimerRef.current !== null) {\n window.clearTimeout(timeoutTimerRef.current)\n timeoutTimerRef.current = null\n }\n }, [])\n\n useEffect(() => {\n const unsubscribe = transportRef.current.subscribe({\n onEvent: (event) => {\n if (terminalRef.current) {\n return\n }\n if (event.event === 'delta') {\n if (event.kind === 'reasoning') {\n reasoningBufferRef.current += event.text\n } else {\n textBufferRef.current += event.text\n }\n scheduleFlush()\n return\n }\n flush()\n if (event.event === 'flow_finished' || event.event === 'flow_failed' || event.event === 'flow_interrupted') {\n terminalRef.current = true\n clearTimeoutTimer()\n }\n dispatch({ type: 'event', event })\n },\n })\n return unsubscribe\n }, [scheduleFlush, flush, clearTimeoutTimer, dispatch])\n\n useEffect(\n () => () => {\n cancelFlush()\n clearTimeoutTimer()\n },\n [cancelFlush, clearTimeoutTimer],\n )\n\n const send = useCallback(\n async (text: string) => {\n const trimmed = text.trim()\n const active = stateRef.current.status === 'pending' || stateRef.current.status === 'streaming'\n if (trimmed === '' || active) {\n return false\n }\n terminalRef.current = false\n textBufferRef.current = ''\n reasoningBufferRef.current = ''\n cancelFlush()\n clearTimeoutTimer()\n dispatch({ type: 'send', at: Date.now() })\n timeoutTimerRef.current = window.setTimeout(() => {\n if (terminalRef.current) {\n return\n }\n terminalRef.current = true\n flush()\n dispatch({\n type: 'event',\n event: { event: 'flow_failed', code: 'timeout', message: 'Chat turn timed out', retryable: true },\n })\n }, timeoutMs)\n try {\n await transportRef.current.send({ text: trimmed })\n } catch (sendError) {\n if (terminalRef.current) {\n return true\n }\n terminalRef.current = true\n clearTimeoutTimer()\n flush()\n dispatch({\n type: 'event',\n event: {\n event: 'flow_failed',\n code: 'send_failed',\n message: sendError instanceof Error ? sendError.message : String(sendError),\n retryable: true,\n },\n })\n }\n return true\n },\n [cancelFlush, clearTimeoutTimer, dispatch, flush, timeoutMs],\n )\n\n const stop = useCallback(async () => {\n const current = stateRef.current\n if (current.status !== 'pending' && current.status !== 'streaming') {\n return\n }\n terminalRef.current = true\n clearTimeoutTimer()\n flush()\n dispatch({ type: 'stop', at: Date.now() })\n await transportRef.current.stop?.().catch(() => undefined)\n }, [clearTimeoutTimer, dispatch, flush])\n\n const reset = useCallback(() => {\n terminalRef.current = false\n cancelFlush()\n clearTimeoutTimer()\n dispatch({ type: 'reset' })\n }, [cancelFlush, clearTimeoutTimer, dispatch])\n\n return {\n ...state,\n live: state.status === 'idle' ? null : state,\n send,\n stop,\n reset,\n }\n}\n"]}
|
package/dist/index.d.ts
CHANGED
|
@@ -2079,9 +2079,12 @@ interface FlowStepInfo {
|
|
|
2079
2079
|
}
|
|
2080
2080
|
/**
|
|
2081
2081
|
* Family streaming vocabulary (guidelines/ai-features.md §5), extended with
|
|
2082
|
-
* the `tool_call` observation event
|
|
2083
|
-
*
|
|
2084
|
-
*
|
|
2082
|
+
* the `tool_call` observation event and the terminal `flow_interrupted`
|
|
2083
|
+
* stop. `interrupt` is the resumable HITL pause; `flow_interrupted` is the
|
|
2084
|
+
* terminal cancel the server broadcasts so every consumer of the turn
|
|
2085
|
+
* learns it was stopped. Transports map their wire format (SSE named
|
|
2086
|
+
* events, WS topic payloads) onto these shapes app-side; the reducer
|
|
2087
|
+
* tolerates unknown `event` values by ignoring them.
|
|
2085
2088
|
*/
|
|
2086
2089
|
type ChatStreamEvent = {
|
|
2087
2090
|
event: 'flow_started';
|
|
@@ -2129,6 +2132,10 @@ type ChatStreamEvent = {
|
|
|
2129
2132
|
message: string;
|
|
2130
2133
|
retryable: boolean;
|
|
2131
2134
|
run_id?: string;
|
|
2135
|
+
} | {
|
|
2136
|
+
event: 'flow_interrupted';
|
|
2137
|
+
reason?: 'user' | 'server';
|
|
2138
|
+
partial?: boolean;
|
|
2132
2139
|
};
|
|
2133
2140
|
|
|
2134
2141
|
type LiveTurnStatus = 'idle' | 'pending' | 'streaming' | 'interrupted' | 'done' | 'error';
|
|
@@ -2148,9 +2155,14 @@ interface LiveToolCall {
|
|
|
2148
2155
|
}
|
|
2149
2156
|
/**
|
|
2150
2157
|
* Ephemeral state of the in-flight assistant turn. `text`/`reasoning` are
|
|
2151
|
-
* `null` until the first delta (study's "not streaming yet" sentinel)
|
|
2152
|
-
* `interrupted`
|
|
2153
|
-
*
|
|
2158
|
+
* `null` until the first delta (study's "not streaming yet" sentinel).
|
|
2159
|
+
* `status: 'interrupted'` has two distinct readings: the HITL `interrupt`
|
|
2160
|
+
* event is a *resumable pause* — it carries `interruptPayload` and the
|
|
2161
|
+
* next delta streams again — while a *stop* is terminal for event intake:
|
|
2162
|
+
* the local `stop` action (the user cancel this consumer issued) or the
|
|
2163
|
+
* server-broadcast `flow_interrupted` event (how a consumer that did NOT
|
|
2164
|
+
* issue the stop learns the turn was cancelled, e.g. another tab). Both
|
|
2165
|
+
* stop paths set `stopped: true`; `stopped`/`done`/`error` close intake.
|
|
2154
2166
|
*/
|
|
2155
2167
|
interface LiveTurnState {
|
|
2156
2168
|
status: LiveTurnStatus;
|
package/dist/index.js
CHANGED
|
@@ -70,7 +70,7 @@ export { AiMagicFill } from './chunk-X6L5Z5LV.js';
|
|
|
70
70
|
export { FormModal } from './chunk-QPY2Y532.js';
|
|
71
71
|
export { Modal, ModalBody, ModalClose, ModalContent, ModalDescription, ModalFooter, ModalHeader, ModalTitle, ModalTrigger, modalContentVariants } from './chunk-FVQWIDB5.js';
|
|
72
72
|
export { Breadcrumbs } from './chunk-FILCIMXX.js';
|
|
73
|
-
export { initialLiveTurnState, liveTurnReducer, useChatStream } from './chunk-
|
|
73
|
+
export { initialLiveTurnState, liveTurnReducer, useChatStream } from './chunk-O3OLATVE.js';
|
|
74
74
|
export { activePathSet, buildBranchTree, linearTree, variantInfo, walkActivePath } from './chunk-Z4IBQ53C.js';
|
|
75
75
|
export { ChatLauncher } from './chunk-E3NZM4U7.js';
|
|
76
76
|
export { Portal } from './chunk-XZWLTTHA.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neuronection/assistant-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.51.0",
|
|
4
4
|
"description": "Shared React component library for the Assistant family (career-, study-, health-, desktop-assistant). Family look by default, app-flavored via CSS-variable tokens.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/chat-core/liveTurnReducer.ts","../src/components/chat-core/useChatStream.ts"],"names":[],"mappings":"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","file":"chunk-6GNLY2EB.js","sourcesContent":["import type { ChatStreamEvent } from './events'\nimport type { ChatError } from './types'\n\nexport type LiveTurnStatus = 'idle' | 'pending' | 'streaming' | 'interrupted' | 'done' | 'error'\n\nexport interface LiveNodeState {\n id: string\n label?: string\n status: 'pending' | 'running' | 'done' | 'failed' | 'interrupted'\n}\n\nexport interface LiveToolCall {\n id: string\n name: string\n title?: string\n status: 'running' | 'done' | 'failed'\n args?: string\n result?: string\n durationMs?: number\n}\n\n/**\n * Ephemeral state of the in-flight assistant turn. `text`/`reasoning` are\n * `null` until the first delta (study's \"not streaming yet\" sentinel);\n * `interrupted` is a resumable pause (HITL) unless `stopped` marks a user\n * cancel; `stopped`/`done`/`error` are terminal for event intake.\n */\nexport interface LiveTurnState {\n status: LiveTurnStatus\n text: string | null\n reasoning: string | null\n toolCalls: LiveToolCall[]\n nodes: LiveNodeState[]\n error: ChatError | null\n interruptPayload: unknown | null\n runId: string | null\n stopped: boolean\n startedAt: number | null\n finishedAt: number | null\n}\n\nexport type LiveTurnAction =\n | { type: 'send'; at: number }\n | { type: 'event'; event: ChatStreamEvent; at?: number }\n | { type: 'stop'; at: number }\n | { type: 'reset' }\n\nexport const initialLiveTurnState: LiveTurnState = {\n status: 'idle',\n text: null,\n reasoning: null,\n toolCalls: [],\n nodes: [],\n error: null,\n interruptPayload: null,\n runId: null,\n stopped: false,\n startedAt: null,\n finishedAt: null,\n}\n\nfunction isTerminal(state: LiveTurnState): boolean {\n return state.status === 'done' || state.status === 'error' || state.stopped\n}\n\nfunction upsertNode(nodes: LiveNodeState[], id: string, label: string | undefined, status: LiveNodeState['status']): LiveNodeState[] {\n const index = nodes.findIndex((node) => node.id === id)\n if (index === -1) {\n return [...nodes, { id, label, status }]\n }\n const next = nodes.slice()\n const current = next[index]!\n next[index] = { ...current, label: label ?? current.label, status }\n return next\n}\n\nfunction upsertToolCall(state: LiveTurnState, event: Extract<ChatStreamEvent, { event: 'tool_call' }>): LiveToolCall[] {\n const index = state.toolCalls.findIndex((call) => call.id === event.id)\n const merged = {\n id: event.id,\n name: event.name,\n title: event.title,\n status: event.status,\n args: event.args,\n result: event.result,\n durationMs: event.durationMs,\n }\n if (index === -1) {\n return [...state.toolCalls, merged]\n }\n const next = state.toolCalls.slice()\n const current = next[index]!\n next[index] = {\n ...current,\n ...merged,\n title: merged.title ?? current.title,\n args: merged.args ?? current.args,\n result: merged.result ?? current.result,\n durationMs: merged.durationMs ?? current.durationMs,\n }\n return next\n}\n\n/**\n * Pure state machine for one streamed assistant turn — the fixture-testable\n * core behind `useChatStream` (same role career's `chatFlow.ts` plays for\n * `FlowStatusCard`). Unknown event names are ignored (additive contract).\n */\nexport function liveTurnReducer(state: LiveTurnState, action: LiveTurnAction): LiveTurnState {\n switch (action.type) {\n case 'send':\n return { ...initialLiveTurnState, status: 'pending', startedAt: action.at }\n case 'stop': {\n if (state.status !== 'pending' && state.status !== 'streaming') {\n return state\n }\n return { ...state, status: 'interrupted', stopped: true, finishedAt: action.at }\n }\n case 'reset':\n return initialLiveTurnState\n case 'event': {\n const { event, at = Date.now() } = action\n if (state.status === 'idle' || isTerminal(state)) {\n return state\n }\n if (event.run_id !== undefined && state.runId !== null && event.run_id !== state.runId) {\n return state\n }\n switch (event.event) {\n case 'flow_started':\n return {\n ...state,\n runId: event.run_id ?? state.runId,\n nodes:\n event.steps !== undefined && event.steps.length > 0\n ? event.steps.map((step): LiveNodeState => ({ id: step.id, label: step.label, status: 'pending' }))\n : state.nodes,\n }\n case 'node_started':\n return { ...state, nodes: upsertNode(state.nodes, event.node, event.label, 'running') }\n case 'node_finished':\n return { ...state, nodes: upsertNode(state.nodes, event.node, event.label, event.outcome) }\n case 'delta': {\n const streaming = { ...state, status: 'streaming' as const }\n if (event.kind === 'reasoning') {\n return { ...streaming, reasoning: (state.reasoning ?? '') + event.text }\n }\n return { ...streaming, text: (state.text ?? '') + event.text }\n }\n case 'tool_call':\n return { ...state, toolCalls: upsertToolCall(state, event) }\n case 'interrupt':\n return { ...state, status: 'interrupted', interruptPayload: event.payload, finishedAt: at }\n case 'flow_finished':\n return { ...state, status: 'done', finishedAt: at }\n case 'flow_failed':\n return {\n ...state,\n status: 'error',\n error: { code: event.code, message: event.message, retryable: event.retryable },\n finishedAt: at,\n }\n default:\n return state\n }\n }\n default:\n return state\n }\n}\n","import { useCallback, useEffect, useRef, useState } from 'react'\n\nimport type { ChatStreamEvent } from './events'\nimport { initialLiveTurnState, liveTurnReducer, type LiveTurnAction, type LiveTurnState } from './liveTurnReducer'\n\nexport interface ChatStreamTransport {\n /** Enqueue/send a user turn. Resolves when accepted; errors surface as a failed turn. */\n send(input: { text: string }): Promise<void>\n /** Subscribe to the session's stream events; returns an unsubscribe function. */\n subscribe(handlers: { onEvent: (event: ChatStreamEvent) => void }): () => void\n /** Best-effort cancel of the in-flight turn (backend stop endpoint). */\n stop?(): Promise<void>\n}\n\nexport interface UseChatStreamOptions {\n transport: ChatStreamTransport\n /** Delta coalescing window. Default 33 ms (study's rAF-scale budget). */\n flushMs?: number\n /** Watchdog for a turn that never terminates. Default 90 000 ms. */\n timeoutMs?: number\n}\n\nexport interface UseChatStreamResult extends LiveTurnState {\n /** The live turn, or `null` when idle — hand to the transcript's `live` slot. */\n live: LiveTurnState | null\n /** Returns false and does nothing for blank input or while a turn is active. */\n send(text: string): Promise<boolean>\n stop(): Promise<void>\n reset(): void\n}\n\n/**\n * Transport-injected live-turn state machine for chat surfaces: send →\n * pending → streaming (coalesced text/reasoning deltas, tool calls, node\n * progress) → done / error / interrupted. Persists nothing — the app owns\n * sessions, messages and the branching endpoints; after a terminal state\n * the app refetches and calls `reset()`.\n */\nexport function useChatStream(options: UseChatStreamOptions): UseChatStreamResult {\n const { transport, flushMs = 33, timeoutMs = 90_000 } = options\n const [state, setState] = useState<LiveTurnState>(initialLiveTurnState)\n const stateRef = useRef(state)\n const dispatch = useCallback((action: LiveTurnAction) => {\n const next = liveTurnReducer(stateRef.current, action)\n stateRef.current = next\n setState(next)\n }, [])\n\n const transportRef = useRef(transport)\n transportRef.current = transport\n\n const textBufferRef = useRef('')\n const reasoningBufferRef = useRef('')\n const flushTimerRef = useRef<number | null>(null)\n const timeoutTimerRef = useRef<number | null>(null)\n const terminalRef = useRef(false)\n\n const cancelFlush = useCallback(() => {\n if (flushTimerRef.current !== null) {\n window.clearTimeout(flushTimerRef.current)\n flushTimerRef.current = null\n }\n }, [])\n\n const flush = useCallback(() => {\n cancelFlush()\n const text = textBufferRef.current\n const reasoning = reasoningBufferRef.current\n textBufferRef.current = ''\n reasoningBufferRef.current = ''\n if (text !== '') {\n dispatch({ type: 'event', event: { event: 'delta', text, kind: 'text' } })\n }\n if (reasoning !== '') {\n dispatch({ type: 'event', event: { event: 'delta', text: reasoning, kind: 'reasoning' } })\n }\n }, [cancelFlush, dispatch])\n\n const scheduleFlush = useCallback(() => {\n if (flushTimerRef.current !== null) {\n return\n }\n flushTimerRef.current = window.setTimeout(() => {\n flushTimerRef.current = null\n flush()\n }, flushMs)\n }, [flush, flushMs])\n\n const clearTimeoutTimer = useCallback(() => {\n if (timeoutTimerRef.current !== null) {\n window.clearTimeout(timeoutTimerRef.current)\n timeoutTimerRef.current = null\n }\n }, [])\n\n useEffect(() => {\n const unsubscribe = transportRef.current.subscribe({\n onEvent: (event) => {\n if (terminalRef.current) {\n return\n }\n if (event.event === 'delta') {\n if (event.kind === 'reasoning') {\n reasoningBufferRef.current += event.text\n } else {\n textBufferRef.current += event.text\n }\n scheduleFlush()\n return\n }\n flush()\n if (event.event === 'flow_finished' || event.event === 'flow_failed') {\n terminalRef.current = true\n clearTimeoutTimer()\n }\n dispatch({ type: 'event', event })\n },\n })\n return unsubscribe\n }, [scheduleFlush, flush, clearTimeoutTimer, dispatch])\n\n useEffect(\n () => () => {\n cancelFlush()\n clearTimeoutTimer()\n },\n [cancelFlush, clearTimeoutTimer],\n )\n\n const send = useCallback(\n async (text: string) => {\n const trimmed = text.trim()\n const active = stateRef.current.status === 'pending' || stateRef.current.status === 'streaming'\n if (trimmed === '' || active) {\n return false\n }\n terminalRef.current = false\n textBufferRef.current = ''\n reasoningBufferRef.current = ''\n cancelFlush()\n clearTimeoutTimer()\n dispatch({ type: 'send', at: Date.now() })\n timeoutTimerRef.current = window.setTimeout(() => {\n if (terminalRef.current) {\n return\n }\n terminalRef.current = true\n flush()\n dispatch({\n type: 'event',\n event: { event: 'flow_failed', code: 'timeout', message: 'Chat turn timed out', retryable: true },\n })\n }, timeoutMs)\n try {\n await transportRef.current.send({ text: trimmed })\n } catch (sendError) {\n if (terminalRef.current) {\n return true\n }\n terminalRef.current = true\n clearTimeoutTimer()\n flush()\n dispatch({\n type: 'event',\n event: {\n event: 'flow_failed',\n code: 'send_failed',\n message: sendError instanceof Error ? sendError.message : String(sendError),\n retryable: true,\n },\n })\n }\n return true\n },\n [cancelFlush, clearTimeoutTimer, dispatch, flush, timeoutMs],\n )\n\n const stop = useCallback(async () => {\n const current = stateRef.current\n if (current.status !== 'pending' && current.status !== 'streaming') {\n return\n }\n terminalRef.current = true\n clearTimeoutTimer()\n flush()\n dispatch({ type: 'stop', at: Date.now() })\n await transportRef.current.stop?.().catch(() => undefined)\n }, [clearTimeoutTimer, dispatch, flush])\n\n const reset = useCallback(() => {\n terminalRef.current = false\n cancelFlush()\n clearTimeoutTimer()\n dispatch({ type: 'reset' })\n }, [cancelFlush, clearTimeoutTimer, dispatch])\n\n return {\n ...state,\n live: state.status === 'idle' ? null : state,\n send,\n stop,\n reset,\n }\n}\n"]}
|