@stapel/chat-react 0.1.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 +1 -0
- package/MODULE.md +118 -0
- package/README.md +126 -0
- package/dist/api/chatApi.d.ts +49 -0
- package/dist/api/chatApi.d.ts.map +1 -0
- package/dist/api/chatApi.js +46 -0
- package/dist/api/chatApi.js.map +1 -0
- package/dist/api/extensions.d.ts +19 -0
- package/dist/api/extensions.d.ts.map +1 -0
- package/dist/api/extensions.js +19 -0
- package/dist/api/extensions.js.map +1 -0
- package/dist/api/generated/schema.d.ts +547 -0
- package/dist/api/generated/schema.d.ts.map +1 -0
- package/dist/api/generated/schema.js +2 -0
- package/dist/api/generated/schema.js.map +1 -0
- package/dist/api/types.d.ts +75 -0
- package/dist/api/types.d.ts.map +1 -0
- package/dist/api/types.js +2 -0
- package/dist/api/types.js.map +1 -0
- package/dist/default/ConversationListPanel.d.ts +22 -0
- package/dist/default/ConversationListPanel.d.ts.map +1 -0
- package/dist/default/ConversationListPanel.js +49 -0
- package/dist/default/ConversationListPanel.js.map +1 -0
- package/dist/default/ConversationThreadPanel.d.ts +21 -0
- package/dist/default/ConversationThreadPanel.d.ts.map +1 -0
- package/dist/default/ConversationThreadPanel.js +51 -0
- package/dist/default/ConversationThreadPanel.js.map +1 -0
- package/dist/default/ErrorAlert.d.ts +8 -0
- package/dist/default/ErrorAlert.d.ts.map +1 -0
- package/dist/default/ErrorAlert.js +26 -0
- package/dist/default/ErrorAlert.js.map +1 -0
- package/dist/default/StartChatButton.d.ts +17 -0
- package/dist/default/StartChatButton.d.ts.map +1 -0
- package/dist/default/StartChatButton.js +18 -0
- package/dist/default/StartChatButton.js.map +1 -0
- package/dist/default/index.d.ts +21 -0
- package/dist/default/index.d.ts.map +1 -0
- package/dist/default/index.js +18 -0
- package/dist/default/index.js.map +1 -0
- package/dist/flows/errors.d.ts +12 -0
- package/dist/flows/errors.d.ts.map +1 -0
- package/dist/flows/errors.js +13 -0
- package/dist/flows/errors.js.map +1 -0
- package/dist/flows/freshness.d.ts +69 -0
- package/dist/flows/freshness.d.ts.map +1 -0
- package/dist/flows/freshness.js +227 -0
- package/dist/flows/freshness.js.map +1 -0
- package/dist/flows/registry.d.ts +26 -0
- package/dist/flows/registry.d.ts.map +1 -0
- package/dist/flows/registry.js +24 -0
- package/dist/flows/registry.js.map +1 -0
- package/dist/headless/ChatProvider.d.ts +22 -0
- package/dist/headless/ChatProvider.d.ts.map +1 -0
- package/dist/headless/ChatProvider.js +18 -0
- package/dist/headless/ChatProvider.js.map +1 -0
- package/dist/headless/ConversationList.d.ts +51 -0
- package/dist/headless/ConversationList.d.ts.map +1 -0
- package/dist/headless/ConversationList.js +47 -0
- package/dist/headless/ConversationList.js.map +1 -0
- package/dist/headless/ConversationThread.d.ts +50 -0
- package/dist/headless/ConversationThread.d.ts.map +1 -0
- package/dist/headless/ConversationThread.js +64 -0
- package/dist/headless/ConversationThread.js.map +1 -0
- package/dist/headless/MessageComposer.d.ts +39 -0
- package/dist/headless/MessageComposer.d.ts.map +1 -0
- package/dist/headless/MessageComposer.js +45 -0
- package/dist/headless/MessageComposer.js.map +1 -0
- package/dist/headless/StartDirectChat.d.ts +40 -0
- package/dist/headless/StartDirectChat.d.ts.map +1 -0
- package/dist/headless/StartDirectChat.js +46 -0
- package/dist/headless/StartDirectChat.js.map +1 -0
- package/dist/i18n/errorsMap.d.ts +10 -0
- package/dist/i18n/errorsMap.d.ts.map +1 -0
- package/dist/i18n/errorsMap.js +20 -0
- package/dist/i18n/errorsMap.js.map +1 -0
- package/dist/i18n/es.d.ts +23 -0
- package/dist/i18n/es.d.ts.map +1 -0
- package/dist/i18n/es.js +75 -0
- package/dist/i18n/es.js.map +1 -0
- package/dist/i18n/generated/errors.es.gen.d.ts +16 -0
- package/dist/i18n/generated/errors.es.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.es.gen.js +58 -0
- package/dist/i18n/generated/errors.es.gen.js.map +1 -0
- package/dist/i18n/generated/errors.gen.d.ts +359 -0
- package/dist/i18n/generated/errors.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.gen.js +183 -0
- package/dist/i18n/generated/errors.gen.js.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts +16 -0
- package/dist/i18n/generated/errors.ru.gen.d.ts.map +1 -0
- package/dist/i18n/generated/errors.ru.gen.js +58 -0
- package/dist/i18n/generated/errors.ru.gen.js.map +1 -0
- package/dist/i18n/keys.d.ts +78 -0
- package/dist/i18n/keys.d.ts.map +1 -0
- package/dist/i18n/keys.js +130 -0
- package/dist/i18n/keys.js.map +1 -0
- package/dist/i18n/ru.d.ts +28 -0
- package/dist/i18n/ru.d.ts.map +1 -0
- package/dist/i18n/ru.js +80 -0
- package/dist/i18n/ru.js.map +1 -0
- package/dist/index.d.ts +61 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +55 -0
- package/dist/index.js.map +1 -0
- package/dist/model/context.d.ts +11 -0
- package/dist/model/context.d.ts.map +1 -0
- package/dist/model/context.js +16 -0
- package/dist/model/context.js.map +1 -0
- package/dist/model/limits.d.ts +12 -0
- package/dist/model/limits.d.ts.map +1 -0
- package/dist/model/limits.js +12 -0
- package/dist/model/limits.js.map +1 -0
- package/dist/model/mutations.d.ts +66 -0
- package/dist/model/mutations.d.ts.map +1 -0
- package/dist/model/mutations.js +156 -0
- package/dist/model/mutations.js.map +1 -0
- package/dist/model/queries.d.ts +33 -0
- package/dist/model/queries.d.ts.map +1 -0
- package/dist/model/queries.js +93 -0
- package/dist/model/queries.js.map +1 -0
- package/dist/model/queryKeys.d.ts +8 -0
- package/dist/model/queryKeys.d.ts.map +1 -0
- package/dist/model/queryKeys.js +26 -0
- package/dist/model/queryKeys.js.map +1 -0
- package/dist/model/readMarker.d.ts +25 -0
- package/dist/model/readMarker.d.ts.map +1 -0
- package/dist/model/readMarker.js +31 -0
- package/dist/model/readMarker.js.map +1 -0
- package/dist/model/runtime.d.ts +50 -0
- package/dist/model/runtime.d.ts.map +1 -0
- package/dist/model/runtime.js +27 -0
- package/dist/model/runtime.js.map +1 -0
- package/dist/model/threadWindow.d.ts +95 -0
- package/dist/model/threadWindow.d.ts.map +1 -0
- package/dist/model/threadWindow.js +100 -0
- package/dist/model/threadWindow.js.map +1 -0
- package/dist/nav/manifest.d.ts +23 -0
- package/dist/nav/manifest.d.ts.map +1 -0
- package/dist/nav/manifest.js +15 -0
- package/dist/nav/manifest.js.map +1 -0
- package/dist/realtime/chatSocket.d.ts +93 -0
- package/dist/realtime/chatSocket.d.ts.map +1 -0
- package/dist/realtime/chatSocket.js +199 -0
- package/dist/realtime/chatSocket.js.map +1 -0
- package/dist/realtime/frames.d.ts +123 -0
- package/dist/realtime/frames.d.ts.map +1 -0
- package/dist/realtime/frames.js +123 -0
- package/dist/realtime/frames.js.map +1 -0
- package/dist/realtime/streams.d.ts +49 -0
- package/dist/realtime/streams.d.ts.map +1 -0
- package/dist/realtime/streams.js +52 -0
- package/dist/realtime/streams.js.map +1 -0
- package/llms.txt +89 -0
- package/manifest.json +733 -0
- package/nav-manifest.json +25 -0
- package/package.json +114 -0
- package/src/analytics/generated/events.json +7 -0
- package/src/api/chatApi.ts +142 -0
- package/src/api/extensions.ts +18 -0
- package/src/api/generated/schema.ts +549 -0
- package/src/api/types.ts +87 -0
- package/src/default/ConversationListPanel.tsx +163 -0
- package/src/default/ConversationThreadPanel.tsx +245 -0
- package/src/default/ErrorAlert.tsx +43 -0
- package/src/default/StartChatButton.tsx +78 -0
- package/src/default/index.ts +20 -0
- package/src/flows/errors.ts +16 -0
- package/src/flows/freshness.ts +315 -0
- package/src/flows/registry.ts +36 -0
- package/src/headless/ChatProvider.tsx +23 -0
- package/src/headless/ConversationList.tsx +92 -0
- package/src/headless/ConversationThread.tsx +107 -0
- package/src/headless/MessageComposer.tsx +86 -0
- package/src/headless/StartDirectChat.tsx +75 -0
- package/src/i18n/errorsMap.ts +31 -0
- package/src/i18n/es.ts +95 -0
- package/src/i18n/generated/errors.es.gen.ts +64 -0
- package/src/i18n/generated/errors.gen.ts +214 -0
- package/src/i18n/generated/errors.json +408 -0
- package/src/i18n/generated/errors.ru.gen.ts +64 -0
- package/src/i18n/keys.ts +152 -0
- package/src/i18n/ru.ts +98 -0
- package/src/index.ts +189 -0
- package/src/model/context.tsx +25 -0
- package/src/model/limits.ts +11 -0
- package/src/model/mutations.ts +208 -0
- package/src/model/queries.ts +120 -0
- package/src/model/queryKeys.ts +32 -0
- package/src/model/readMarker.ts +32 -0
- package/src/model/runtime.ts +90 -0
- package/src/model/threadWindow.ts +173 -0
- package/src/nav/manifest.ts +36 -0
- package/src/realtime/chatSocket.ts +304 -0
- package/src/realtime/frames.ts +229 -0
- package/src/realtime/streams.ts +85 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* THE TRANSPORT SEAM. One hook, two transports, and no screen that knows
|
|
3
|
+
* which one is running.
|
|
4
|
+
*
|
|
5
|
+
* ── What changed since the spec ─────────────────────────────────────────────
|
|
6
|
+
*
|
|
7
|
+
* `tasks/darom-storefront-design.md` §3.6 ruled polling-by-seq for chat v1,
|
|
8
|
+
* and it was right about the fleet it surveyed: stapel-chat had the resumable
|
|
9
|
+
* consumer but `routing.py` exported nothing, so no host could mount it.
|
|
10
|
+
* stapel-chat 0.2.2 ships that mount (`ws/chat/<uuid:conversation_id>`) and
|
|
11
|
+
* the darom fleet runs it. So the pair carries BOTH transports and picks at
|
|
12
|
+
* runtime — a deployment without sockets (WSGI, no channel layer, a
|
|
13
|
+
* misconfigured proxy) is not a broken chat, it is a chat that refreshes on a
|
|
14
|
+
* timer.
|
|
15
|
+
*
|
|
16
|
+
* ── The shape, and why it is this shape ─────────────────────────────────────
|
|
17
|
+
*
|
|
18
|
+
* `useChatFreshness(streamKey, mapToQueryKeys, { fallbackRefetchInterval })`
|
|
19
|
+
* is deliberately the signature §3.6 specified and the realtime substrate
|
|
20
|
+
* spec (§7) reserves for `useSignalInvalidate`: a signal maps to query keys,
|
|
21
|
+
* the keys are refetched, and the fallback interval turns the same interface
|
|
22
|
+
* into polling. Both halves therefore end in the same place — a refetch of
|
|
23
|
+
* the thread query, whose query function advances the window BY SEQ
|
|
24
|
+
* (`model/queries.ts`). A socket frame does not carry its payload into the
|
|
25
|
+
* cache; it says "there is something after your tip", and the store goes and
|
|
26
|
+
* gets it. That is resync-by-refetch as a first-class construction, and it is
|
|
27
|
+
* why the tests below hold for both transports without a branch.
|
|
28
|
+
*
|
|
29
|
+
* ── The replacement criterion, stated up front ──────────────────────────────
|
|
30
|
+
*
|
|
31
|
+
* When `@stapel/realtime` phase 1 lands, THIS FILE is the migration: `createChatSocket`
|
|
32
|
+
* goes away, `useSignalInvalidate` takes its place, and the pair's tests must
|
|
33
|
+
* stay green with no edits. Nothing above this file imports `realtime/`, so
|
|
34
|
+
* the blast radius is checkable rather than promised. There is no
|
|
35
|
+
* "TODO: replace with sockets" comment anywhere in this package — there is a
|
|
36
|
+
* seam with one consumer and a written criterion.
|
|
37
|
+
*/
|
|
38
|
+
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
39
|
+
import { useQueryClient } from "@tanstack/react-query";
|
|
40
|
+
import { useChatRuntime } from "../model/context.js";
|
|
41
|
+
import { chatQueryKeys } from "../model/queryKeys.js";
|
|
42
|
+
import { threadLastSeq } from "../model/threadWindow.js";
|
|
43
|
+
import { createChatSocket } from "../realtime/chatSocket.js";
|
|
44
|
+
import { CHAT_WS_RESYNC } from "../realtime/frames.js";
|
|
45
|
+
import { chatSocketUrl } from "../realtime/streams.js";
|
|
46
|
+
/**
|
|
47
|
+
* The polling periods the storefront spec fixes (§3.6): an open thread 3 s,
|
|
48
|
+
* the inbox 15 s. They live here, beside the transport that uses them, rather
|
|
49
|
+
* than in the components — the numbers are a property of the seam.
|
|
50
|
+
*/
|
|
51
|
+
export const THREAD_INTERVAL_MS = 3_000;
|
|
52
|
+
export const CONVERSATION_LIST_INTERVAL_MS = 15_000;
|
|
53
|
+
/** Frames arriving in a burst (a replay) collapse into one refetch. */
|
|
54
|
+
const FLUSH_DELAY_MS = 40;
|
|
55
|
+
/** Backoff ceiling: interval × 2^4 (3s → 48s) before it stops growing. */
|
|
56
|
+
const MAX_BACKOFF_STEPS = 4;
|
|
57
|
+
const IDLE_STATUS = {
|
|
58
|
+
state: "closed",
|
|
59
|
+
refusal: undefined,
|
|
60
|
+
attempt: 0,
|
|
61
|
+
};
|
|
62
|
+
function documentVisible() {
|
|
63
|
+
return typeof document === "undefined" || document.visibilityState !== "hidden";
|
|
64
|
+
}
|
|
65
|
+
export function useChatFreshness(stream, mapToQueryKeys, options) {
|
|
66
|
+
const runtime = useChatRuntime();
|
|
67
|
+
const queryClient = useQueryClient();
|
|
68
|
+
const enabled = options?.enabled ?? true;
|
|
69
|
+
const socketEnabled = options?.socketEnabled ?? true;
|
|
70
|
+
const interval = options?.fallbackRefetchInterval ?? 0;
|
|
71
|
+
// Both are plain values, compared by value in the dependency lists below —
|
|
72
|
+
// no memo needed, and no memo to forget to update.
|
|
73
|
+
const socketUrl = chatSocketUrl(runtime.realtime.socketBase, stream);
|
|
74
|
+
const conversationId = stream.kind === "conversation" ? stream.conversationId : null;
|
|
75
|
+
// The mapping function is a call-site lambda; keeping it in a ref is what
|
|
76
|
+
// stops every render from tearing down the socket.
|
|
77
|
+
const mapRef = useRef(mapToQueryKeys);
|
|
78
|
+
useEffect(() => {
|
|
79
|
+
mapRef.current = mapToQueryKeys;
|
|
80
|
+
});
|
|
81
|
+
const failuresRef = useRef(0);
|
|
82
|
+
const [status, setStatus] = useState(IDLE_STATUS);
|
|
83
|
+
const [visible, setVisible] = useState(documentVisible);
|
|
84
|
+
/** Refetch everything the signals touch. Resolves `true` if any read failed. */
|
|
85
|
+
const refresh = useCallback(async (signals) => {
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
const keys = [];
|
|
88
|
+
for (const signal of signals) {
|
|
89
|
+
for (const key of mapRef.current(signal)) {
|
|
90
|
+
const id = JSON.stringify(key);
|
|
91
|
+
if (seen.has(id))
|
|
92
|
+
continue;
|
|
93
|
+
seen.add(id);
|
|
94
|
+
keys.push(key);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (keys.length === 0)
|
|
98
|
+
return false;
|
|
99
|
+
await Promise.all(keys.map((queryKey) => queryClient.refetchQueries({ queryKey, type: "active" })));
|
|
100
|
+
return keys.some((queryKey) => queryClient
|
|
101
|
+
.getQueryCache()
|
|
102
|
+
.findAll({ queryKey })
|
|
103
|
+
.some((query) => query.state.status === "error"));
|
|
104
|
+
}, [queryClient]);
|
|
105
|
+
const pollNow = useCallback(() => {
|
|
106
|
+
void refresh([{ kind: "tick" }]);
|
|
107
|
+
}, [refresh]);
|
|
108
|
+
// ── the socket half ────────────────────────────────────────────────────────
|
|
109
|
+
const socketFactory = runtime.realtime.webSocket;
|
|
110
|
+
const reconnect = runtime.realtime.reconnect;
|
|
111
|
+
useEffect(() => {
|
|
112
|
+
if (!enabled || !socketEnabled || socketUrl === null || conversationId === null) {
|
|
113
|
+
setStatus(IDLE_STATUS);
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
let buffered = [];
|
|
117
|
+
let flushHandle;
|
|
118
|
+
const flush = () => {
|
|
119
|
+
const signals = buffered;
|
|
120
|
+
buffered = [];
|
|
121
|
+
flushHandle = undefined;
|
|
122
|
+
void refresh(signals);
|
|
123
|
+
};
|
|
124
|
+
const push = (signal) => {
|
|
125
|
+
buffered.push(signal);
|
|
126
|
+
if (flushHandle === undefined)
|
|
127
|
+
flushHandle = setTimeout(flush, FLUSH_DELAY_MS);
|
|
128
|
+
};
|
|
129
|
+
const socket = createChatSocket({
|
|
130
|
+
url: socketUrl,
|
|
131
|
+
// Read at every connect: the store keeps advancing by REST while the
|
|
132
|
+
// socket is down, and resuming from a stale cursor is how a reconnect
|
|
133
|
+
// turns into a duplicate storm.
|
|
134
|
+
lastSeq: () => {
|
|
135
|
+
const window = queryClient.getQueryData(chatQueryKeys.thread(conversationId));
|
|
136
|
+
return window ? threadLastSeq(window) : 0;
|
|
137
|
+
},
|
|
138
|
+
onFrame: (frame) => {
|
|
139
|
+
if (frame.type === "message") {
|
|
140
|
+
push({ kind: "message", conversationId, seq: frame.seq });
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
if (frame.type === "error" && frame.code === CHAT_WS_RESYNC) {
|
|
144
|
+
// The gap is wider than the replay window. The journal is the
|
|
145
|
+
// truth; go and read it.
|
|
146
|
+
push({ kind: "resync", conversationId });
|
|
147
|
+
}
|
|
148
|
+
// `welcome`, `replay_done` and `pong` carry no news the store needs:
|
|
149
|
+
// every message they bracket arrives as its own frame.
|
|
150
|
+
},
|
|
151
|
+
onStatus: setStatus,
|
|
152
|
+
...(socketFactory !== undefined ? { webSocket: socketFactory } : {}),
|
|
153
|
+
...(reconnect !== undefined ? { reconnect } : {}),
|
|
154
|
+
});
|
|
155
|
+
return () => {
|
|
156
|
+
if (flushHandle !== undefined)
|
|
157
|
+
clearTimeout(flushHandle);
|
|
158
|
+
socket.close();
|
|
159
|
+
};
|
|
160
|
+
}, [
|
|
161
|
+
enabled,
|
|
162
|
+
socketEnabled,
|
|
163
|
+
socketUrl,
|
|
164
|
+
conversationId,
|
|
165
|
+
queryClient,
|
|
166
|
+
refresh,
|
|
167
|
+
socketFactory,
|
|
168
|
+
reconnect,
|
|
169
|
+
]);
|
|
170
|
+
// ── the polling half ───────────────────────────────────────────────────────
|
|
171
|
+
//
|
|
172
|
+
// Runs whenever the socket is NOT carrying the stream — including the
|
|
173
|
+
// inbox, which has no socket at all — and never while the tab is in the
|
|
174
|
+
// background (nobody is reading; the catch-up happens on the way back).
|
|
175
|
+
const polling = enabled && interval > 0 && visible && status.state !== "open";
|
|
176
|
+
useEffect(() => {
|
|
177
|
+
if (!polling)
|
|
178
|
+
return;
|
|
179
|
+
let cancelled = false;
|
|
180
|
+
let handle;
|
|
181
|
+
const arm = (delay) => {
|
|
182
|
+
handle = setTimeout(() => {
|
|
183
|
+
void refresh([{ kind: "tick" }]).then((failed) => {
|
|
184
|
+
if (cancelled)
|
|
185
|
+
return;
|
|
186
|
+
// Consecutive failures back off exponentially; one success resets
|
|
187
|
+
// it. A backend that is down must not be asked twenty times a
|
|
188
|
+
// minute by every open tab.
|
|
189
|
+
failuresRef.current = failed
|
|
190
|
+
? Math.min(failuresRef.current + 1, MAX_BACKOFF_STEPS)
|
|
191
|
+
: 0;
|
|
192
|
+
arm(interval * 2 ** failuresRef.current);
|
|
193
|
+
});
|
|
194
|
+
}, delay);
|
|
195
|
+
};
|
|
196
|
+
arm(interval * 2 ** failuresRef.current);
|
|
197
|
+
return () => {
|
|
198
|
+
cancelled = true;
|
|
199
|
+
if (handle !== undefined)
|
|
200
|
+
clearTimeout(handle);
|
|
201
|
+
};
|
|
202
|
+
}, [polling, interval, refresh]);
|
|
203
|
+
// ── visibility ─────────────────────────────────────────────────────────────
|
|
204
|
+
useEffect(() => {
|
|
205
|
+
if (typeof document === "undefined")
|
|
206
|
+
return;
|
|
207
|
+
const onVisibility = () => {
|
|
208
|
+
const nowVisible = document.visibilityState !== "hidden";
|
|
209
|
+
setVisible(nowVisible);
|
|
210
|
+
// Coming back is the moment the reader most wants the truth.
|
|
211
|
+
if (nowVisible)
|
|
212
|
+
void refresh([{ kind: "tick" }]);
|
|
213
|
+
};
|
|
214
|
+
document.addEventListener("visibilitychange", onVisibility);
|
|
215
|
+
return () => {
|
|
216
|
+
document.removeEventListener("visibilitychange", onVisibility);
|
|
217
|
+
};
|
|
218
|
+
}, [refresh]);
|
|
219
|
+
const transport = status.state === "open" ? "socket" : polling ? "polling" : "idle";
|
|
220
|
+
return useMemo(() => ({
|
|
221
|
+
transport,
|
|
222
|
+
connection: status.state,
|
|
223
|
+
refusal: status.refusal,
|
|
224
|
+
pollNow,
|
|
225
|
+
}), [transport, status.state, status.refusal, pollNow]);
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=freshness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"freshness.js","sourceRoot":"","sources":["../../src/flows/freshness.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-flow registry shim (slim wave §21/S3). stapel-chat annotates no
|
|
3
|
+
* `@flow_step`: its `docs/flows.json` is an empty array, so `gen:flows` skips
|
|
4
|
+
* emission for this pair (no `flows/generated/`). This hand-written shim
|
|
5
|
+
* preserves the pair's public flow-registry surface at its zero-flow shape.
|
|
6
|
+
*
|
|
7
|
+
* That is not an oversight upstream, and worth saying once: chat's
|
|
8
|
+
* multi-step-ness lives in the TRANSPORT (resume, replay, resync), not in a
|
|
9
|
+
* server-declared funnel of endpoints. `flows/freshness.ts` is where this
|
|
10
|
+
* pair's real state machine is.
|
|
11
|
+
*
|
|
12
|
+
* When the backend annotates flows, `pnpm gen:flows` emits
|
|
13
|
+
* `./generated/flows.gen.ts` again — replace these exports with re-exports
|
|
14
|
+
* from it (the shapes match by construction) and delete this file.
|
|
15
|
+
*/
|
|
16
|
+
export declare const CHAT_FLOWS: {};
|
|
17
|
+
/** Canonical flow ids present in flows.json (none yet — see above). */
|
|
18
|
+
export type ChatFlowId = keyof typeof CHAT_FLOWS;
|
|
19
|
+
export type ChatFlowSpec = (typeof CHAT_FLOWS)[ChatFlowId];
|
|
20
|
+
export interface FlowEndpoint {
|
|
21
|
+
readonly method: string;
|
|
22
|
+
readonly path: string;
|
|
23
|
+
}
|
|
24
|
+
/** All HTTP endpoints a flow touches, in step order (for the contract test / MSW). */
|
|
25
|
+
export declare function flowEndpoints(id: ChatFlowId): readonly FlowEndpoint[];
|
|
26
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/flows/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,IAAc,CAAC;AAEtC,uEAAuE;AACvE,MAAM,MAAM,UAAU,GAAG,MAAM,OAAO,UAAU,CAAC;AAEjD,MAAM,MAAM,YAAY,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,UAAU,CAAC,CAAC;AAE3D,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;CACvB;AAED,sFAAsF;AACtF,wBAAgB,aAAa,CAAC,EAAE,EAAE,UAAU,GAAG,SAAS,YAAY,EAAE,CAOrE"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Zero-flow registry shim (slim wave §21/S3). stapel-chat annotates no
|
|
3
|
+
* `@flow_step`: its `docs/flows.json` is an empty array, so `gen:flows` skips
|
|
4
|
+
* emission for this pair (no `flows/generated/`). This hand-written shim
|
|
5
|
+
* preserves the pair's public flow-registry surface at its zero-flow shape.
|
|
6
|
+
*
|
|
7
|
+
* That is not an oversight upstream, and worth saying once: chat's
|
|
8
|
+
* multi-step-ness lives in the TRANSPORT (resume, replay, resync), not in a
|
|
9
|
+
* server-declared funnel of endpoints. `flows/freshness.ts` is where this
|
|
10
|
+
* pair's real state machine is.
|
|
11
|
+
*
|
|
12
|
+
* When the backend annotates flows, `pnpm gen:flows` emits
|
|
13
|
+
* `./generated/flows.gen.ts` again — replace these exports with re-exports
|
|
14
|
+
* from it (the shapes match by construction) and delete this file.
|
|
15
|
+
*/
|
|
16
|
+
export const CHAT_FLOWS = {};
|
|
17
|
+
/** All HTTP endpoints a flow touches, in step order (for the contract test / MSW). */
|
|
18
|
+
export function flowEndpoints(id) {
|
|
19
|
+
// Same widened body as the generated registry's — valid for the zero-flow
|
|
20
|
+
// shape AND correct once flows exist.
|
|
21
|
+
const spec = CHAT_FLOWS[id];
|
|
22
|
+
return spec ? spec.steps.flatMap((s) => s.endpoints) : [];
|
|
23
|
+
}
|
|
24
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/flows/registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,EAAW,CAAC;AAYtC,sFAAsF;AACtF,MAAM,UAAU,aAAa,CAAC,EAAc;IAC1C,0EAA0E;IAC1E,sCAAsC;IACtC,MAAM,IAAI,GAAG,UAAU,CAAC,EAAE,CAEb,CAAC;IACd,OAAO,IAAI,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactElement, ReactNode } from "react";
|
|
2
|
+
import type { ChatRuntime } from "../model/runtime.js";
|
|
3
|
+
/**
|
|
4
|
+
* Provides the wired {@link ChatRuntime} to every chat hook and headless
|
|
5
|
+
* component below it. Bring your own visual shell — this component renders
|
|
6
|
+
* nothing of its own.
|
|
7
|
+
*
|
|
8
|
+
* ```tsx
|
|
9
|
+
* const runtime = createChatRuntime({
|
|
10
|
+
* baseUrl: "/chat/api/v1",
|
|
11
|
+
* // sockets are derived from baseUrl's origin; pass `{ socketUrl: null }`
|
|
12
|
+
* // on a WSGI deployment to go straight to polling.
|
|
13
|
+
* });
|
|
14
|
+
* // give runtime.client to core's <StapelConfigProvider config={{ client }}>
|
|
15
|
+
* <ChatProvider runtime={runtime}>{app}</ChatProvider>
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export declare const ChatProvider: (props: {
|
|
19
|
+
runtime: ChatRuntime;
|
|
20
|
+
children: ReactNode;
|
|
21
|
+
}) => ReactElement;
|
|
22
|
+
//# sourceMappingURL=ChatProvider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatProvider.d.ts","sourceRoot":"","sources":["../../src/headless/ChatProvider.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAErD,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAEvD;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,YAAY,EAAE,CAAC,KAAK,EAAE;IACjC,OAAO,EAAE,WAAW,CAAC;IACrB,QAAQ,EAAE,SAAS,CAAC;CACrB,KAAK,YAA6B,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { ModuleProvider } from "../model/context.js";
|
|
2
|
+
/**
|
|
3
|
+
* Provides the wired {@link ChatRuntime} to every chat hook and headless
|
|
4
|
+
* component below it. Bring your own visual shell — this component renders
|
|
5
|
+
* nothing of its own.
|
|
6
|
+
*
|
|
7
|
+
* ```tsx
|
|
8
|
+
* const runtime = createChatRuntime({
|
|
9
|
+
* baseUrl: "/chat/api/v1",
|
|
10
|
+
* // sockets are derived from baseUrl's origin; pass `{ socketUrl: null }`
|
|
11
|
+
* // on a WSGI deployment to go straight to polling.
|
|
12
|
+
* });
|
|
13
|
+
* // give runtime.client to core's <StapelConfigProvider config={{ client }}>
|
|
14
|
+
* <ChatProvider runtime={runtime}>{app}</ChatProvider>
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export const ChatProvider = ModuleProvider;
|
|
18
|
+
//# sourceMappingURL=ChatProvider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ChatProvider.js","sourceRoot":"","sources":["../../src/headless/ChatProvider.tsx"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAGrD;;;;;;;;;;;;;;GAcG;AACH,MAAM,CAAC,MAAM,YAAY,GAGJ,cAAc,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { LoadState } from "@stapel/core";
|
|
3
|
+
import type { Conversation } from "../api/types.js";
|
|
4
|
+
import type { ChatTransport } from "../flows/freshness.js";
|
|
5
|
+
/** Render-prop bag for {@link ConversationList}. */
|
|
6
|
+
export interface ConversationListBag {
|
|
7
|
+
/**
|
|
8
|
+
* The list, as a state a skin cannot flatten: `loading` / `ready` with the
|
|
9
|
+
* conversations loaded so far (most recent activity first) / `failed`.
|
|
10
|
+
* Render it with core's `matchList`.
|
|
11
|
+
*/
|
|
12
|
+
readonly state: LoadState<readonly Conversation[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Unread messages across the LOADED pages, as its own load state — a badge
|
|
15
|
+
* must not read "0" while the list is failing to load. Server-computed per
|
|
16
|
+
* conversation (`unread_count`), summed here; a host that pages deeper sees
|
|
17
|
+
* the number grow, which is honest for a badge over a paginated list.
|
|
18
|
+
*/
|
|
19
|
+
readonly unreadTotal: LoadState<number>;
|
|
20
|
+
readonly hasNextPage: boolean;
|
|
21
|
+
readonly isFetchingNextPage: boolean;
|
|
22
|
+
fetchNextPage(): void;
|
|
23
|
+
refetch(): void;
|
|
24
|
+
/** Which transport is keeping this list fresh (always polling — the module
|
|
25
|
+
* mounts no socket for the inbox; see `realtime/streams.ts`). */
|
|
26
|
+
readonly transport: ChatTransport;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Headless conversation list — a renderless load-more list over
|
|
30
|
+
* `GET /chat/api/v1/conversations`, kept fresh through the transport seam.
|
|
31
|
+
*
|
|
32
|
+
* ```tsx
|
|
33
|
+
* <ConversationList>
|
|
34
|
+
* {({ state }) =>
|
|
35
|
+
* matchList(state, {
|
|
36
|
+
* loading: () => <Spinner />,
|
|
37
|
+
* failed: (error) => <ErrorPanel error={error} />,
|
|
38
|
+
* empty: () => <NothingYet />,
|
|
39
|
+
* ready: (rows) => <List rows={rows} />,
|
|
40
|
+
* })
|
|
41
|
+
* }
|
|
42
|
+
* </ConversationList>
|
|
43
|
+
* ```
|
|
44
|
+
*/
|
|
45
|
+
export declare function ConversationList(props: {
|
|
46
|
+
limit?: number;
|
|
47
|
+
/** Poll period in ms; `0` turns the list's own freshness off entirely. */
|
|
48
|
+
refreshIntervalMs?: number;
|
|
49
|
+
children: (bag: ConversationListBag) => ReactNode;
|
|
50
|
+
}): ReactNode;
|
|
51
|
+
//# sourceMappingURL=ConversationList.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConversationList.d.ts","sourceRoot":"","sources":["../../src/headless/ConversationList.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAOpD,OAAO,KAAK,EAAc,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAGvE,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,YAAY,EAAE,CAAC,CAAC;IACnD;;;;;OAKG;IACH,QAAQ,CAAC,WAAW,EAAE,SAAS,CAAC,MAAM,CAAC,CAAC;IACxC,QAAQ,CAAC,WAAW,EAAE,OAAO,CAAC;IAC9B,QAAQ,CAAC,kBAAkB,EAAE,OAAO,CAAC;IACrC,aAAa,IAAI,IAAI,CAAC;IACtB,OAAO,IAAI,IAAI,CAAC;IAChB;qEACiE;IACjE,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;CACnC;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,gBAAgB,CAAC,KAAK,EAAE;IACtC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,0EAA0E;IAC1E,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,QAAQ,EAAE,CAAC,GAAG,EAAE,mBAAmB,KAAK,SAAS,CAAC;CACnD,GAAG,SAAS,CA+BZ"}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { useCallback } from "react";
|
|
2
|
+
import { loadStateFromQuery, mapLoad } from "@stapel/core";
|
|
3
|
+
import { useConversations } from "../model/queries.js";
|
|
4
|
+
import { chatQueryKeys } from "../model/queryKeys.js";
|
|
5
|
+
import { CONVERSATION_LIST_INTERVAL_MS, useChatFreshness, } from "../flows/freshness.js";
|
|
6
|
+
import { chatInboxStream } from "../realtime/streams.js";
|
|
7
|
+
/**
|
|
8
|
+
* Headless conversation list — a renderless load-more list over
|
|
9
|
+
* `GET /chat/api/v1/conversations`, kept fresh through the transport seam.
|
|
10
|
+
*
|
|
11
|
+
* ```tsx
|
|
12
|
+
* <ConversationList>
|
|
13
|
+
* {({ state }) =>
|
|
14
|
+
* matchList(state, {
|
|
15
|
+
* loading: () => <Spinner />,
|
|
16
|
+
* failed: (error) => <ErrorPanel error={error} />,
|
|
17
|
+
* empty: () => <NothingYet />,
|
|
18
|
+
* ready: (rows) => <List rows={rows} />,
|
|
19
|
+
* })
|
|
20
|
+
* }
|
|
21
|
+
* </ConversationList>
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export function ConversationList(props) {
|
|
25
|
+
const query = useConversations(props.limit);
|
|
26
|
+
const mapKeys = useCallback((_signal) => [chatQueryKeys.conversations()], []);
|
|
27
|
+
const freshness = useChatFreshness(chatInboxStream(), mapKeys, {
|
|
28
|
+
fallbackRefetchInterval: props.refreshIntervalMs ?? CONVERSATION_LIST_INTERVAL_MS,
|
|
29
|
+
});
|
|
30
|
+
// Pages are flattened INSIDE the ready arm — a failed or not-yet-run read
|
|
31
|
+
// never produces a list at all.
|
|
32
|
+
const state = mapLoad(loadStateFromQuery(query), (data) => data.pages.flatMap((page) => page.items));
|
|
33
|
+
return props.children({
|
|
34
|
+
state,
|
|
35
|
+
unreadTotal: mapLoad(state, (rows) => rows.reduce((total, row) => total + row.unread_count, 0)),
|
|
36
|
+
hasNextPage: query.hasNextPage,
|
|
37
|
+
isFetchingNextPage: query.isFetchingNextPage,
|
|
38
|
+
fetchNextPage: () => {
|
|
39
|
+
void query.fetchNextPage();
|
|
40
|
+
},
|
|
41
|
+
refetch: () => {
|
|
42
|
+
void query.refetch();
|
|
43
|
+
},
|
|
44
|
+
transport: freshness.transport,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
//# sourceMappingURL=ConversationList.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConversationList.js","sourceRoot":"","sources":["../../src/headless/ConversationList.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,MAAM,OAAO,CAAC;AAEpC,OAAO,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAG3D,OAAO,EAAE,gBAAgB,EAAE,MAAM,qBAAqB,CAAC;AACvD,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EACL,6BAA6B,EAC7B,gBAAgB,GACjB,MAAM,uBAAuB,CAAC;AAE/B,OAAO,EAAE,eAAe,EAAE,MAAM,wBAAwB,CAAC;AA0BzD;;;;;;;;;;;;;;;;GAgBG;AACH,MAAM,UAAU,gBAAgB,CAAC,KAKhC;IACC,MAAM,KAAK,GAAG,gBAAgB,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;IAC5C,MAAM,OAAO,GAAG,WAAW,CACzB,CAAC,OAAmB,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,aAAa,EAAE,CAAC,EACxD,EAAE,CACH,CAAC;IACF,MAAM,SAAS,GAAG,gBAAgB,CAAC,eAAe,EAAE,EAAE,OAAO,EAAE;QAC7D,uBAAuB,EAAE,KAAK,CAAC,iBAAiB,IAAI,6BAA6B;KAClF,CAAC,CAAC;IAEH,0EAA0E;IAC1E,gCAAgC;IAChC,MAAM,KAAK,GAAG,OAAO,CAAC,kBAAkB,CAAC,KAAK,CAAC,EAAE,CAAC,IAAI,EAAE,EAAE,CACxD,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,IAAI,CAAC,KAAK,CAAC,CACzC,CAAC;IAEF,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,KAAK;QACL,WAAW,EAAE,OAAO,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,EAAE,CACnC,IAAI,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE,CAAC,KAAK,GAAG,GAAG,CAAC,YAAY,EAAE,CAAC,CAAC,CACzD;QACD,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,kBAAkB,EAAE,KAAK,CAAC,kBAAkB;QAC5C,aAAa,EAAE,GAAG,EAAE;YAClB,KAAK,KAAK,CAAC,aAAa,EAAE,CAAC;QAC7B,CAAC;QACD,OAAO,EAAE,GAAG,EAAE;YACZ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;QACD,SAAS,EAAE,SAAS,CAAC,SAAS;KAC/B,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { LoadState } from "@stapel/core";
|
|
3
|
+
import type { ChatMessage } from "../api/types.js";
|
|
4
|
+
import type { ChatTransport } from "../flows/freshness.js";
|
|
5
|
+
import type { ChatConnectionState } from "../realtime/chatSocket.js";
|
|
6
|
+
/** Render-prop bag for {@link ConversationThread}. */
|
|
7
|
+
export interface ConversationThreadBag {
|
|
8
|
+
/**
|
|
9
|
+
* The thread, ascending by `seq`, as a state a skin cannot flatten. The
|
|
10
|
+
* messages are a contiguous run: a hole is never rendered, it is healed by
|
|
11
|
+
* a re-read (see `model/threadWindow.ts`).
|
|
12
|
+
*/
|
|
13
|
+
readonly state: LoadState<readonly ChatMessage[]>;
|
|
14
|
+
/** Older history exists before the first loaded message. */
|
|
15
|
+
readonly hasOlder: boolean;
|
|
16
|
+
readonly isLoadingOlder: boolean;
|
|
17
|
+
/** Page one window of older history onto the front. */
|
|
18
|
+
loadOlder(): void;
|
|
19
|
+
refetch(): void;
|
|
20
|
+
/** The tip — the highest `seq` loaded. 0 for an empty or unloaded thread. */
|
|
21
|
+
readonly lastSeq: number;
|
|
22
|
+
/** Which transport is carrying this thread right now. */
|
|
23
|
+
readonly transport: ChatTransport;
|
|
24
|
+
readonly connection: ChatConnectionState;
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Headless conversation thread: replay plus live tail, over whichever
|
|
28
|
+
* transport this deployment has.
|
|
29
|
+
*
|
|
30
|
+
* The socket subscription starts only once the window is loaded, and that is
|
|
31
|
+
* deliberate: `hello{last_seq}` with a cursor of 0 asks the server to replay
|
|
32
|
+
* the entire thread over the socket, which the store would then throw away
|
|
33
|
+
* and re-read by REST. Loading first makes the resume carry a real cursor.
|
|
34
|
+
*
|
|
35
|
+
* The read marker advances automatically to the tip while the thread is
|
|
36
|
+
* mounted (`autoMarkRead`, on by default): the person is looking at these
|
|
37
|
+
* messages. It only ever moves forward — `useMarkRead` drops a candidate at
|
|
38
|
+
* or below what was already reported, so a remount or a re-render costs
|
|
39
|
+
* nothing.
|
|
40
|
+
*/
|
|
41
|
+
export declare function ConversationThread(props: {
|
|
42
|
+
conversationId: string;
|
|
43
|
+
limit?: number;
|
|
44
|
+
/** Poll period in ms while the socket is not carrying this thread. */
|
|
45
|
+
refreshIntervalMs?: number;
|
|
46
|
+
/** Advance the read marker to the tip while mounted. Default `true`. */
|
|
47
|
+
autoMarkRead?: boolean;
|
|
48
|
+
children: (bag: ConversationThreadBag) => ReactNode;
|
|
49
|
+
}): ReactNode;
|
|
50
|
+
//# sourceMappingURL=ConversationThread.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConversationThread.d.ts","sourceRoot":"","sources":["../../src/headless/ConversationThread.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,cAAc,CAAC;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,iBAAiB,CAAC;AAMnD,OAAO,KAAK,EAAc,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACvE,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AAGrE,sDAAsD;AACtD,MAAM,WAAW,qBAAqB;IACpC;;;;OAIG;IACH,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAC,SAAS,WAAW,EAAE,CAAC,CAAC;IAClD,4DAA4D;IAC5D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC;IAC3B,QAAQ,CAAC,cAAc,EAAE,OAAO,CAAC;IACjC,uDAAuD;IACvD,SAAS,IAAI,IAAI,CAAC;IAClB,OAAO,IAAI,IAAI,CAAC;IAChB,6EAA6E;IAC7E,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,yDAAyD;IACzD,QAAQ,CAAC,SAAS,EAAE,aAAa,CAAC;IAClC,QAAQ,CAAC,UAAU,EAAE,mBAAmB,CAAC;CAC1C;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE;IACxC,cAAc,EAAE,MAAM,CAAC;IACvB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,sEAAsE;IACtE,iBAAiB,CAAC,EAAE,MAAM,CAAC;IAC3B,wEAAwE;IACxE,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,QAAQ,EAAE,CAAC,GAAG,EAAE,qBAAqB,KAAK,SAAS,CAAC;CACrD,GAAG,SAAS,CAgDZ"}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { useCallback, useEffect } from "react";
|
|
2
|
+
import { isLoadReady, loadStateFromQuery, mapLoad } from "@stapel/core";
|
|
3
|
+
import { useThread } from "../model/queries.js";
|
|
4
|
+
import { useLoadOlderMessages, useMarkRead } from "../model/mutations.js";
|
|
5
|
+
import { chatQueryKeys } from "../model/queryKeys.js";
|
|
6
|
+
import { threadLastSeq } from "../model/threadWindow.js";
|
|
7
|
+
import { THREAD_INTERVAL_MS, useChatFreshness } from "../flows/freshness.js";
|
|
8
|
+
import { chatConversationStream } from "../realtime/streams.js";
|
|
9
|
+
/**
|
|
10
|
+
* Headless conversation thread: replay plus live tail, over whichever
|
|
11
|
+
* transport this deployment has.
|
|
12
|
+
*
|
|
13
|
+
* The socket subscription starts only once the window is loaded, and that is
|
|
14
|
+
* deliberate: `hello{last_seq}` with a cursor of 0 asks the server to replay
|
|
15
|
+
* the entire thread over the socket, which the store would then throw away
|
|
16
|
+
* and re-read by REST. Loading first makes the resume carry a real cursor.
|
|
17
|
+
*
|
|
18
|
+
* The read marker advances automatically to the tip while the thread is
|
|
19
|
+
* mounted (`autoMarkRead`, on by default): the person is looking at these
|
|
20
|
+
* messages. It only ever moves forward — `useMarkRead` drops a candidate at
|
|
21
|
+
* or below what was already reported, so a remount or a re-render costs
|
|
22
|
+
* nothing.
|
|
23
|
+
*/
|
|
24
|
+
export function ConversationThread(props) {
|
|
25
|
+
const { conversationId } = props;
|
|
26
|
+
const query = useThread(conversationId, props.limit);
|
|
27
|
+
const older = useLoadOlderMessages(conversationId, props.limit);
|
|
28
|
+
const markRead = useMarkRead(conversationId);
|
|
29
|
+
const mapKeys = useCallback((_signal) => [
|
|
30
|
+
chatQueryKeys.thread(conversationId),
|
|
31
|
+
// A new message also moves this thread up the list and changes its
|
|
32
|
+
// unread badge — one signal, two reads.
|
|
33
|
+
chatQueryKeys.conversations(),
|
|
34
|
+
], [conversationId]);
|
|
35
|
+
const windowState = loadStateFromQuery(query);
|
|
36
|
+
const loaded = isLoadReady(windowState);
|
|
37
|
+
const freshness = useChatFreshness(chatConversationStream(conversationId), mapKeys, {
|
|
38
|
+
socketEnabled: loaded,
|
|
39
|
+
fallbackRefetchInterval: props.refreshIntervalMs ?? THREAD_INTERVAL_MS,
|
|
40
|
+
});
|
|
41
|
+
const lastSeq = isLoadReady(windowState) ? threadLastSeq(windowState.data) : 0;
|
|
42
|
+
const autoMarkRead = props.autoMarkRead ?? true;
|
|
43
|
+
const markReadMutate = markRead.mutate;
|
|
44
|
+
useEffect(() => {
|
|
45
|
+
if (!autoMarkRead || lastSeq === 0)
|
|
46
|
+
return;
|
|
47
|
+
markReadMutate(lastSeq);
|
|
48
|
+
}, [autoMarkRead, lastSeq, markReadMutate]);
|
|
49
|
+
return props.children({
|
|
50
|
+
state: mapLoad(windowState, (window) => window.messages),
|
|
51
|
+
hasOlder: isLoadReady(windowState) ? windowState.data.hasOlder : false,
|
|
52
|
+
isLoadingOlder: older.isPending,
|
|
53
|
+
loadOlder: () => {
|
|
54
|
+
older.mutate();
|
|
55
|
+
},
|
|
56
|
+
refetch: () => {
|
|
57
|
+
void query.refetch();
|
|
58
|
+
},
|
|
59
|
+
lastSeq,
|
|
60
|
+
transport: freshness.transport,
|
|
61
|
+
connection: freshness.connection,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=ConversationThread.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ConversationThread.js","sourceRoot":"","sources":["../../src/headless/ConversationThread.tsx"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAE/C,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AAGxE,OAAO,EAAE,SAAS,EAAE,MAAM,qBAAqB,CAAC;AAChD,OAAO,EAAE,oBAAoB,EAAE,WAAW,EAAE,MAAM,uBAAuB,CAAC;AAC1E,OAAO,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,0BAA0B,CAAC;AACzD,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;AAG7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,wBAAwB,CAAC;AAuBhE;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,kBAAkB,CAAC,KAQlC;IACC,MAAM,EAAE,cAAc,EAAE,GAAG,KAAK,CAAC;IACjC,MAAM,KAAK,GAAG,SAAS,CAAC,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IACrD,MAAM,KAAK,GAAG,oBAAoB,CAAC,cAAc,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC;IAChE,MAAM,QAAQ,GAAG,WAAW,CAAC,cAAc,CAAC,CAAC;IAE7C,MAAM,OAAO,GAAG,WAAW,CACzB,CAAC,OAAmB,EAAE,EAAE,CAAC;QACvB,aAAa,CAAC,MAAM,CAAC,cAAc,CAAC;QACpC,mEAAmE;QACnE,wCAAwC;QACxC,aAAa,CAAC,aAAa,EAAE;KAC9B,EACD,CAAC,cAAc,CAAC,CACjB,CAAC;IACF,MAAM,WAAW,GAAG,kBAAkB,CAAC,KAAK,CAAC,CAAC;IAC9C,MAAM,MAAM,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC;IACxC,MAAM,SAAS,GAAG,gBAAgB,CAChC,sBAAsB,CAAC,cAAc,CAAC,EACtC,OAAO,EACP;QACE,aAAa,EAAE,MAAM;QACrB,uBAAuB,EAAE,KAAK,CAAC,iBAAiB,IAAI,kBAAkB;KACvE,CACF,CAAC;IAEF,MAAM,OAAO,GAAG,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAC/E,MAAM,YAAY,GAAG,KAAK,CAAC,YAAY,IAAI,IAAI,CAAC;IAChD,MAAM,cAAc,GAAG,QAAQ,CAAC,MAAM,CAAC;IACvC,SAAS,CAAC,GAAG,EAAE;QACb,IAAI,CAAC,YAAY,IAAI,OAAO,KAAK,CAAC;YAAE,OAAO;QAC3C,cAAc,CAAC,OAAO,CAAC,CAAC;IAC1B,CAAC,EAAE,CAAC,YAAY,EAAE,OAAO,EAAE,cAAc,CAAC,CAAC,CAAC;IAE5C,OAAO,KAAK,CAAC,QAAQ,CAAC;QACpB,KAAK,EAAE,OAAO,CAAC,WAAW,EAAE,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,QAAQ,CAAC;QACxD,QAAQ,EAAE,WAAW,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,WAAW,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK;QACtE,cAAc,EAAE,KAAK,CAAC,SAAS;QAC/B,SAAS,EAAE,GAAG,EAAE;YACd,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,CAAC;QACD,OAAO,EAAE,GAAG,EAAE;YACZ,KAAK,KAAK,CAAC,OAAO,EAAE,CAAC;QACvB,CAAC;QACD,OAAO;QACP,SAAS,EAAE,SAAS,CAAC,SAAS;QAC9B,UAAU,EAAE,SAAS,CAAC,UAAU;KACjC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { ReactNode } from "react";
|
|
2
|
+
import type { ActionAvailability } from "@stapel/core";
|
|
3
|
+
/** Render-prop bag for {@link MessageComposer}. */
|
|
4
|
+
export interface MessageComposerBag {
|
|
5
|
+
readonly value: string;
|
|
6
|
+
setValue(next: string): void;
|
|
7
|
+
/**
|
|
8
|
+
* Whether the message can be sent, and — when it cannot — why, as an i18n
|
|
9
|
+
* key a skin renders as TEXT beside the control (`useActionGate`). There is
|
|
10
|
+
* no way to spell "disabled, reason unknown".
|
|
11
|
+
*/
|
|
12
|
+
readonly availability: ActionAvailability;
|
|
13
|
+
readonly isSending: boolean;
|
|
14
|
+
/** The thrown value from the last failed send, for the error dialect. */
|
|
15
|
+
readonly error: unknown;
|
|
16
|
+
/** Send the current value; clears it on success. No-op while blocked. */
|
|
17
|
+
send(): void;
|
|
18
|
+
readonly maxLength: number;
|
|
19
|
+
/**
|
|
20
|
+
* The length the SERVER counts. Python's `len()` over a `str` counts code
|
|
21
|
+
* points, and JavaScript's `String.length` counts UTF-16 code units — so a
|
|
22
|
+
* single emoji is 1 there and 2 here. Counting code points is what keeps
|
|
23
|
+
* the mirror from refusing a message the backend would have accepted.
|
|
24
|
+
*/
|
|
25
|
+
readonly length: number;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Headless composer over `POST /chat/api/v1/conversations/{id}/messages`.
|
|
29
|
+
* REST, always — the socket's `send` frame refuses with codes that carry no
|
|
30
|
+
* i18n key (`realtime/frames.ts`).
|
|
31
|
+
*/
|
|
32
|
+
export declare function MessageComposer(props: {
|
|
33
|
+
conversationId: string;
|
|
34
|
+
maxLength?: number;
|
|
35
|
+
/** Called with the persisted message after a successful send. */
|
|
36
|
+
onSent?: (seq: number) => void;
|
|
37
|
+
children: (bag: MessageComposerBag) => ReactNode;
|
|
38
|
+
}): ReactNode;
|
|
39
|
+
//# sourceMappingURL=MessageComposer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"MessageComposer.d.ts","sourceRoot":"","sources":["../../src/headless/MessageComposer.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,OAAO,CAAC;AAEvC,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAKvD,mDAAmD;AACnD,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B;;;;OAIG;IACH,QAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC;IAC1C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;IAC5B,yEAAyE;IACzE,QAAQ,CAAC,KAAK,EAAE,OAAO,CAAC;IACxB,yEAAyE;IACzE,IAAI,IAAI,IAAI,CAAC;IACb,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;IAC3B;;;;;OAKG;IACH,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED;;;;GAIG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE;IACrC,cAAc,EAAE,MAAM,CAAC;IACvB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iEAAiE;IACjE,MAAM,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAC;IAC/B,QAAQ,EAAE,CAAC,GAAG,EAAE,kBAAkB,KAAK,SAAS,CAAC;CAClD,GAAG,SAAS,CAyCZ"}
|