MCPcopy Create free account
hub / github.com/Kilo-Org/cloud / useAttachmentQueue

Function useAttachmentQueue

packages/kilo-chat-hooks/src/use-attachment-queue.ts:243–420  ·  view source on GitHub ↗
(
  client: KiloChatClient,
  conversationId: string,
  options: UseAttachmentQueueOptions
)

Source from the content-addressed store, hash-verified

241}
242
243export function useAttachmentQueue(
244 client: KiloChatClient,
245 conversationId: string,
246 options: UseAttachmentQueueOptions
247): UseAttachmentQueueResult {
248 const [state, dispatch] = useReducer(attachmentQueueReducer, { rows: [] });
249 const { performUpload, maxBytes, generateTempId, onSizeRejected } = options;
250 const generate = generateTempId ?? defaultTempId;
251
252 type Pending = {
253 abort: AbortController;
254 putUrl?: string;
255 putHeaders?: Record<string, string>;
256 putUrlExpiresAtMs?: number;
257 };
258 const pendingRef = useRef<Map<string, Pending>>(new Map());
259 // Blobs are tracked separately so previews keep working after the upload
260 // completes — pendingRef releases its entry on setReady to free upload
261 // bookkeeping, but the local bytes are still useful for the preview chip
262 // until the user sends or removes the attachment.
263 const blobsRef = useRef<Map<string, Blob>>(new Map());
264
265 type UploadArgs = { tempId: string; filename: string; mimeType: string; size: number };
266
267 const startUpload = useCallback(
268 async (args: UploadArgs) => {
269 const { tempId, filename, mimeType, size } = args;
270 const pending = pendingRef.current.get(tempId);
271 const blob = blobsRef.current.get(tempId);
272 if (!pending || !blob) return;
273
274 try {
275 let putUrl = pending.putUrl;
276 let putHeaders = pending.putHeaders;
277 const expiresAtMs = pending.putUrlExpiresAtMs ?? 0;
278 // Treat the URL as expired a minute early to avoid racing R2 at the boundary.
279 const putUrlExpired = Date.now() > expiresAtMs - 60 * 1000;
280
281 if (!putUrl || !putHeaders || putUrlExpired) {
282 const res = await client.initAttachment({
283 conversationId,
284 mimeType,
285 size,
286 filename,
287 idempotencyKey: tempId,
288 });
289 if (pending.abort.signal.aborted) return;
290 pending.putUrl = res.putUrl;
291 pending.putHeaders = res.putHeaders;
292 pending.putUrlExpiresAtMs = res.putUrlExpiresAt * 1000;
293 putUrl = res.putUrl;
294 putHeaders = res.putHeaders;
295 dispatch({ type: 'setInited', tempId, attachmentId: res.attachmentId });
296 }
297
298 await performUpload(blob, putUrl, putHeaders, {
299 signal: pending.abort.signal,
300 onProgress: fraction => dispatch({ type: 'setProgress', tempId, progress: fraction }),

Callers 2

MessageInputFunction · 0.90

Calls 12

selectReadyBlocksFunction · 0.85
selectIsUploadingFunction · 0.85
selectHasFailedFunction · 0.85
hasMethod · 0.80
getMethod · 0.65
deleteMethod · 0.65
setMethod · 0.65
abortMethod · 0.65
valuesMethod · 0.65
clearMethod · 0.65
dispatchFunction · 0.50
initAttachmentMethod · 0.45

Tested by

no test coverage detected