@neuronection/assistant-ui 0.50.1 → 0.52.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 +12 -0
- package/dist/chat-composer.d.ts +8 -0
- package/dist/chat-composer.js +1 -1
- 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/{chunk-NUD32RLL.js → chunk-YQQZMWHL.js} +5 -3
- package/dist/chunk-YQQZMWHL.js.map +1 -0
- package/dist/index.d.ts +26 -6
- package/dist/index.js +2 -2
- package/package.json +5 -5
- package/dist/chunk-6GNLY2EB.js.map +0 -1
- package/dist/chunk-NUD32RLL.js.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
1
1
|
# @neuronection/assistant-ui
|
|
2
2
|
|
|
3
|
+
## 0.52.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#76](https://github.com/neuronection/assistant-ui/pull/76) [`2911feb`](https://github.com/neuronection/assistant-ui/commit/2911feb99688cf5bb349411aae5482d8763edab1) Thanks [@constLiakos](https://github.com/constLiakos)! - ChatComposer: add the `canSubmit` override for attachment-only submits. The send button's default guard (`value.trim() !== ''`) blocks turns that carry only attachments — health-assistant's image-only "what's this?" chat turn is the driving case. Apps pass `canSubmit={draft.trim() !== '' || hasReadyAttachments}` (or `false` to force-disable); the Enter/form path is unchanged, the app's `onSubmit` stays the single submit guard. Consumers: health wires it in its chat composer (plan 13 H1); career/study only if they add attachment-only sends.
|
|
8
|
+
|
|
9
|
+
## 0.51.0
|
|
10
|
+
|
|
11
|
+
### Minor Changes
|
|
12
|
+
|
|
13
|
+
- [`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).
|
|
14
|
+
|
|
3
15
|
## 0.50.1
|
|
4
16
|
|
|
5
17
|
### Patch Changes
|
package/dist/chat-composer.d.ts
CHANGED
|
@@ -21,6 +21,14 @@ interface ChatComposerProps {
|
|
|
21
21
|
sending?: boolean;
|
|
22
22
|
onStop?: () => void;
|
|
23
23
|
disabled?: boolean;
|
|
24
|
+
/**
|
|
25
|
+
* Overrides the send button's blank-text guard. Apps that allow
|
|
26
|
+
* attachment-only submits (health's image-only "what's this?" turn) pass
|
|
27
|
+
* `true` while ready attachments exist; pass `false` to force-disable.
|
|
28
|
+
* Default: the button enables only when `value.trim() !== ''`. The
|
|
29
|
+
* Enter/form path is unaffected — the app's `onSubmit` stays the guard.
|
|
30
|
+
*/
|
|
31
|
+
canSubmit?: boolean;
|
|
24
32
|
placeholder?: string;
|
|
25
33
|
ariaLabel?: string;
|
|
26
34
|
/** Auto-grow cap before the textarea scrolls. Default 8. */
|
package/dist/chat-composer.js
CHANGED
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"]}
|
|
@@ -14,6 +14,7 @@ var ChatComposer = React.forwardRef(
|
|
|
14
14
|
sending = false,
|
|
15
15
|
onStop,
|
|
16
16
|
disabled = false,
|
|
17
|
+
canSubmit,
|
|
17
18
|
placeholder,
|
|
18
19
|
ariaLabel,
|
|
19
20
|
maxRows = 8,
|
|
@@ -119,6 +120,7 @@ var ChatComposer = React.forwardRef(
|
|
|
119
120
|
const SendIcon = icons?.send ?? ArrowUp;
|
|
120
121
|
const StopIcon = icons?.stop ?? Square;
|
|
121
122
|
const showStop = sending && onStop !== void 0;
|
|
123
|
+
const submitBlocked = disabled || sending || (canSubmit === void 0 ? value.trim() === "" : !canSubmit);
|
|
122
124
|
return /* @__PURE__ */ jsxs(
|
|
123
125
|
"form",
|
|
124
126
|
{
|
|
@@ -179,7 +181,7 @@ var ChatComposer = React.forwardRef(
|
|
|
179
181
|
type: "submit",
|
|
180
182
|
"aria-label": labels?.send ?? "Send message",
|
|
181
183
|
title: labels?.send ?? "Send message",
|
|
182
|
-
disabled:
|
|
184
|
+
disabled: submitBlocked,
|
|
183
185
|
className: "mb-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-[var(--as-primary)] text-[var(--as-primary-fg)] transition-opacity hover:opacity-90 disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]",
|
|
184
186
|
children: /* @__PURE__ */ jsx(SendIcon, { className: "size-4", "aria-hidden": true })
|
|
185
187
|
}
|
|
@@ -195,5 +197,5 @@ var ChatComposer = React.forwardRef(
|
|
|
195
197
|
);
|
|
196
198
|
|
|
197
199
|
export { ChatComposer };
|
|
198
|
-
//# sourceMappingURL=chunk-
|
|
199
|
-
//# sourceMappingURL=chunk-
|
|
200
|
+
//# sourceMappingURL=chunk-YQQZMWHL.js.map
|
|
201
|
+
//# sourceMappingURL=chunk-YQQZMWHL.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/components/chat-composer/ChatComposer.tsx"],"names":["ChatComposer"],"mappings":";;;;;AAwDA,SAAS,iBAAiB,KAAA,EAA0D;AAClF,EAAA,OAAO,KAAA,CAAM,WAAA,CAAY,WAAA,IAAe,KAAA,CAAM,OAAA,KAAY,GAAA;AAC5D;AAOO,IAAM,YAAA,GAAqB,KAAA,CAAA,UAAA;AAAA,EAChC,SAASA,aAAAA,CACP;AAAA,IACE,KAAA;AAAA,IACA,aAAA;AAAA,IACA,QAAA;AAAA,IACA,OAAA,GAAU,KAAA;AAAA,IACV,MAAA;AAAA,IACA,QAAA,GAAW,KAAA;AAAA,IACX,SAAA;AAAA,IACA,WAAA;AAAA,IACA,SAAA;AAAA,IACA,OAAA,GAAU,CAAA;AAAA,IACV,YAAA;AAAA,IACA,UAAA;AAAA,IACA,WAAA;AAAA,IACA,WAAA;AAAA,IACA,aAAA;AAAA,IACA,MAAA;AAAA,IACA,KAAA;AAAA,IACA,WAAA;AAAA,IACA;AAAA,KAEF,GAAA,EACA;AACA,IAAA,MAAM,mBAAA,GAA4B,aAAmC,IAAI,CAAA;AACzE,IAAA,MAAM,CAAC,QAAA,EAAU,WAAW,CAAA,GAAU,eAAS,KAAK,CAAA;AACpD,IAAA,MAAM,CAAC,SAAA,EAAW,YAAY,CAAA,GAAU,eAAS,KAAK,CAAA;AACtD,IAAA,MAAM,YAAA,GAAqB,aAAO,KAAK,CAAA;AACvC,IAAA,MAAM,SAAA,GAAkB,aAAO,CAAC,CAAA;AAEhC,IAAA,MAAM,WAAA,GAAc,CAAC,OAAA,KAAwC;AAC3D,MAAA,mBAAA,CAAoB,OAAA,GAAU,OAAA;AAC9B,MAAA,IAAI,OAAO,gBAAgB,UAAA,EAAY;AACrC,QAAA,WAAA,CAAY,OAAO,CAAA;AAAA,MACrB,CAAA,MAAA,IAAW,WAAA,IAAe,OAAO,WAAA,KAAgB,QAAA,EAAU;AACxD,QAAC,YAAmE,OAAA,GAAU,OAAA;AAAA,MACjF;AAAA,IACF,CAAA;AAEA,IAAM,sBAAgB,MAAM;AAC1B,MAAA,MAAM,UAAU,mBAAA,CAAoB,OAAA;AACpC,MAAA,IAAI,YAAY,IAAA,EAAM;AACpB,QAAA;AAAA,MACF;AACA,MAAA,MAAM,KAAA,GAAQ,MAAA,CAAO,gBAAA,CAAiB,OAAO,CAAA;AAC7C,MAAA,MAAM,UAAA,GAAa,MAAA,CAAO,UAAA,CAAW,KAAA,CAAM,UAAU,CAAA,IAAK,EAAA;AAC1D,MAAA,MAAM,eAAA,GAAA,CACH,MAAA,CAAO,UAAA,CAAW,KAAA,CAAM,UAAU,CAAA,IAAK,CAAA,KAAM,MAAA,CAAO,UAAA,CAAW,KAAA,CAAM,aAAa,CAAA,IAAK,CAAA,CAAA;AAI1F,MAAA,MAAM,MAAM,MAAA,CAAO,UAAA,CAAW,KAAA,CAAM,SAAS,KAAK,OAAA,GAAU,EAAA;AAC5D,MAAA,OAAA,CAAQ,MAAM,MAAA,GAAS,MAAA;AAOvB,MAAA,MAAM,IAAA,GACJ,OAAA,CAAQ,YAAA,GAAe,CAAA,GAAI,IAAA,CAAK,GAAA,CAAI,IAAA,CAAK,IAAA,CAAK,OAAA,CAAQ,YAAY,CAAA,GAAI,CAAA,EAAG,GAAG,CAAA,GAAI,CAAA;AAClF,MAAA,OAAA,CAAQ,MAAM,MAAA,GAAS,IAAA,GAAO,CAAA,GAAI,CAAA,EAAG,IAAI,CAAA,EAAA,CAAA,GAAO,MAAA;AAChD,MAAA,MAAM,WAAA,GAAc,OAAA,CAAQ,YAAA,GAAe,OAAA,CAAQ,YAAA,GAAe,CAAA;AAClE,MAAA,OAAA,CAAQ,KAAA,CAAM,SAAA,GAAY,WAAA,GAAc,MAAA,GAAS,QAAA;AACjD,MAAA,IAAI,CAAC,WAAA,IAAe,OAAA,CAAQ,SAAA,KAAc,CAAA,EAAG;AAC3C,QAAA,OAAA,CAAQ,SAAA,GAAY,CAAA;AAAA,MACtB;AAQA,MAAA,MAAM,IAAA,GAAO,OAAA,CAAQ,YAAA,GAAe,UAAA,GAAa,eAAA,GAAkB,CAAA;AACnE,MAAA,YAAA,CAAa,OAAA,GAAU,KAAA,KAAU,EAAA,GAAK,KAAA,GAAQ,aAAa,OAAA,IAAW,IAAA;AACtE,MAAA,YAAA,CAAa,aAAa,OAAO,CAAA;AAAA,IACnC,CAAA,EAAG,CAAC,KAAA,EAAO,OAAO,CAAC,CAAA;AAEnB,IAAA,MAAM,SAAS,MAAM;AACnB,MAAA,IAAI,YAAY,OAAA,EAAS;AACvB,QAAA;AAAA,MACF;AACA,MAAA,QAAA,EAAS;AAAA,IACX,CAAA;AAEA,IAAA,MAAM,SAAA,GAA6D,CAAC,KAAA,KAAU;AAC5E,MAAA,IAAI,KAAA,CAAM,GAAA,KAAQ,OAAA,IAAW,KAAA,CAAM,QAAA,EAAU;AAC3C,QAAA;AAAA,MACF;AACA,MAAA,IAAI,gBAAA,CAAiB,KAAK,CAAA,EAAG;AAC3B,QAAA;AAAA,MACF;AACA,MAAA,KAAA,CAAM,cAAA,EAAe;AACrB,MAAA,MAAA,EAAO;AAAA,IACT,CAAA;AAEA,IAAA,MAAM,SAAA,GAAY,CAAC,QAAA,KAA0C;AAC3D,MAAA,MAAM,QAAQ,QAAA,GAAW,KAAA,CAAM,IAAA,CAAK,QAAQ,IAAI,EAAC;AACjD,MAAA,IAAI,KAAA,CAAM,SAAS,CAAA,EAAG;AACpB,QAAA,aAAA,GAAgB,KAAK,CAAA;AAAA,MACvB;AAAA,IACF,CAAA;AAEA,IAAA,MAAM,mBAAA,GAAsB;AAAA,MAC1B,WAAA,EAAa,CAAC,KAAA,KAA2B;AACvC,QAAA,IAAI,aAAA,KAAkB,UAAa,CAAC,KAAA,CAAM,aAAa,KAAA,CAAM,QAAA,CAAS,OAAO,CAAA,EAAG;AAC9E,UAAA;AAAA,QACF;AACA,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,SAAA,CAAU,OAAA,IAAW,CAAA;AACrB,QAAA,WAAA,CAAY,IAAI,CAAA;AAAA,MAClB,CAAA;AAAA,MACA,UAAA,EAAY,CAAC,KAAA,KAA2B;AACtC,QAAA,IAAI,aAAA,KAAkB,UAAa,CAAC,KAAA,CAAM,aAAa,KAAA,CAAM,QAAA,CAAS,OAAO,CAAA,EAAG;AAC9E,UAAA;AAAA,QACF;AACA,QAAA,KAAA,CAAM,cAAA,EAAe;AAAA,MACvB,CAAA;AAAA,MACA,aAAa,MAAM;AACjB,QAAA,IAAI,SAAA,CAAU,UAAU,CAAA,EAAG;AACzB,UAAA,SAAA,CAAU,OAAA,IAAW,CAAA;AACrB,UAAA,IAAI,SAAA,CAAU,YAAY,CAAA,EAAG;AAC3B,YAAA,WAAA,CAAY,KAAK,CAAA;AAAA,UACnB;AAAA,QACF;AAAA,MACF,CAAA;AAAA,MACA,MAAA,EAAQ,CAAC,KAAA,KAA2B;AAClC,QAAA,IAAI,kBAAkB,MAAA,EAAW;AAC/B,UAAA;AAAA,QACF;AACA,QAAA,KAAA,CAAM,cAAA,EAAe;AACrB,QAAA,SAAA,CAAU,OAAA,GAAU,CAAA;AACpB,QAAA,WAAA,CAAY,KAAK,CAAA;AACjB,QAAA,SAAA,CAAU,KAAA,CAAM,aAAa,KAAK,CAAA;AAAA,MACpC;AAAA,KACF;AAEA,IAAA,MAAM,QAAA,GAAW,OAAO,IAAA,IAAQ,OAAA;AAChC,IAAA,MAAM,QAAA,GAAW,OAAO,IAAA,IAAQ,MAAA;AAChC,IAAA,MAAM,QAAA,GAAW,WAAW,MAAA,KAAW,MAAA;AACvC,IAAA,MAAM,aAAA,GACJ,YAAY,OAAA,KAAY,SAAA,KAAc,SAAY,KAAA,CAAM,IAAA,EAAK,KAAM,EAAA,GAAK,CAAC,SAAA,CAAA;AAE3E,IAAA,uBACE,IAAA;AAAA,MAAC,MAAA;AAAA,MAAA;AAAA,QACC,GAAA;AAAA,QACA,SAAA,EAAQ,eAAA;AAAA,QACR,cAAA,EAAc,OAAA;AAAA,QACd,QAAA,EAAU,CAAC,KAAA,KAAU;AACnB,UAAA,KAAA,CAAM,cAAA,EAAe;AACrB,UAAA,MAAA,EAAO;AAAA,QACT,CAAA;AAAA,QACA,SAAA,EAAW,EAAA,CAAG,4BAAA,EAA8B,SAAS,CAAA;AAAA,QACpD,GAAG,mBAAA;AAAA,QAEH,QAAA,EAAA;AAAA,UAAA,WAAA,mBAAc,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wBAAA,EAA0B,uBAAY,CAAA,GAAS,IAAA;AAAA,UAC5E,8BAAc,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,wBAAA,EAA0B,uBAAY,CAAA,GAAS,IAAA;AAAA,0BAC7E,IAAA;AAAA,YAAC,KAAA;AAAA,YAAA;AAAA,cACC,SAAA,EAAQ,mBAAA;AAAA,cACR,kBAAgB,SAAA,IAAa,MAAA;AAAA,cAC7B,iBAAe,QAAA,IAAY,MAAA;AAAA,cAC3B,SAAA,EAAW,EAAA;AAAA,gBACT,oLAAA;AAAA,gBACA,QAAA,IAAY,mEAAA;AAAA,gBACZ,QAAA,IAAY;AAAA,eACd;AAAA,cAEC,QAAA,EAAA;AAAA,gBAAA,YAAA,mBAAe,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2CAAA,EAA6C,wBAAa,CAAA,GAAS,IAAA;AAAA,gCAClG,GAAA;AAAA,kBAAC,UAAA;AAAA,kBAAA;AAAA,oBACC,GAAA,EAAK,WAAA;AAAA,oBACL,KAAA;AAAA,oBACA,IAAA,EAAM,CAAA;AAAA,oBACN,QAAA;AAAA,oBACA,WAAA,EAAa,WAAA,IAAe,MAAA,EAAQ,WAAA,IAAe,sBAAA;AAAA,oBACnD,YAAA,EAAY,SAAA,IAAa,MAAA,EAAQ,SAAA,IAAa,SAAA;AAAA,oBAC9C,UAAU,CAAC,KAAA,KAAU,aAAA,CAAc,KAAA,CAAM,OAAO,KAAK,CAAA;AAAA,oBACrD,SAAA;AAAA,oBACA,SAAS,CAAC,KAAA,KAAU,SAAA,CAAU,KAAA,CAAM,cAAc,KAAK,CAAA;AAAA,oBACvD,SAAA,EAAU;AAAA;AAAA,iBACZ;AAAA,gBACC,6BAAa,GAAA,CAAC,KAAA,EAAA,EAAI,SAAA,EAAU,2CAAA,EAA6C,sBAAW,CAAA,GAAS,IAAA;AAAA,gBAC7F,QAAA,mBACC,GAAA;AAAA,kBAAC,QAAA;AAAA,kBAAA;AAAA,oBACC,IAAA,EAAK,QAAA;AAAA,oBACL,YAAA,EAAY,QAAQ,IAAA,IAAQ,iBAAA;AAAA,oBAC5B,KAAA,EAAO,QAAQ,IAAA,IAAQ,iBAAA;AAAA,oBACvB,OAAA,EAAS,MAAA;AAAA,oBACT,SAAA,EAAU,8SAAA;AAAA,oBAEV,QAAA,kBAAA,GAAA,CAAC,QAAA,EAAA,EAAS,SAAA,EAAU,UAAA,EAAW,eAAW,IAAA,EAAC;AAAA;AAAA,iBAC7C,mBAEA,GAAA;AAAA,kBAAC,QAAA;AAAA,kBAAA;AAAA,oBACC,IAAA,EAAK,QAAA;AAAA,oBACL,YAAA,EAAY,QAAQ,IAAA,IAAQ,cAAA;AAAA,oBAC5B,KAAA,EAAO,QAAQ,IAAA,IAAQ,cAAA;AAAA,oBACvB,QAAA,EAAU,aAAA;AAAA,oBACV,SAAA,EAAU,kTAAA;AAAA,oBAEV,QAAA,kBAAA,GAAA,CAAC,QAAA,EAAA,EAAS,SAAA,EAAU,QAAA,EAAS,eAAW,IAAA,EAAC;AAAA;AAAA;AAC3C;AAAA;AAAA,WAEJ;AAAA,UACC,QAAA,IAAY,aAAA,mBACX,GAAA,CAAC,GAAA,EAAA,EAAE,SAAA,EAAU,mCAAA,EAAoC,IAAA,EAAK,QAAA,EACnD,QAAA,EAAA,MAAA,EAAQ,SAAA,IAAa,sBAAA,EACxB,CAAA,GACE;AAAA;AAAA;AAAA,KACN;AAAA,EAEJ;AACF","file":"chunk-YQQZMWHL.js","sourcesContent":["import * as React from 'react'\nimport { ArrowUp, Square } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\n\nimport { cn } from '../../lib/utils'\n\nexport interface ChatComposerLabels {\n send: string\n stop: string\n placeholder: string\n ariaLabel: string\n dropFiles: string\n}\n\nexport interface ChatComposerIcons {\n send: LucideIcon\n stop: LucideIcon\n}\n\nexport interface ChatComposerProps {\n value: string\n onValueChange: (value: string) => void\n /** Enter / Send — IME-safe (composition and keyCode 229 are ignored). */\n onSubmit: () => void\n /** A turn is in flight — renders the stop control when `onStop` is provided. */\n sending?: boolean\n onStop?: () => void\n disabled?: boolean\n /**\n * Overrides the send button's blank-text guard. Apps that allow\n * attachment-only submits (health's image-only \"what's this?\" turn) pass\n * `true` while ready attachments exist; pass `false` to force-disable.\n * Default: the button enables only when `value.trim() !== ''`. The\n * Enter/form path is unaffected — the app's `onSubmit` stays the guard.\n */\n canSubmit?: boolean\n placeholder?: string\n ariaLabel?: string\n /** Auto-grow cap before the textarea scrolls. Default 8. */\n maxRows?: number\n /** Leading slot inside the input row (attach menu, equation, draw…). */\n toolbarStart?: React.ReactNode\n /** Trailing slot inside the input row (dictation, tools…). */\n toolbarEnd?: React.ReactNode\n /** Attachment rail above the input (usually `FileQueue` / chips). */\n attachments?: React.ReactNode\n /** Suggestion chips / context strip above everything. */\n suggestions?: React.ReactNode\n /** Drag-drop + paste wiring for file attachments. */\n onAttachFiles?: (files: File[]) => void\n labels?: Partial<ChatComposerLabels>\n icons?: Partial<ChatComposerIcons>\n textareaRef?: React.Ref<HTMLTextAreaElement>\n className?: string\n}\n\nfunction isIMEComposition(event: React.KeyboardEvent<HTMLTextAreaElement>): boolean {\n return event.nativeEvent.isComposing || event.keyCode === 229\n}\n\n/**\n * The family chat input: auto-growing IME-safe textarea, send/stop\n * states, attachment/dictation/toolbar slots, drag-drop + paste file\n * wiring. Presentational + controlled; transports stay app-side.\n */\nexport const ChatComposer = React.forwardRef<HTMLFormElement, ChatComposerProps>(\n function ChatComposer(\n {\n value,\n onValueChange,\n onSubmit,\n sending = false,\n onStop,\n disabled = false,\n canSubmit,\n placeholder,\n ariaLabel,\n maxRows = 8,\n toolbarStart,\n toolbarEnd,\n attachments,\n suggestions,\n onAttachFiles,\n labels,\n icons,\n textareaRef,\n className,\n },\n ref,\n ) {\n const internalTextareaRef = React.useRef<HTMLTextAreaElement | null>(null)\n const [dragging, setDragging] = React.useState(false)\n const [multiline, setMultiline] = React.useState(false)\n const multilineRef = React.useRef(false)\n const dragDepth = React.useRef(0)\n\n const setTextarea = (element: HTMLTextAreaElement | null) => {\n internalTextareaRef.current = element\n if (typeof textareaRef === 'function') {\n textareaRef(element)\n } else if (textareaRef && typeof textareaRef === 'object') {\n ;(textareaRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = element\n }\n }\n\n React.useLayoutEffect(() => {\n const element = internalTextareaRef.current\n if (element === null) {\n return\n }\n const style = window.getComputedStyle(element)\n const lineHeight = Number.parseFloat(style.lineHeight) || 22\n const verticalPadding =\n (Number.parseFloat(style.paddingTop) || 0) + (Number.parseFloat(style.paddingBottom) || 0)\n // The JS cap must mirror the CSS box: a `max-h-*` class wins over\n // the maxRows fallback, otherwise overflowY and the real visible\n // cap disagree and lines clip silently.\n const cap = Number.parseFloat(style.maxHeight) || maxRows * 22\n element.style.height = 'auto'\n // scrollHeight rounds fractional line-heights down (22.75px at\n // text-sm/leading-relaxed), leaving the box ~1px short — the\n // caret then scrolls the hidden-overflow textarea and clips the\n // first line at the box top. The +1 absorbs the rounding; the\n // scrollTop reset guarantees the first line stays visible\n // whenever the whole content fits.\n const next =\n element.scrollHeight > 0 ? Math.min(Math.ceil(element.scrollHeight) + 1, cap) : 0\n element.style.height = next > 0 ? `${next}px` : 'auto'\n const overflowing = element.scrollHeight > element.clientHeight + 1\n element.style.overflowY = overflowing ? 'auto' : 'hidden'\n if (!overflowing && element.scrollTop !== 0) {\n element.scrollTop = 0\n }\n // Hysteresis: apps restyle this row off [data-multiline] (e.g.\n // footer-wrapping the toolbars under a full-width textarea), which\n // changes the textarea's width between the two states. A flag\n // re-measured at whichever width the previous decision produced\n // feeds back into itself and oscillates near the wrap threshold —\n // one keystroke wide, the next squeezed. Once multiline, stay\n // multiline until the draft clears.\n const grew = element.scrollHeight > lineHeight + verticalPadding + 1\n multilineRef.current = value === '' ? false : multilineRef.current || grew\n setMultiline(multilineRef.current)\n }, [value, maxRows])\n\n const submit = () => {\n if (disabled || sending) {\n return\n }\n onSubmit()\n }\n\n const onKeyDown: React.KeyboardEventHandler<HTMLTextAreaElement> = (event) => {\n if (event.key !== 'Enter' || event.shiftKey) {\n return\n }\n if (isIMEComposition(event)) {\n return\n }\n event.preventDefault()\n submit()\n }\n\n const emitFiles = (fileList: FileList | null | undefined) => {\n const files = fileList ? Array.from(fileList) : []\n if (files.length > 0) {\n onAttachFiles?.(files)\n }\n }\n\n const dragCounterHandlers = {\n onDragEnter: (event: React.DragEvent) => {\n if (onAttachFiles === undefined || !event.dataTransfer.types.includes('Files')) {\n return\n }\n event.preventDefault()\n dragDepth.current += 1\n setDragging(true)\n },\n onDragOver: (event: React.DragEvent) => {\n if (onAttachFiles === undefined || !event.dataTransfer.types.includes('Files')) {\n return\n }\n event.preventDefault()\n },\n onDragLeave: () => {\n if (dragDepth.current > 0) {\n dragDepth.current -= 1\n if (dragDepth.current === 0) {\n setDragging(false)\n }\n }\n },\n onDrop: (event: React.DragEvent) => {\n if (onAttachFiles === undefined) {\n return\n }\n event.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n emitFiles(event.dataTransfer.files)\n },\n }\n\n const SendIcon = icons?.send ?? ArrowUp\n const StopIcon = icons?.stop ?? Square\n const showStop = sending && onStop !== undefined\n const submitBlocked =\n disabled || sending || (canSubmit === undefined ? value.trim() === '' : !canSubmit)\n\n return (\n <form\n ref={ref}\n data-as=\"chat-composer\"\n data-sending={sending}\n onSubmit={(event) => {\n event.preventDefault()\n submit()\n }}\n className={cn('flex w-full flex-col gap-2', className)}\n {...dragCounterHandlers}\n >\n {suggestions ? <div className=\"flex flex-wrap gap-1.5\">{suggestions}</div> : null}\n {attachments ? <div className=\"flex flex-wrap gap-1.5\">{attachments}</div> : null}\n <div\n data-as=\"chat-composer-row\"\n data-multiline={multiline || undefined}\n data-dragging={dragging || undefined}\n className={cn(\n 'flex items-end gap-1.5 rounded-[var(--as-radius-lg)] border border-[var(--as-border)] bg-[var(--as-surface)] px-2 py-1.5 transition-colors focus-within:border-[var(--as-primary)]',\n dragging && 'border-dashed border-[var(--as-primary)] bg-[var(--as-primary)]/5',\n disabled && 'opacity-50',\n )}\n >\n {toolbarStart ? <div className=\"flex shrink-0 items-center gap-0.5 pb-0.5\">{toolbarStart}</div> : null}\n <textarea\n ref={setTextarea}\n value={value}\n rows={1}\n disabled={disabled}\n placeholder={placeholder ?? labels?.placeholder ?? 'Send a message…'}\n aria-label={ariaLabel ?? labels?.ariaLabel ?? 'Message'}\n onChange={(event) => onValueChange(event.target.value)}\n onKeyDown={onKeyDown}\n onPaste={(event) => emitFiles(event.clipboardData.files)}\n className=\"max-h-40 min-h-[1.5rem] w-full flex-1 resize-none bg-transparent px-1 py-1 text-sm leading-relaxed text-[var(--as-fg)] placeholder:text-[var(--as-muted-fg)] focus-visible:outline-none disabled:cursor-not-allowed\"\n />\n {toolbarEnd ? <div className=\"flex shrink-0 items-center gap-0.5 pb-0.5\">{toolbarEnd}</div> : null}\n {showStop ? (\n <button\n type=\"button\"\n aria-label={labels?.stop ?? 'Stop generating'}\n title={labels?.stop ?? 'Stop generating'}\n onClick={onStop}\n className=\"mb-0.5 flex size-7 shrink-0 items-center justify-center rounded-full border border-[var(--as-border)] bg-[var(--as-surface-raised)] text-[var(--as-fg)] transition-colors hover:bg-[var(--as-secondary)] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <StopIcon className=\"size-3.5\" aria-hidden />\n </button>\n ) : (\n <button\n type=\"submit\"\n aria-label={labels?.send ?? 'Send message'}\n title={labels?.send ?? 'Send message'}\n disabled={submitBlocked}\n className=\"mb-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-[var(--as-primary)] text-[var(--as-primary-fg)] transition-opacity hover:opacity-90 disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <SendIcon className=\"size-4\" aria-hidden />\n </button>\n )}\n </div>\n {dragging && onAttachFiles ? (\n <p className=\"text-xs text-[var(--as-muted-fg)]\" role=\"status\">\n {labels?.dropFiles ?? 'Drop files to attach'}\n </p>\n ) : null}\n </form>\n )\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;
|
|
@@ -2657,6 +2669,14 @@ interface ChatComposerProps {
|
|
|
2657
2669
|
sending?: boolean;
|
|
2658
2670
|
onStop?: () => void;
|
|
2659
2671
|
disabled?: boolean;
|
|
2672
|
+
/**
|
|
2673
|
+
* Overrides the send button's blank-text guard. Apps that allow
|
|
2674
|
+
* attachment-only submits (health's image-only "what's this?" turn) pass
|
|
2675
|
+
* `true` while ready attachments exist; pass `false` to force-disable.
|
|
2676
|
+
* Default: the button enables only when `value.trim() !== ''`. The
|
|
2677
|
+
* Enter/form path is unaffected — the app's `onSubmit` stays the guard.
|
|
2678
|
+
*/
|
|
2679
|
+
canSubmit?: boolean;
|
|
2660
2680
|
placeholder?: string;
|
|
2661
2681
|
ariaLabel?: string;
|
|
2662
2682
|
/** Auto-grow cap before the textarea scrolls. Default 8. */
|
package/dist/index.js
CHANGED
|
@@ -70,11 +70,11 @@ 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';
|
|
77
|
-
export { ChatComposer } from './chunk-
|
|
77
|
+
export { ChatComposer } from './chunk-YQQZMWHL.js';
|
|
78
78
|
export { rawTokens, semanticTokens } from './chunk-GDGKARRB.js';
|
|
79
79
|
export { AiActionsDropdown } from './chunk-HUIKW2PK.js';
|
|
80
80
|
export { AboutCard, AboutFooterLine, AboutLinkList, AboutNote, AboutPanel, FamilyBadge, SponsorCard, TechChips } from './chunk-PPW4GNM6.js';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@neuronection/assistant-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.52.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",
|
|
@@ -439,8 +439,8 @@
|
|
|
439
439
|
"@testing-library/react": "^16.3.2",
|
|
440
440
|
"@testing-library/user-event": "^14.6.1",
|
|
441
441
|
"@types/node": "^26.2.0",
|
|
442
|
-
"@types/react": "^19.
|
|
443
|
-
"@types/react-dom": "^19.
|
|
442
|
+
"@types/react": "^19.3.0",
|
|
443
|
+
"@types/react-dom": "^19.3.0",
|
|
444
444
|
"@vitejs/plugin-react": "^6.0.5",
|
|
445
445
|
"concurrently": "^9.1.2",
|
|
446
446
|
"eslint": "^10.8.1",
|
|
@@ -450,8 +450,8 @@
|
|
|
450
450
|
"jsdom": "^30.0.1",
|
|
451
451
|
"lucide-react": "^1.31.0",
|
|
452
452
|
"postcss": "^8.5.3",
|
|
453
|
-
"react": "^19.
|
|
454
|
-
"react-dom": "^19.
|
|
453
|
+
"react": "^19.3.0",
|
|
454
|
+
"react-dom": "^19.3.0",
|
|
455
455
|
"tailwindcss": "^4.3.3",
|
|
456
456
|
"tsup": "^8.4.0",
|
|
457
457
|
"typescript": "^5.9.3",
|
|
@@ -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"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/components/chat-composer/ChatComposer.tsx"],"names":["ChatComposer"],"mappings":"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","file":"chunk-NUD32RLL.js","sourcesContent":["import * as React from 'react'\nimport { ArrowUp, Square } from 'lucide-react'\nimport type { LucideIcon } from 'lucide-react'\n\nimport { cn } from '../../lib/utils'\n\nexport interface ChatComposerLabels {\n send: string\n stop: string\n placeholder: string\n ariaLabel: string\n dropFiles: string\n}\n\nexport interface ChatComposerIcons {\n send: LucideIcon\n stop: LucideIcon\n}\n\nexport interface ChatComposerProps {\n value: string\n onValueChange: (value: string) => void\n /** Enter / Send — IME-safe (composition and keyCode 229 are ignored). */\n onSubmit: () => void\n /** A turn is in flight — renders the stop control when `onStop` is provided. */\n sending?: boolean\n onStop?: () => void\n disabled?: boolean\n placeholder?: string\n ariaLabel?: string\n /** Auto-grow cap before the textarea scrolls. Default 8. */\n maxRows?: number\n /** Leading slot inside the input row (attach menu, equation, draw…). */\n toolbarStart?: React.ReactNode\n /** Trailing slot inside the input row (dictation, tools…). */\n toolbarEnd?: React.ReactNode\n /** Attachment rail above the input (usually `FileQueue` / chips). */\n attachments?: React.ReactNode\n /** Suggestion chips / context strip above everything. */\n suggestions?: React.ReactNode\n /** Drag-drop + paste wiring for file attachments. */\n onAttachFiles?: (files: File[]) => void\n labels?: Partial<ChatComposerLabels>\n icons?: Partial<ChatComposerIcons>\n textareaRef?: React.Ref<HTMLTextAreaElement>\n className?: string\n}\n\nfunction isIMEComposition(event: React.KeyboardEvent<HTMLTextAreaElement>): boolean {\n return event.nativeEvent.isComposing || event.keyCode === 229\n}\n\n/**\n * The family chat input: auto-growing IME-safe textarea, send/stop\n * states, attachment/dictation/toolbar slots, drag-drop + paste file\n * wiring. Presentational + controlled; transports stay app-side.\n */\nexport const ChatComposer = React.forwardRef<HTMLFormElement, ChatComposerProps>(\n function ChatComposer(\n {\n value,\n onValueChange,\n onSubmit,\n sending = false,\n onStop,\n disabled = false,\n placeholder,\n ariaLabel,\n maxRows = 8,\n toolbarStart,\n toolbarEnd,\n attachments,\n suggestions,\n onAttachFiles,\n labels,\n icons,\n textareaRef,\n className,\n },\n ref,\n ) {\n const internalTextareaRef = React.useRef<HTMLTextAreaElement | null>(null)\n const [dragging, setDragging] = React.useState(false)\n const [multiline, setMultiline] = React.useState(false)\n const multilineRef = React.useRef(false)\n const dragDepth = React.useRef(0)\n\n const setTextarea = (element: HTMLTextAreaElement | null) => {\n internalTextareaRef.current = element\n if (typeof textareaRef === 'function') {\n textareaRef(element)\n } else if (textareaRef && typeof textareaRef === 'object') {\n ;(textareaRef as React.MutableRefObject<HTMLTextAreaElement | null>).current = element\n }\n }\n\n React.useLayoutEffect(() => {\n const element = internalTextareaRef.current\n if (element === null) {\n return\n }\n const style = window.getComputedStyle(element)\n const lineHeight = Number.parseFloat(style.lineHeight) || 22\n const verticalPadding =\n (Number.parseFloat(style.paddingTop) || 0) + (Number.parseFloat(style.paddingBottom) || 0)\n // The JS cap must mirror the CSS box: a `max-h-*` class wins over\n // the maxRows fallback, otherwise overflowY and the real visible\n // cap disagree and lines clip silently.\n const cap = Number.parseFloat(style.maxHeight) || maxRows * 22\n element.style.height = 'auto'\n // scrollHeight rounds fractional line-heights down (22.75px at\n // text-sm/leading-relaxed), leaving the box ~1px short — the\n // caret then scrolls the hidden-overflow textarea and clips the\n // first line at the box top. The +1 absorbs the rounding; the\n // scrollTop reset guarantees the first line stays visible\n // whenever the whole content fits.\n const next =\n element.scrollHeight > 0 ? Math.min(Math.ceil(element.scrollHeight) + 1, cap) : 0\n element.style.height = next > 0 ? `${next}px` : 'auto'\n const overflowing = element.scrollHeight > element.clientHeight + 1\n element.style.overflowY = overflowing ? 'auto' : 'hidden'\n if (!overflowing && element.scrollTop !== 0) {\n element.scrollTop = 0\n }\n // Hysteresis: apps restyle this row off [data-multiline] (e.g.\n // footer-wrapping the toolbars under a full-width textarea), which\n // changes the textarea's width between the two states. A flag\n // re-measured at whichever width the previous decision produced\n // feeds back into itself and oscillates near the wrap threshold —\n // one keystroke wide, the next squeezed. Once multiline, stay\n // multiline until the draft clears.\n const grew = element.scrollHeight > lineHeight + verticalPadding + 1\n multilineRef.current = value === '' ? false : multilineRef.current || grew\n setMultiline(multilineRef.current)\n }, [value, maxRows])\n\n const submit = () => {\n if (disabled || sending) {\n return\n }\n onSubmit()\n }\n\n const onKeyDown: React.KeyboardEventHandler<HTMLTextAreaElement> = (event) => {\n if (event.key !== 'Enter' || event.shiftKey) {\n return\n }\n if (isIMEComposition(event)) {\n return\n }\n event.preventDefault()\n submit()\n }\n\n const emitFiles = (fileList: FileList | null | undefined) => {\n const files = fileList ? Array.from(fileList) : []\n if (files.length > 0) {\n onAttachFiles?.(files)\n }\n }\n\n const dragCounterHandlers = {\n onDragEnter: (event: React.DragEvent) => {\n if (onAttachFiles === undefined || !event.dataTransfer.types.includes('Files')) {\n return\n }\n event.preventDefault()\n dragDepth.current += 1\n setDragging(true)\n },\n onDragOver: (event: React.DragEvent) => {\n if (onAttachFiles === undefined || !event.dataTransfer.types.includes('Files')) {\n return\n }\n event.preventDefault()\n },\n onDragLeave: () => {\n if (dragDepth.current > 0) {\n dragDepth.current -= 1\n if (dragDepth.current === 0) {\n setDragging(false)\n }\n }\n },\n onDrop: (event: React.DragEvent) => {\n if (onAttachFiles === undefined) {\n return\n }\n event.preventDefault()\n dragDepth.current = 0\n setDragging(false)\n emitFiles(event.dataTransfer.files)\n },\n }\n\n const SendIcon = icons?.send ?? ArrowUp\n const StopIcon = icons?.stop ?? Square\n const showStop = sending && onStop !== undefined\n\n return (\n <form\n ref={ref}\n data-as=\"chat-composer\"\n data-sending={sending}\n onSubmit={(event) => {\n event.preventDefault()\n submit()\n }}\n className={cn('flex w-full flex-col gap-2', className)}\n {...dragCounterHandlers}\n >\n {suggestions ? <div className=\"flex flex-wrap gap-1.5\">{suggestions}</div> : null}\n {attachments ? <div className=\"flex flex-wrap gap-1.5\">{attachments}</div> : null}\n <div\n data-as=\"chat-composer-row\"\n data-multiline={multiline || undefined}\n data-dragging={dragging || undefined}\n className={cn(\n 'flex items-end gap-1.5 rounded-[var(--as-radius-lg)] border border-[var(--as-border)] bg-[var(--as-surface)] px-2 py-1.5 transition-colors focus-within:border-[var(--as-primary)]',\n dragging && 'border-dashed border-[var(--as-primary)] bg-[var(--as-primary)]/5',\n disabled && 'opacity-50',\n )}\n >\n {toolbarStart ? <div className=\"flex shrink-0 items-center gap-0.5 pb-0.5\">{toolbarStart}</div> : null}\n <textarea\n ref={setTextarea}\n value={value}\n rows={1}\n disabled={disabled}\n placeholder={placeholder ?? labels?.placeholder ?? 'Send a message…'}\n aria-label={ariaLabel ?? labels?.ariaLabel ?? 'Message'}\n onChange={(event) => onValueChange(event.target.value)}\n onKeyDown={onKeyDown}\n onPaste={(event) => emitFiles(event.clipboardData.files)}\n className=\"max-h-40 min-h-[1.5rem] w-full flex-1 resize-none bg-transparent px-1 py-1 text-sm leading-relaxed text-[var(--as-fg)] placeholder:text-[var(--as-muted-fg)] focus-visible:outline-none disabled:cursor-not-allowed\"\n />\n {toolbarEnd ? <div className=\"flex shrink-0 items-center gap-0.5 pb-0.5\">{toolbarEnd}</div> : null}\n {showStop ? (\n <button\n type=\"button\"\n aria-label={labels?.stop ?? 'Stop generating'}\n title={labels?.stop ?? 'Stop generating'}\n onClick={onStop}\n className=\"mb-0.5 flex size-7 shrink-0 items-center justify-center rounded-full border border-[var(--as-border)] bg-[var(--as-surface-raised)] text-[var(--as-fg)] transition-colors hover:bg-[var(--as-secondary)] focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <StopIcon className=\"size-3.5\" aria-hidden />\n </button>\n ) : (\n <button\n type=\"submit\"\n aria-label={labels?.send ?? 'Send message'}\n title={labels?.send ?? 'Send message'}\n disabled={disabled || value.trim() === '' || sending}\n className=\"mb-0.5 flex size-7 shrink-0 items-center justify-center rounded-full bg-[var(--as-primary)] text-[var(--as-primary-fg)] transition-opacity hover:opacity-90 disabled:pointer-events-none disabled:opacity-30 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-[var(--as-focus-ring)]\"\n >\n <SendIcon className=\"size-4\" aria-hidden />\n </button>\n )}\n </div>\n {dragging && onAttachFiles ? (\n <p className=\"text-xs text-[var(--as-muted-fg)]\" role=\"status\">\n {labels?.dropFiles ?? 'Drop files to attach'}\n </p>\n ) : null}\n </form>\n )\n },\n)\n"]}
|