@dudousxd/nestjs-agent-react 0.16.0 → 0.18.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/README.md CHANGED
@@ -592,6 +592,23 @@ override with `sendMessage(msg, { body: { model } })`; `chat.setThreadModel(id)`
592
592
  the thread (`null` unpins), so every later turn without its own runs on it. The server refuses a
593
593
  model its catalog does not offer as available.
594
594
 
595
+ ### Quota
596
+
597
+ ```tsx
598
+ import { QuotaBlockedError, useQuota } from '@dudousxd/nestjs-agent-react';
599
+
600
+ const quota = useQuota({ backend }); // GET <base>/quota
601
+ const chat = useAgentChat({ backend, blocked: quota.blocked });
602
+
603
+ <meter value={quota.month?.usedUsd} max={quota.month?.limitUsd} />
604
+ {quota.blocked ? <p>{quota.blocked.reason}</p> : null}
605
+ ```
606
+
607
+ `useQuota` returns every window (`day`, `month`, …) with its usage and ceilings, and `blocked` when
608
+ one is exhausted; it re-reads after every run a chat on the same backend settles (`pollMs` to poll
609
+ as well). With `blocked` passed in, `sendMessage`/`regenerate` reject with `QuotaBlockedError`
610
+ instead of starting a turn the server would refuse with `429`.
611
+
595
612
  ### The transport, standalone
596
613
 
597
614
  `AgentChatTransport` is a plain AI SDK v7 `ChatTransport` — wire it straight into `useChat` for the SSE
@@ -645,15 +662,52 @@ merged. For a single already-atomic row, `storedMessageToUiMessage` maps it 1:1
645
662
  Persisted reasoning comes back as a `reasoning` part before the text (with its duration), and
646
663
  persisted pushed components as `data-ui` parts, so a reloaded thread shows what the live one did.
647
664
 
648
- ### Attachments and the raw client
665
+ ### Attachments
649
666
 
650
- `uploadAttachment` and the rest of `AgentClient` work outside `useAgentChat` too — e.g. from a
651
- file-picker or a standalone approvals-inbox screen:
667
+ `useAttachments` is the composer's file tray without the tray: validation, one upload per file
668
+ with progress and cancel, retry, image previews, and the handlers for a file input, a drop zone and
669
+ paste.
652
670
 
653
- ```ts
654
- const attachment = await chat.client.uploadAttachment(file); // → MessageAttachment
655
- await chat.sendMessage({ text: 'what is in this?' }, { body: { attachments: [attachment] } });
671
+ ```tsx
672
+ import { messageFiles, useAttachments } from '@dudousxd/nestjs-agent-react';
656
673
 
674
+ const files = useAttachments({
675
+ backend: chat.backend, // or `upload: (file, { signal, onProgress }) => myUpload(file)`
676
+ accept: 'image/*,.pdf',
677
+ maxBytes: 20 * 1024 * 1024,
678
+ maxFiles: 5,
679
+ });
680
+
681
+ <div {...files.dropZoneProps} data-dragging={files.isDragging}>
682
+ <textarea onPaste={files.onPaste} />
683
+ <input {...files.inputProps} hidden ref={pickerRef} />
684
+ {files.items.map((item) => (
685
+ <span key={item.id} data-status={item.status}>
686
+ {item.previewUrl ? <img src={item.previewUrl} alt="" /> : item.name}
687
+ {item.status === 'uploading' ? ` ${Math.round(item.progress * 100)}%` : null}
688
+ {item.error}
689
+ <button onClick={() => files.remove(item.id)}>Remove</button>
690
+ </span>
691
+ ))}
692
+ </div>
693
+
694
+ <button
695
+ disabled={files.isUploading}
696
+ onClick={async () => {
697
+ await chat.sendMessage({ text }, { body: { attachments: files.refs } });
698
+ files.clear();
699
+ }}
700
+ />
701
+ ```
702
+
703
+ An item is `uploading`, `ready`, `error` (retry with `files.retry(id)`) or `rejected` (failed
704
+ `accept`/`maxBytes`/`maxFiles` and never uploaded; `error` says why). `remove` cancels an upload in
705
+ flight. `messageFiles(message)` reads the files back off any message — live or replayed — with a
706
+ `kind` (`image`, `pdf`, `text`, …), the extension and, for replayed ones, the stored `mediaId`.
707
+
708
+ The rest of `AgentClient` works outside `useAgentChat` too — e.g. a standalone approvals inbox:
709
+
710
+ ```ts
657
711
  // Human-in-the-loop, callable from any component — not just the chat screen that raised it:
658
712
  await chat.approve({ toolCallId });
659
713
  await chat.reject({ toolCallId, reason: 'not now' });