@coffer-org/plugin-webchat 2.2.1 → 2.2.3

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.
@@ -6186,12 +6186,49 @@ function useMinWidth(name, fallback) {
6186
6186
  }
6187
6187
  //#endregion
6188
6188
  //#region ../web/api.ts
6189
+ function uploadFileWithProgress(file, onProgress) {
6190
+ return new Promise((resolve) => {
6191
+ const xhr = new XMLHttpRequest();
6192
+ xhr.open("POST", "/api/upload");
6193
+ xhr.upload.addEventListener("progress", (event) => {
6194
+ if (event.lengthComputable) onProgress(Math.min(100, Math.round(event.loaded / event.total * 100)));
6195
+ });
6196
+ xhr.addEventListener("load", () => {
6197
+ if (xhr.status < 200 || xhr.status >= 300) {
6198
+ resolve(null);
6199
+ return;
6200
+ }
6201
+ try {
6202
+ const j = JSON.parse(xhr.responseText);
6203
+ const name = j.name ?? j.filename;
6204
+ if (!name) {
6205
+ resolve(null);
6206
+ return;
6207
+ }
6208
+ onProgress(100);
6209
+ resolve({
6210
+ name,
6211
+ ...j.mime ? { mime: j.mime } : {},
6212
+ ...j.size != null ? { size: j.size } : {}
6213
+ });
6214
+ } catch {
6215
+ resolve(null);
6216
+ }
6217
+ });
6218
+ xhr.addEventListener("error", () => resolve(null));
6219
+ xhr.addEventListener("abort", () => resolve(null));
6220
+ const fd = new FormData();
6221
+ fd.append("file", file);
6222
+ xhr.send(fd);
6223
+ });
6224
+ }
6189
6225
  /**
6190
6226
  * Upload a file → descriptor from the server (or null).
6191
6227
  * Take mime/size from the response, not the browser File: the server overwrites
6192
6228
  * them from disk anyway, so client values would only be noise.
6193
6229
  */
6194
- async function uploadFile(file) {
6230
+ async function uploadFile(file, onProgress) {
6231
+ if (onProgress) return uploadFileWithProgress(file, onProgress);
6195
6232
  const fd = new FormData();
6196
6233
  fd.append("file", file);
6197
6234
  const r = await fetch("/api/upload", {
@@ -29254,7 +29291,7 @@ var Bubble = memo(function Bubble({ role, text, reasoning, attachments }) {
29254
29291
  });
29255
29292
  //#endregion
29256
29293
  //#region src/ui/ChatComposer.tsx
29257
- function ChatComposer({ streaming, onSend, onStop, attachments, uploading, uploadError, onPickFiles, onRemoveAttachment }) {
29294
+ function ChatComposer({ streaming, onSend, onStop, attachments, uploading, uploadError, uploadProgress, onPickFiles, onRemoveAttachment }) {
29258
29295
  const { t } = useTranslation();
29259
29296
  const [text, setText] = useState("");
29260
29297
  function submit() {
@@ -29263,7 +29300,7 @@ function ChatComposer({ streaming, onSend, onStop, attachments, uploading, uploa
29263
29300
  }
29264
29301
  return /* @__PURE__ */ jsxs("div", {
29265
29302
  className: "flex shrink-0 flex-col gap-2 border-t border-border p-2",
29266
- children: [(attachments.length > 0 || uploadError) && /* @__PURE__ */ jsxs("div", {
29303
+ children: [(attachments.length > 0 || uploading || uploadError) && /* @__PURE__ */ jsxs("div", {
29267
29304
  className: "flex flex-wrap items-center gap-1.5 px-1",
29268
29305
  "data-chat-attachments": true,
29269
29306
  children: [
@@ -29287,10 +29324,31 @@ function ChatComposer({ streaming, onSend, onStop, attachments, uploading, uploa
29287
29324
  })
29288
29325
  ]
29289
29326
  }, `${a.name}-${i}`)),
29290
- uploading && /* @__PURE__ */ jsx(LoaderCircle, {
29291
- size: 14,
29292
- className: "animate-spin text-muted",
29293
- "aria-label": t("webchat.uploading")
29327
+ uploading && /* @__PURE__ */ jsxs("div", {
29328
+ className: "flex min-w-32 flex-1 items-center gap-2",
29329
+ "aria-label": t("webchat.uploading"),
29330
+ children: [
29331
+ /* @__PURE__ */ jsx(LoaderCircle, {
29332
+ size: 14,
29333
+ className: "shrink-0 animate-spin text-muted"
29334
+ }),
29335
+ /* @__PURE__ */ jsx("div", {
29336
+ role: "progressbar",
29337
+ "aria-label": t("webchat.uploadProgress"),
29338
+ "aria-valuemin": 0,
29339
+ "aria-valuemax": 100,
29340
+ "aria-valuenow": uploadProgress,
29341
+ className: "h-1.5 min-w-0 flex-1 overflow-hidden rounded-full bg-border",
29342
+ children: /* @__PURE__ */ jsx("div", {
29343
+ className: "h-full rounded-full bg-accent transition-[width]",
29344
+ style: { width: `${uploadProgress}%` }
29345
+ })
29346
+ }),
29347
+ /* @__PURE__ */ jsxs("span", {
29348
+ className: "text-xs tabular-nums text-muted",
29349
+ children: [uploadProgress, "%"]
29350
+ })
29351
+ ]
29294
29352
  }),
29295
29353
  uploadError && /* @__PURE__ */ jsx("span", {
29296
29354
  className: "text-xs text-danger",
@@ -29443,6 +29501,7 @@ function ChatWidget() {
29443
29501
  const [threadsOpen, setThreadsOpen] = useState(false);
29444
29502
  const [attachments, setAttachments] = useState([]);
29445
29503
  const [uploading, setUploading] = useState(false);
29504
+ const [uploadProgress, setUploadProgress] = useState(0);
29446
29505
  const [uploadError, setUploadError] = useState(null);
29447
29506
  const wide = useMinWidth("sm", 640);
29448
29507
  const wideNav = useMinWidth("nav", 768);
@@ -29486,6 +29545,7 @@ function ChatWidget() {
29486
29545
  if (!files.length) return;
29487
29546
  setUploadError(null);
29488
29547
  setUploading(true);
29548
+ setUploadProgress(0);
29489
29549
  const added = [];
29490
29550
  let error = null;
29491
29551
  for (const file of files) {
@@ -29494,7 +29554,8 @@ function ChatWidget() {
29494
29554
  continue;
29495
29555
  }
29496
29556
  try {
29497
- const rec = await uploadFile(file);
29557
+ setUploadProgress(0);
29558
+ const rec = await uploadFile(file, setUploadProgress);
29498
29559
  if (rec) added.push({
29499
29560
  ...rec,
29500
29561
  label: file.name
@@ -29506,6 +29567,7 @@ function ChatWidget() {
29506
29567
  }
29507
29568
  if (added.length) setAttachments((prev) => [...prev, ...added]);
29508
29569
  setUploading(false);
29570
+ setUploadProgress(0);
29509
29571
  if (error) setUploadError(error);
29510
29572
  }, []);
29511
29573
  const hasConversation = chat.messages.length > 0 || chat.pending !== null;
@@ -29564,6 +29626,7 @@ function ChatWidget() {
29564
29626
  streaming: chat.pending !== null,
29565
29627
  attachments,
29566
29628
  uploading,
29629
+ uploadProgress,
29567
29630
  uploadError,
29568
29631
  onPickFiles: (files) => void pickFiles(Array.from(files)),
29569
29632
  onRemoveAttachment: (index) => setAttachments((prev) => prev.filter((_, i) => i !== index)),
@@ -29582,7 +29645,7 @@ function ChatWidget() {
29582
29645
  });
29583
29646
  if (mode === "bubble") return /* @__PURE__ */ jsx("button", {
29584
29647
  "aria-label": t("webchat.open"),
29585
- onClick: () => setMode("panel"),
29648
+ onClick: () => setMode(wide ? "panel" : "modal"),
29586
29649
  className: "fixed bottom-20 right-4 z-40 grid h-14 w-14 place-items-center rounded-full bg-accent text-white shadow-lg nav:bottom-4",
29587
29650
  children: /* @__PURE__ */ jsx(MessageCircle, { size: 24 })
29588
29651
  });
package/dist/web.js CHANGED
@@ -11,7 +11,7 @@ import { definePluginUI } from "@coffer-org/web-sdk";
11
11
  var ui_default = definePluginUI({ slots: [{
12
12
  slot: "overlay",
13
13
  id: "webchat",
14
- load: () => import("./ChatWidget-Cew9znAf.js")
14
+ load: () => import("./ChatWidget-1G0ZxQH2.js")
15
15
  }] });
16
16
  //#endregion
17
17
  export { ui_default as default };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@coffer-org/plugin-webchat",
3
- "version": "2.2.1",
3
+ "version": "2.2.3",
4
4
  "type": "module",
5
5
  "engines": {
6
6
  "node": ">=24"