@jini-ai/chat 0.3.7 → 0.3.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/react/features/chat-pane/agent-tools.d.ts +1 -1
- package/dist/react/features/chat-pane/agent-tools.js +3 -3
- package/dist/react/features/chat-pane/agent-tools.js.map +1 -1
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js +1 -1
- package/dist/react/features/chat-pane/components/AgentRuntimePicker.js.map +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.d.ts +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.d.ts.map +1 -1
- package/dist/react/features/chat-pane/components/ChatPane.js +2 -1
- package/dist/react/features/chat-pane/components/ChatPane.js.map +1 -1
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts +4 -2
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.d.ts.map +1 -1
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js +1 -0
- package/dist/react/features/chat-pane/hooks/useChatPane.hooks.js.map +1 -1
- package/dist/react/features/chat-pane/types.d.ts +14 -2
- package/dist/react/features/chat-pane/types.d.ts.map +1 -1
- package/dist/react/hooks/composer-draft-cache.d.ts +104 -41
- package/dist/react/hooks/composer-draft-cache.d.ts.map +1 -1
- package/dist/react/hooks/composer-draft-cache.js +369 -51
- package/dist/react/hooks/composer-draft-cache.js.map +1 -1
- package/dist/react/hooks/useComposer.d.ts +17 -0
- package/dist/react/hooks/useComposer.d.ts.map +1 -1
- package/dist/react/hooks/useComposer.js +93 -12
- package/dist/react/hooks/useComposer.js.map +1 -1
- package/dist/react/hooks/useConversation.d.ts.map +1 -1
- package/dist/react/hooks/useConversation.js +55 -0
- package/dist/react/hooks/useConversation.js.map +1 -1
- package/package.json +2 -2
|
@@ -1,89 +1,407 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* @module composer-draft-cache
|
|
3
3
|
*
|
|
4
|
-
* Per-conversation composer draft cache, keyed by conversation id. Exists so
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
* `conversationId` it is given
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
4
|
+
* Per-conversation composer draft cache, keyed by conversation id. Exists so whatever the operator
|
|
5
|
+
* was typing comes back — across a `ChatPane` remount (a host re-keying the pane on a conversation
|
|
6
|
+
* switch, so a user-initiated switch re-seeds the transcript from `initialMessages` — a documented
|
|
7
|
+
* pattern for this component: the pane owns its own transcript and takes no `conversationId`-change
|
|
8
|
+
* effect of its own), across a navigation away and back, and across a full page reload or a server
|
|
9
|
+
* restart that takes the page down with it. `useComposer.ts` reads/writes this on every
|
|
10
|
+
* `conversationId` it is given, with zero host wiring required.
|
|
11
|
+
*
|
|
12
|
+
* Two tiers, same key:
|
|
13
|
+
* - a module-level `Map`, which answers instantly and is the only tier that works when storage is
|
|
14
|
+
* unavailable;
|
|
15
|
+
* - `localStorage` under {@link COMPOSER_DRAFT_STORAGE_PREFIX}, which is what actually survives a
|
|
16
|
+
* reload. Every access is wrapped: a private window, blocked site data, a quota rejection, or a
|
|
17
|
+
* non-browser runtime degrades this module to memory-only. It never throws at a caller and it
|
|
18
|
+
* never returns a wiped draft in place of a live one — a failed read falls back to the in-memory
|
|
19
|
+
* tier rather than reporting "no draft".
|
|
20
|
+
*
|
|
21
|
+
* Durability is deliberately NOT gated behind `ComposerDraftPersistence` (`useComposer`'s
|
|
22
|
+
* host-supplied port). That port is a single opaque host-owned slot with no conversation key, and
|
|
23
|
+
* grep across every known host finds nothing passing it — an opt-in seam nobody opted into would
|
|
24
|
+
* not have fixed the reported bug (an operator lost a half-written message to a site-server
|
|
25
|
+
* restart, 2026-09-18). The port still works and still takes precedence at mount for a host that
|
|
26
|
+
* wires one.
|
|
27
|
+
*
|
|
28
|
+
* Scope limits worth knowing: `localStorage` is per-browser and per-origin, so a draft never
|
|
29
|
+
* reaches the server, never follows the operator to another machine, and is visible to anything
|
|
30
|
+
* else running on that origin. Two tabs open on the same conversation last-write-wins. None of
|
|
31
|
+
* that is a regression — before this, the draft simply did not survive at all.
|
|
18
32
|
*
|
|
19
33
|
* @tradeoffs A module-level singleton (rather than a per-instance store a host constructs and
|
|
20
|
-
* passes down) was chosen so the fix works for every host with no new prop plumbing
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
*
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
*
|
|
27
|
-
*
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*/
|
|
35
|
-
/** Cap on distinct conversations tracked at once; see the module doc's `@tradeoffs`. */
|
|
34
|
+
* passes down) was chosen so the fix works for every host with no new prop plumbing. The risk a
|
|
35
|
+
* singleton usually carries — two unrelated call sites sharing state that should be isolated (see
|
|
36
|
+
* `create-daemon-attachment-uploader.ts`'s per-uploader `batchUsage` map, which exists for exactly
|
|
37
|
+
* that reason) — does not apply the same way here: the partition key (`conversationId`) is already
|
|
38
|
+
* globally unique per backend, so two `ChatPane` instances showing two different conversations can
|
|
39
|
+
* never collide on it. Tests must call {@link __resetComposerDraftCacheForTests} between cases to
|
|
40
|
+
* avoid cross-test leakage; that is the real cost of this choice.
|
|
41
|
+
*
|
|
42
|
+
* Both tiers are bounded to `MAX_CACHED_CONVERSATION_DRAFTS` distinct conversations so a long
|
|
43
|
+
* session visiting many conversations cannot grow without limit — the oldest entry is evicted once
|
|
44
|
+
* the cap is exceeded (insertion order in memory, stored timestamp in storage, since storage
|
|
45
|
+
* outlives insertion order). A cleared/empty draft is deleted from both tiers outright rather than
|
|
46
|
+
* kept as `''`, so the common case (drafted, then sent) leaves nothing behind to resurrect on the
|
|
47
|
+
* next load.
|
|
48
|
+
*/
|
|
49
|
+
/** Cap on distinct conversations tracked at once, per tier; see the module doc's `@tradeoffs`. */
|
|
36
50
|
export const MAX_CACHED_CONVERSATION_DRAFTS = 50;
|
|
51
|
+
/**
|
|
52
|
+
* Key prefix for the `localStorage` tier. Versioned (`.v1.`) so a future change to the stored shape
|
|
53
|
+
* can be introduced without having to interpret, or mis-interpret, entries written by this one.
|
|
54
|
+
*/
|
|
55
|
+
export const COMPOSER_DRAFT_STORAGE_PREFIX = 'jini.chat.composer-draft.v1.';
|
|
56
|
+
/**
|
|
57
|
+
* Key prefix for a conversation's staged attachment REFERENCES. Deliberately a separate entry from
|
|
58
|
+
* the draft text rather than one combined record: text and attachments must degrade independently,
|
|
59
|
+
* so a corrupt or unwritable attachment entry can never cost the operator their words, and vice
|
|
60
|
+
* versa. See {@link readCachedAttachments} for what these references are and are not.
|
|
61
|
+
*/
|
|
62
|
+
export const COMPOSER_ATTACHMENTS_STORAGE_PREFIX = 'jini.chat.composer-attachments.v1.';
|
|
37
63
|
const drafts = new Map();
|
|
64
|
+
const stagedAttachments = new Map();
|
|
38
65
|
/**
|
|
39
|
-
*
|
|
66
|
+
* The origin's `localStorage`, or `null` when it cannot be used.
|
|
40
67
|
*
|
|
41
|
-
* @returns
|
|
42
|
-
*
|
|
68
|
+
* @returns `null` in a non-browser runtime (SSR, a node-environment test) and in a browser that
|
|
69
|
+
* throws on the property access itself — Safari and Chrome both do that with site data blocked,
|
|
70
|
+
* which is why this is a try/catch rather than a `typeof` check alone.
|
|
43
71
|
* @complexity Time/space: O(1).
|
|
44
72
|
*/
|
|
73
|
+
function storage() {
|
|
74
|
+
try {
|
|
75
|
+
return typeof localStorage === 'undefined' ? null : localStorage;
|
|
76
|
+
}
|
|
77
|
+
catch {
|
|
78
|
+
return null;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* Reads and validates the stored draft for `conversationId`.
|
|
83
|
+
*
|
|
84
|
+
* Anything that does not parse as this module's own envelope is treated as foreign or corrupt and
|
|
85
|
+
* deleted rather than returned — a stale format, another tool writing under a colliding key, or a
|
|
86
|
+
* truncated write must not surface as composer text.
|
|
87
|
+
*
|
|
88
|
+
* @returns The stored draft, or `null` when there is none, storage is unavailable, or the entry
|
|
89
|
+
* failed validation.
|
|
90
|
+
* @complexity Time/space: O(n) in the stored draft's length (the JSON parse).
|
|
91
|
+
*/
|
|
92
|
+
function readStoredDraft(conversationId) {
|
|
93
|
+
const store = storage();
|
|
94
|
+
if (!store)
|
|
95
|
+
return null;
|
|
96
|
+
const key = `${COMPOSER_DRAFT_STORAGE_PREFIX}${conversationId}`;
|
|
97
|
+
try {
|
|
98
|
+
const raw = store.getItem(key);
|
|
99
|
+
if (raw === null)
|
|
100
|
+
return null;
|
|
101
|
+
const parsed = JSON.parse(raw);
|
|
102
|
+
const draft = parsed?.d;
|
|
103
|
+
if (typeof draft !== 'string' || draft === '') {
|
|
104
|
+
store.removeItem(key);
|
|
105
|
+
return null;
|
|
106
|
+
}
|
|
107
|
+
return draft;
|
|
108
|
+
}
|
|
109
|
+
catch {
|
|
110
|
+
// A parse failure leaves the bad entry behind on purpose when `removeItem` is what threw —
|
|
111
|
+
// there is nothing further this module can do, and memory-only is still a working composer.
|
|
112
|
+
try {
|
|
113
|
+
store.removeItem(key);
|
|
114
|
+
}
|
|
115
|
+
catch {
|
|
116
|
+
/* storage is unusable; the in-memory tier still serves this conversation */
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Deletes every stored draft past the cap, oldest write first, so an operator who abandons drafts
|
|
123
|
+
* in many conversations cannot fill the origin's quota and silently break persistence for the
|
|
124
|
+
* conversation they actually care about.
|
|
125
|
+
*
|
|
126
|
+
* @param keep How many entries may remain. Entries whose envelope does not parse are treated as
|
|
127
|
+
* oldest (timestamp `0`) so corrupt rows are the first to go.
|
|
128
|
+
* @complexity Time O(n log n) in the number of this module's stored keys (a sort), bounded in
|
|
129
|
+
* practice by `MAX_CACHED_CONVERSATION_DRAFTS` plus whatever a previous session left behind. Space
|
|
130
|
+
* O(n) in the same.
|
|
131
|
+
*/
|
|
132
|
+
function pruneStored(prefix, keep) {
|
|
133
|
+
const store = storage();
|
|
134
|
+
if (!store)
|
|
135
|
+
return;
|
|
136
|
+
try {
|
|
137
|
+
const entries = [];
|
|
138
|
+
for (let i = 0; i < store.length; i += 1) {
|
|
139
|
+
const key = store.key(i);
|
|
140
|
+
if (key === null || !key.startsWith(prefix))
|
|
141
|
+
continue;
|
|
142
|
+
let at = 0;
|
|
143
|
+
try {
|
|
144
|
+
const parsed = JSON.parse(store.getItem(key) ?? '');
|
|
145
|
+
at = typeof parsed?.t === 'number' ? parsed.t : 0;
|
|
146
|
+
}
|
|
147
|
+
catch {
|
|
148
|
+
at = 0;
|
|
149
|
+
}
|
|
150
|
+
entries.push({ key, at });
|
|
151
|
+
}
|
|
152
|
+
if (entries.length <= keep)
|
|
153
|
+
return;
|
|
154
|
+
entries.sort((a, b) => a.at - b.at);
|
|
155
|
+
for (const entry of entries.slice(0, entries.length - keep))
|
|
156
|
+
store.removeItem(entry.key);
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
/* storage is unusable; nothing to prune and nothing to report */
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
/**
|
|
163
|
+
* Writes or removes the stored copy for `conversationId`.
|
|
164
|
+
*
|
|
165
|
+
* @param draft A blank draft removes the entry. A quota rejection is swallowed: the in-memory tier
|
|
166
|
+
* has already accepted the draft, so the composer keeps working and only reload-survival is lost.
|
|
167
|
+
* @complexity Time O(n) in the draft's length, plus a prune (see {@link pruneStored}) only
|
|
168
|
+
* when this introduces a new key at the cap. Space O(n) in the draft's length.
|
|
169
|
+
*/
|
|
170
|
+
function writeStoredDraft(conversationId, draft) {
|
|
171
|
+
const store = storage();
|
|
172
|
+
if (!store)
|
|
173
|
+
return;
|
|
174
|
+
const key = `${COMPOSER_DRAFT_STORAGE_PREFIX}${conversationId}`;
|
|
175
|
+
try {
|
|
176
|
+
if (draft === '') {
|
|
177
|
+
store.removeItem(key);
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
if (store.getItem(key) === null)
|
|
181
|
+
pruneStored(COMPOSER_DRAFT_STORAGE_PREFIX, MAX_CACHED_CONVERSATION_DRAFTS - 1);
|
|
182
|
+
const envelope = { v: 1, t: Date.now(), d: draft };
|
|
183
|
+
store.setItem(key, JSON.stringify(envelope));
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
/* blocked, full, or unusable storage — memory-only is the documented degraded mode */
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Records `draft` in the in-memory tier, evicting the oldest-inserted conversation first if this
|
|
191
|
+
* would introduce a new entry past {@link MAX_CACHED_CONVERSATION_DRAFTS}.
|
|
192
|
+
* @complexity Time/space: O(1) amortized.
|
|
193
|
+
*/
|
|
194
|
+
function rememberInMemory(conversationId, draft) {
|
|
195
|
+
if (!drafts.has(conversationId) && drafts.size >= MAX_CACHED_CONVERSATION_DRAFTS) {
|
|
196
|
+
const oldest = drafts.keys().next().value;
|
|
197
|
+
if (oldest !== undefined)
|
|
198
|
+
drafts.delete(oldest);
|
|
199
|
+
}
|
|
200
|
+
drafts.set(conversationId, draft);
|
|
201
|
+
}
|
|
202
|
+
/**
|
|
203
|
+
* Reads the cached draft for `conversationId`, in memory first and storage second.
|
|
204
|
+
*
|
|
205
|
+
* A storage hit is promoted into memory so the reload-survival path costs one read, not one per
|
|
206
|
+
* keystroke-triggered re-render.
|
|
207
|
+
*
|
|
208
|
+
* @returns The cached draft, or `null` when there is none — including when `conversationId` itself
|
|
209
|
+
* is absent (an untitled/new conversation has nothing to key on yet). Never returns `''`; callers
|
|
210
|
+
* treat `null` as "nothing to restore".
|
|
211
|
+
* @complexity Time/space: O(1) on an in-memory hit, O(n) in the stored draft's length otherwise.
|
|
212
|
+
*/
|
|
45
213
|
export function readCachedDraft(conversationId) {
|
|
46
214
|
if (!conversationId)
|
|
47
215
|
return null;
|
|
48
|
-
|
|
216
|
+
const remembered = drafts.get(conversationId);
|
|
217
|
+
if (remembered !== undefined)
|
|
218
|
+
return remembered;
|
|
219
|
+
const stored = readStoredDraft(conversationId);
|
|
220
|
+
if (stored === null)
|
|
221
|
+
return null;
|
|
222
|
+
rememberInMemory(conversationId, stored);
|
|
223
|
+
return stored;
|
|
49
224
|
}
|
|
50
225
|
/**
|
|
51
|
-
* Stores `draft` for `conversationId
|
|
52
|
-
* introduce a new entry past {@link MAX_CACHED_CONVERSATION_DRAFTS}.
|
|
226
|
+
* Stores `draft` for `conversationId` in both tiers.
|
|
53
227
|
*
|
|
54
228
|
* @param conversationId No-ops when absent (`null`/`undefined`) — there is nothing to key on yet.
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
229
|
+
* `useComposer` covers that window by adopting the typed text once an id arrives; see its own doc.
|
|
230
|
+
* @param draft A blank (or whitespace-only) draft deletes the entry from both tiers instead of
|
|
231
|
+
* storing `''`. This is what makes a successful send clear the draft for free: `useComposer.reset`
|
|
232
|
+
* writes `''` here.
|
|
233
|
+
* @complexity Time/space: O(n) in the draft's length; see {@link writeStoredDraft}.
|
|
58
234
|
*/
|
|
59
235
|
export function writeCachedDraft(conversationId, draft) {
|
|
60
236
|
if (!conversationId)
|
|
61
237
|
return;
|
|
62
238
|
if (draft.trim() === '') {
|
|
63
239
|
drafts.delete(conversationId);
|
|
240
|
+
writeStoredDraft(conversationId, '');
|
|
64
241
|
return;
|
|
65
242
|
}
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (oldest !== undefined)
|
|
69
|
-
drafts.delete(oldest);
|
|
70
|
-
}
|
|
71
|
-
drafts.set(conversationId, draft);
|
|
243
|
+
rememberInMemory(conversationId, draft);
|
|
244
|
+
writeStoredDraft(conversationId, draft);
|
|
72
245
|
}
|
|
73
246
|
/**
|
|
74
|
-
* Explicitly evicts `conversationId`'s
|
|
75
|
-
* (e.g. on conversation delete) rather than waiting for the cap-triggered eviction
|
|
76
|
-
* currently called by any host — a host's own conversation-delete flow is a candidate
|
|
77
|
-
* caller, out of scope here (that flow lives outside this package).
|
|
247
|
+
* Explicitly evicts `conversationId`'s draft from both tiers, for a host that wants to free it
|
|
248
|
+
* immediately (e.g. on conversation delete) rather than waiting for the cap-triggered eviction.
|
|
249
|
+
* Not currently called by any host — a host's own conversation-delete flow is a candidate
|
|
250
|
+
* follow-up caller, out of scope here (that flow lives outside this package).
|
|
78
251
|
* @complexity Time/space: O(1).
|
|
79
252
|
*/
|
|
80
253
|
export function clearCachedDraft(conversationId) {
|
|
81
254
|
if (!conversationId)
|
|
82
255
|
return;
|
|
83
256
|
drafts.delete(conversationId);
|
|
257
|
+
writeStoredDraft(conversationId, '');
|
|
258
|
+
writeCachedAttachments(conversationId, []);
|
|
84
259
|
}
|
|
85
|
-
/**
|
|
86
|
-
|
|
260
|
+
/**
|
|
261
|
+
* Narrows one parsed array element to a `ChatAttachment`.
|
|
262
|
+
*
|
|
263
|
+
* Validated field by field rather than trusted, for the same reason the draft envelope is: this
|
|
264
|
+
* data came back from storage, where anything on the origin could have written it. `size`/`order`
|
|
265
|
+
* are optional in the type, so they are checked only when present.
|
|
266
|
+
* @complexity Time/space: O(1).
|
|
267
|
+
*/
|
|
268
|
+
function isChatAttachment(candidate) {
|
|
269
|
+
if (typeof candidate !== 'object' || candidate === null)
|
|
270
|
+
return false;
|
|
271
|
+
const { path, name, kind, size, order } = candidate;
|
|
272
|
+
if (typeof path !== 'string' || path === '')
|
|
273
|
+
return false;
|
|
274
|
+
if (typeof name !== 'string')
|
|
275
|
+
return false;
|
|
276
|
+
if (kind !== 'image' && kind !== 'file')
|
|
277
|
+
return false;
|
|
278
|
+
if (size !== undefined && typeof size !== 'number')
|
|
279
|
+
return false;
|
|
280
|
+
if (order !== undefined && typeof order !== 'number')
|
|
281
|
+
return false;
|
|
282
|
+
return true;
|
|
283
|
+
}
|
|
284
|
+
/**
|
|
285
|
+
* Reads the stored attachment references for `conversationId`.
|
|
286
|
+
*
|
|
287
|
+
* Elements that do not validate are dropped individually rather than failing the whole entry — one
|
|
288
|
+
* malformed row should cost the operator that row, not every attachment on the turn.
|
|
289
|
+
*
|
|
290
|
+
* @returns The stored references, or `null` when there are none, storage is unavailable, or nothing
|
|
291
|
+
* in the entry validated.
|
|
292
|
+
* @complexity Time/space: O(n) in the number of stored references.
|
|
293
|
+
*/
|
|
294
|
+
function readStoredAttachments(conversationId) {
|
|
295
|
+
const store = storage();
|
|
296
|
+
if (!store)
|
|
297
|
+
return null;
|
|
298
|
+
const key = `${COMPOSER_ATTACHMENTS_STORAGE_PREFIX}${conversationId}`;
|
|
299
|
+
try {
|
|
300
|
+
const raw = store.getItem(key);
|
|
301
|
+
if (raw === null)
|
|
302
|
+
return null;
|
|
303
|
+
const parsed = JSON.parse(raw);
|
|
304
|
+
const list = parsed?.a;
|
|
305
|
+
if (!Array.isArray(list)) {
|
|
306
|
+
store.removeItem(key);
|
|
307
|
+
return null;
|
|
308
|
+
}
|
|
309
|
+
const valid = list.filter(isChatAttachment);
|
|
310
|
+
if (valid.length === 0) {
|
|
311
|
+
store.removeItem(key);
|
|
312
|
+
return null;
|
|
313
|
+
}
|
|
314
|
+
return valid;
|
|
315
|
+
}
|
|
316
|
+
catch {
|
|
317
|
+
try {
|
|
318
|
+
store.removeItem(key);
|
|
319
|
+
}
|
|
320
|
+
catch {
|
|
321
|
+
/* storage is unusable; the in-memory tier still serves this conversation */
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Reads the cached attachment references for `conversationId`, in memory first, storage second.
|
|
328
|
+
*
|
|
329
|
+
* **These are references, never bytes.** By the time an attachment reaches the composer it has
|
|
330
|
+
* already been uploaded (`ChatPane` blocks sending while uploads are in flight), so what is cached
|
|
331
|
+
* here is the server-side record — a path, a name, a kind. The file itself lives wherever the host
|
|
332
|
+
* put it and is subject to that host's retention, which is why a caller must not treat a returned
|
|
333
|
+
* reference as proof the file still exists. `useComposer` restores these only when the host supplies
|
|
334
|
+
* a validator; see its `validateAttachments` option.
|
|
335
|
+
*
|
|
336
|
+
* @returns The cached references, or `null` when there are none.
|
|
337
|
+
* @complexity Time/space: O(1) on an in-memory hit, O(n) in the stored count otherwise.
|
|
338
|
+
*/
|
|
339
|
+
export function readCachedAttachments(conversationId) {
|
|
340
|
+
if (!conversationId)
|
|
341
|
+
return null;
|
|
342
|
+
const remembered = stagedAttachments.get(conversationId);
|
|
343
|
+
if (remembered !== undefined)
|
|
344
|
+
return remembered;
|
|
345
|
+
const stored = readStoredAttachments(conversationId);
|
|
346
|
+
if (stored === null)
|
|
347
|
+
return null;
|
|
348
|
+
stagedAttachments.set(conversationId, stored);
|
|
349
|
+
return stored;
|
|
350
|
+
}
|
|
351
|
+
/**
|
|
352
|
+
* Stores `attachments` for `conversationId` in both tiers.
|
|
353
|
+
*
|
|
354
|
+
* @param attachments An empty list deletes the entry, so a sent or cleared turn leaves nothing to
|
|
355
|
+
* restore — the same rule the draft text follows.
|
|
356
|
+
* @complexity Time/space: O(n) in the number of references.
|
|
357
|
+
*/
|
|
358
|
+
export function writeCachedAttachments(conversationId, attachments) {
|
|
359
|
+
if (!conversationId)
|
|
360
|
+
return;
|
|
361
|
+
const key = `${COMPOSER_ATTACHMENTS_STORAGE_PREFIX}${conversationId}`;
|
|
362
|
+
const store = storage();
|
|
363
|
+
if (attachments.length === 0) {
|
|
364
|
+
stagedAttachments.delete(conversationId);
|
|
365
|
+
try {
|
|
366
|
+
store?.removeItem(key);
|
|
367
|
+
}
|
|
368
|
+
catch {
|
|
369
|
+
/* blocked storage; the in-memory tier is already correct */
|
|
370
|
+
}
|
|
371
|
+
return;
|
|
372
|
+
}
|
|
373
|
+
if (!stagedAttachments.has(conversationId) && stagedAttachments.size >= MAX_CACHED_CONVERSATION_DRAFTS) {
|
|
374
|
+
const oldest = stagedAttachments.keys().next().value;
|
|
375
|
+
if (oldest !== undefined)
|
|
376
|
+
stagedAttachments.delete(oldest);
|
|
377
|
+
}
|
|
378
|
+
stagedAttachments.set(conversationId, attachments);
|
|
379
|
+
if (!store)
|
|
380
|
+
return;
|
|
381
|
+
try {
|
|
382
|
+
if (store.getItem(key) === null)
|
|
383
|
+
pruneStored(COMPOSER_ATTACHMENTS_STORAGE_PREFIX, MAX_CACHED_CONVERSATION_DRAFTS - 1);
|
|
384
|
+
const envelope = { v: 1, t: Date.now(), a: attachments };
|
|
385
|
+
store.setItem(key, JSON.stringify(envelope));
|
|
386
|
+
}
|
|
387
|
+
catch {
|
|
388
|
+
/* blocked, full, or unusable storage — memory-only is the documented degraded mode */
|
|
389
|
+
}
|
|
390
|
+
}
|
|
391
|
+
/**
|
|
392
|
+
* Test-only: empties the cache so test cases cannot leak drafts into each other.
|
|
393
|
+
*
|
|
394
|
+
* @param options `keepStorage: true` empties only the in-memory tier, leaving `localStorage`
|
|
395
|
+
* intact. That is the honest simulation of a page reload — fresh module scope, storage that
|
|
396
|
+
* outlived it — and is how the durability tests reproduce the bug this module exists to fix.
|
|
397
|
+
* @complexity Time O(n) in the number of this module's stored keys; space O(n) in the same.
|
|
398
|
+
*/
|
|
399
|
+
export function __resetComposerDraftCacheForTests(options) {
|
|
87
400
|
drafts.clear();
|
|
401
|
+
stagedAttachments.clear();
|
|
402
|
+
if (options?.keepStorage === true)
|
|
403
|
+
return;
|
|
404
|
+
pruneStored(COMPOSER_DRAFT_STORAGE_PREFIX, 0);
|
|
405
|
+
pruneStored(COMPOSER_ATTACHMENTS_STORAGE_PREFIX, 0);
|
|
88
406
|
}
|
|
89
407
|
//# sourceMappingURL=composer-draft-cache.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"composer-draft-cache.js","sourceRoot":"","sources":["../../../src/react/hooks/composer-draft-cache.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"composer-draft-cache.js","sourceRoot":"","sources":["../../../src/react/hooks/composer-draft-cache.ts"],"names":[],"mappings":"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"}
|
|
@@ -17,6 +17,23 @@ export interface UseComposerOptions {
|
|
|
17
17
|
* exists.
|
|
18
18
|
*/
|
|
19
19
|
conversationId?: string | null;
|
|
20
|
+
/**
|
|
21
|
+
* Confirms which of a conversation's previously staged attachments still exist, so a restored
|
|
22
|
+
* draft never shows a chip for a file the host has since garbage-collected — that looks intact and
|
|
23
|
+
* then fails at send, which is worse than not restoring it at all.
|
|
24
|
+
*
|
|
25
|
+
* Receives the cached references and returns the subset still valid (order need not be preserved;
|
|
26
|
+
* an empty array means none survived). A rejection is treated as "none survived" — restoring
|
|
27
|
+
* unverified references is the failure mode this option exists to prevent.
|
|
28
|
+
*
|
|
29
|
+
* **Omitting it disables attachment persistence entirely** — nothing is written and nothing is
|
|
30
|
+
* restored, which is exactly the behavior before this option existed. Only the draft TEXT is
|
|
31
|
+
* persisted then, so a host that cannot answer the liveness question loses nothing and risks
|
|
32
|
+
* nothing. Text and attachments are stored under separate keys and degrade independently.
|
|
33
|
+
*
|
|
34
|
+
* Captured in a ref, so an inline arrow is safe and will not re-run the restore on every render.
|
|
35
|
+
*/
|
|
36
|
+
validateAttachments?: (attachments: readonly ChatAttachment[]) => Promise<readonly ChatAttachment[]>;
|
|
20
37
|
}
|
|
21
38
|
export interface MentionPopoverState {
|
|
22
39
|
open: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useComposer.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useComposer.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"useComposer.d.ts","sourceRoot":"","sources":["../../../src/react/hooks/useComposer.ts"],"names":[],"mappings":"AA8BA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAQ1D,OAAO,KAAK,EAAE,cAAc,EAAE,aAAa,EAAE,aAAa,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAErG,MAAM,WAAW,wBAAwB;IACvC,IAAI,EAAE,MAAM,MAAM,GAAG,IAAI,CAAC;IAC1B,KAAK,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;CAChC;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,cAAc,CAAC;IAC9B,OAAO,CAAC,EAAE,mBAAmB,CAAC;IAC9B,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B,WAAW,CAAC,EAAE,wBAAwB,CAAC;IACvC;;;;;OAKG;IACH,cAAc,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC/B;;;;;;;;;;;;;;;OAeG;IACH,mBAAmB,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,cAAc,EAAE,KAAK,OAAO,CAAC,SAAS,cAAc,EAAE,CAAC,CAAC;CACtG;AAED,MAAM,WAAW,mBAAmB;IAClC,IAAI,EAAE,OAAO,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,aAAa,EAAE,CAAC;CAC1B;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,IAAI,CAAC;IACjC,WAAW,EAAE,cAAc,EAAE,CAAC;IAC9B,+IAA+I;IAC/I,cAAc,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IACjD,aAAa,EAAE,CAAC,UAAU,EAAE,cAAc,KAAK,IAAI,CAAC;IACpD,gBAAgB,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,IAAI,CAAC;IACvC,gBAAgB,EAAE,MAAM,IAAI,CAAC;IAC7B,KAAK,EAAE,cAAc,GAAG,SAAS,CAAC;IAClC,QAAQ,EAAE,CAAC,IAAI,EAAE,cAAc,KAAK,IAAI,CAAC;IACzC,OAAO,EAAE,mBAAmB,CAAC;IAC7B,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAC9C,YAAY,EAAE,MAAM,IAAI,CAAC;IACzB,gHAAgH;IAChH,aAAa,EAAE,CAAC,MAAM,EAAE,aAAa,KAAK,IAAI,CAAC;IAC/C,yGAAyG;IACzG,SAAS,EAAE,OAAO,CAAC;IACnB,gFAAgF;IAChF,KAAK,EAAE,MAAM,IAAI,CAAC;CACnB;AAID,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,iBAAiB,CAgN/E"}
|