@coffer-org/plugin-webchat 2.1.0 → 2.2.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/dist/{ChatWidget-B5heKPXv.js → ChatWidget-Cew9znAf.js} +346 -84
- package/dist/runtime/actions.d.ts +6 -0
- package/dist/runtime/actions.js +1 -0
- package/dist/runtime/chain-store.d.ts +1 -0
- package/dist/runtime/chain-store.js +19 -2
- package/dist/runtime/send.js +20 -1
- package/dist/schema.js +23 -30
- package/dist/web.js +1 -1
- package/package.json +4 -4
|
@@ -507,6 +507,34 @@ var createLucideIcon = (iconName, iconNode) => {
|
|
|
507
507
|
* This source code is licensed under the ISC license.
|
|
508
508
|
* See the LICENSE file in the root directory of this source tree.
|
|
509
509
|
*/
|
|
510
|
+
var FileText = createLucideIcon("file-text", [
|
|
511
|
+
["path", {
|
|
512
|
+
d: "M6 22a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h8a2.4 2.4 0 0 1 1.704.706l3.588 3.588A2.4 2.4 0 0 1 20 8v12a2 2 0 0 1-2 2z",
|
|
513
|
+
key: "1oefj6"
|
|
514
|
+
}],
|
|
515
|
+
["path", {
|
|
516
|
+
d: "M14 2v5a1 1 0 0 0 1 1h5",
|
|
517
|
+
key: "wfsgrz"
|
|
518
|
+
}],
|
|
519
|
+
["path", {
|
|
520
|
+
d: "M10 9H8",
|
|
521
|
+
key: "b1mrlr"
|
|
522
|
+
}],
|
|
523
|
+
["path", {
|
|
524
|
+
d: "M16 13H8",
|
|
525
|
+
key: "t4e002"
|
|
526
|
+
}],
|
|
527
|
+
["path", {
|
|
528
|
+
d: "M16 17H8",
|
|
529
|
+
key: "z1uh3a"
|
|
530
|
+
}]
|
|
531
|
+
]);
|
|
532
|
+
/**
|
|
533
|
+
* @license lucide-react v1.24.0 - ISC
|
|
534
|
+
*
|
|
535
|
+
* This source code is licensed under the ISC license.
|
|
536
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
537
|
+
*/
|
|
510
538
|
var List = createLucideIcon("list", [
|
|
511
539
|
["path", {
|
|
512
540
|
d: "M3 5h.01",
|
|
@@ -539,6 +567,16 @@ var List = createLucideIcon("list", [
|
|
|
539
567
|
* This source code is licensed under the ISC license.
|
|
540
568
|
* See the LICENSE file in the root directory of this source tree.
|
|
541
569
|
*/
|
|
570
|
+
var LoaderCircle = createLucideIcon("loader-circle", [["path", {
|
|
571
|
+
d: "M21 12a9 9 0 1 1-6.219-8.56",
|
|
572
|
+
key: "13zald"
|
|
573
|
+
}]]);
|
|
574
|
+
/**
|
|
575
|
+
* @license lucide-react v1.24.0 - ISC
|
|
576
|
+
*
|
|
577
|
+
* This source code is licensed under the ISC license.
|
|
578
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
579
|
+
*/
|
|
542
580
|
var Maximize2 = createLucideIcon("maximize-2", [
|
|
543
581
|
["path", {
|
|
544
582
|
d: "M15 3h6v6",
|
|
@@ -597,6 +635,16 @@ var Minimize2 = createLucideIcon("minimize-2", [
|
|
|
597
635
|
* This source code is licensed under the ISC license.
|
|
598
636
|
* See the LICENSE file in the root directory of this source tree.
|
|
599
637
|
*/
|
|
638
|
+
var Paperclip = createLucideIcon("paperclip", [["path", {
|
|
639
|
+
d: "m16 6-8.414 8.586a2 2 0 0 0 2.829 2.829l8.414-8.586a4 4 0 1 0-5.657-5.657l-8.379 8.551a6 6 0 1 0 8.485 8.485l8.379-8.551",
|
|
640
|
+
key: "1miecu"
|
|
641
|
+
}]]);
|
|
642
|
+
/**
|
|
643
|
+
* @license lucide-react v1.24.0 - ISC
|
|
644
|
+
*
|
|
645
|
+
* This source code is licensed under the ISC license.
|
|
646
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
647
|
+
*/
|
|
600
648
|
var Plus = createLucideIcon("plus", [["path", {
|
|
601
649
|
d: "M5 12h14",
|
|
602
650
|
key: "1ays0h"
|
|
@@ -637,6 +685,26 @@ var Square = createLucideIcon("square", [["rect", {
|
|
|
637
685
|
* This source code is licensed under the ISC license.
|
|
638
686
|
* See the LICENSE file in the root directory of this source tree.
|
|
639
687
|
*/
|
|
688
|
+
var Upload = createLucideIcon("upload", [
|
|
689
|
+
["path", {
|
|
690
|
+
d: "M12 3v12",
|
|
691
|
+
key: "1x0j5s"
|
|
692
|
+
}],
|
|
693
|
+
["path", {
|
|
694
|
+
d: "m17 8-5-5-5 5",
|
|
695
|
+
key: "7q97r8"
|
|
696
|
+
}],
|
|
697
|
+
["path", {
|
|
698
|
+
d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4",
|
|
699
|
+
key: "ih7n3h"
|
|
700
|
+
}]
|
|
701
|
+
]);
|
|
702
|
+
/**
|
|
703
|
+
* @license lucide-react v1.24.0 - ISC
|
|
704
|
+
*
|
|
705
|
+
* This source code is licensed under the ISC license.
|
|
706
|
+
* See the LICENSE file in the root directory of this source tree.
|
|
707
|
+
*/
|
|
640
708
|
var X = createLucideIcon("x", [["path", {
|
|
641
709
|
d: "M18 6 6 18",
|
|
642
710
|
key: "1bl5f8"
|
|
@@ -6040,7 +6108,7 @@ var getDefaultConfig = () => {
|
|
|
6040
6108
|
var twMerge = /*#__PURE__*/ createTailwindMerge(getDefaultConfig);
|
|
6041
6109
|
//#endregion
|
|
6042
6110
|
//#region ../web/lib/utils.ts
|
|
6043
|
-
/**
|
|
6111
|
+
/** Merge classes while resolving Tailwind conflicts (shadcn convention). */
|
|
6044
6112
|
function cn(...inputs) {
|
|
6045
6113
|
return twMerge(clsx(inputs));
|
|
6046
6114
|
}
|
|
@@ -6054,7 +6122,7 @@ var DialogOverlay = React.forwardRef(({ className, ...props }, ref) => /* @__PUR
|
|
|
6054
6122
|
...props
|
|
6055
6123
|
}));
|
|
6056
6124
|
DialogOverlay.displayName = DialogOverlay$1.displayName;
|
|
6057
|
-
/**
|
|
6125
|
+
/** Responsive content: bottom sheet on mobile, centered modal on desktop. */
|
|
6058
6126
|
var DialogContent = React.forwardRef(({ className, children, hideClose, ...props }, ref) => /* @__PURE__ */ jsxs(DialogPortal, { children: [/* @__PURE__ */ jsx(DialogOverlay, {}), /* @__PURE__ */ jsxs(DialogContent$1, {
|
|
6059
6127
|
ref,
|
|
6060
6128
|
className: cn("fixed z-50 flex flex-col border border-border bg-surface text-text shadow-xl", "inset-x-0 bottom-0 max-h-[88dvh] overflow-y-auto rounded-t-2xl", "data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom data-[state=closed]:duration-200", "md:inset-x-auto md:bottom-auto md:left-1/2 md:top-1/2 md:max-h-[85dvh] md:w-full md:max-w-xl md:-translate-x-1/2 md:-translate-y-1/2 md:rounded-2xl", "md:data-[state=open]:slide-in-from-bottom-0 md:data-[state=closed]:slide-out-to-bottom-0 md:data-[state=open]:zoom-in-95 md:data-[state=closed]:zoom-out-95", className),
|
|
@@ -6083,14 +6151,13 @@ DialogDescription.displayName = DialogDescription$1.displayName;
|
|
|
6083
6151
|
//#endregion
|
|
6084
6152
|
//#region ../web/hooks/use-media-query.ts
|
|
6085
6153
|
/**
|
|
6086
|
-
*
|
|
6154
|
+
* Media-query state as a reactive value.
|
|
6087
6155
|
*
|
|
6088
|
-
*
|
|
6089
|
-
*
|
|
6090
|
-
* подвійна робота — таблиця і картки одночасно.
|
|
6156
|
+
* Needed where CSS hiding (`hidden list:block`) is insufficient: hidden elements remain in the
|
|
6157
|
+
* DOM and still render. For a list of 700 records, that means doing the work twice — table and cards.
|
|
6091
6158
|
*
|
|
6092
|
-
* `fallback`
|
|
6093
|
-
*
|
|
6159
|
+
* `fallback` is the answer when `matchMedia` is unavailable (jsdom tests): default to a
|
|
6160
|
+
* “wide screen” so tests see the desktop layout.
|
|
6094
6161
|
*/
|
|
6095
6162
|
function useMediaQuery(query, fallback = true) {
|
|
6096
6163
|
return useSyncExternalStore(useCallback((onChange) => {
|
|
@@ -6104,8 +6171,8 @@ function useMediaQuery(query, fallback = true) {
|
|
|
6104
6171
|
}, [query, fallback]), () => fallback);
|
|
6105
6172
|
}
|
|
6106
6173
|
/**
|
|
6107
|
-
*
|
|
6108
|
-
* Tailwind v4
|
|
6174
|
+
* Breakpoint value from `@theme` (globals.css), so JS and CSS do not drift.
|
|
6175
|
+
* Tailwind v4 publishes them as CSS variables on :root. Without CSS (tests), use `fallback`.
|
|
6109
6176
|
*/
|
|
6110
6177
|
function breakpointPx(name, fallback) {
|
|
6111
6178
|
if (typeof window === "undefined" || typeof getComputedStyle !== "function") return fallback;
|
|
@@ -6113,11 +6180,35 @@ function breakpointPx(name, fallback) {
|
|
|
6113
6180
|
const n = Number.parseInt(raw.trim(), 10);
|
|
6114
6181
|
return Number.isFinite(n) && n > 0 ? n : fallback;
|
|
6115
6182
|
}
|
|
6116
|
-
/** `(min-width: …)`
|
|
6183
|
+
/** `(min-width: …)` for a theme breakpoint. */
|
|
6117
6184
|
function useMinWidth(name, fallback) {
|
|
6118
6185
|
return useMediaQuery(`(min-width: ${breakpointPx(name, fallback)}px)`, true);
|
|
6119
6186
|
}
|
|
6120
6187
|
//#endregion
|
|
6188
|
+
//#region ../web/api.ts
|
|
6189
|
+
/**
|
|
6190
|
+
* Upload a file → descriptor from the server (or null).
|
|
6191
|
+
* Take mime/size from the response, not the browser File: the server overwrites
|
|
6192
|
+
* them from disk anyway, so client values would only be noise.
|
|
6193
|
+
*/
|
|
6194
|
+
async function uploadFile(file) {
|
|
6195
|
+
const fd = new FormData();
|
|
6196
|
+
fd.append("file", file);
|
|
6197
|
+
const r = await fetch("/api/upload", {
|
|
6198
|
+
method: "POST",
|
|
6199
|
+
body: fd
|
|
6200
|
+
});
|
|
6201
|
+
if (!r.ok) return null;
|
|
6202
|
+
const j = await r.json();
|
|
6203
|
+
const name = j.name ?? j.filename;
|
|
6204
|
+
if (!name) return null;
|
|
6205
|
+
return {
|
|
6206
|
+
name,
|
|
6207
|
+
...j.mime ? { mime: j.mime } : {},
|
|
6208
|
+
...j.size != null ? { size: j.size } : {}
|
|
6209
|
+
};
|
|
6210
|
+
}
|
|
6211
|
+
//#endregion
|
|
6121
6212
|
//#region src/ui/sse.ts
|
|
6122
6213
|
async function* readSse(body) {
|
|
6123
6214
|
const reader = body.getReader();
|
|
@@ -6147,14 +6238,13 @@ async function* readSse(body) {
|
|
|
6147
6238
|
}
|
|
6148
6239
|
//#endregion
|
|
6149
6240
|
//#region src/ui/chat-api.ts
|
|
6150
|
-
/**
|
|
6151
|
-
*
|
|
6241
|
+
/** Web-chat transport. Session cookies are sent automatically (same-origin), so there is no
|
|
6242
|
+
* separate authorization here; the gate is on the server. */
|
|
6152
6243
|
var BASE = "/api/plugins/webchat/user";
|
|
6153
6244
|
/**
|
|
6154
|
-
*
|
|
6155
|
-
*
|
|
6156
|
-
*
|
|
6157
|
-
* означали б рівно одне.
|
|
6245
|
+
* Throws on any rejection, including 404 (“plugin was disabled AFTER the widget bundle
|
|
6246
|
+
* loaded”). There is deliberately no separate `null` for this case: the caller keeps
|
|
6247
|
+
* the current thread list either way, so two return codes would mean the same thing.
|
|
6158
6248
|
*/
|
|
6159
6249
|
async function fetchThreads() {
|
|
6160
6250
|
const r = await fetch(`${BASE}/threads`, {
|
|
@@ -6182,7 +6272,8 @@ async function sendMessage(req) {
|
|
|
6182
6272
|
convId: req.convId,
|
|
6183
6273
|
text: req.text,
|
|
6184
6274
|
replyTo: req.replyTo,
|
|
6185
|
-
context: req.context
|
|
6275
|
+
context: req.context,
|
|
6276
|
+
...req.attachments?.length ? { attachments: req.attachments } : {}
|
|
6186
6277
|
}),
|
|
6187
6278
|
signal: req.signal
|
|
6188
6279
|
});
|
|
@@ -6278,7 +6369,7 @@ function useChat() {
|
|
|
6278
6369
|
pending,
|
|
6279
6370
|
pendingReasoning,
|
|
6280
6371
|
error,
|
|
6281
|
-
send: useCallback((text, context) => {
|
|
6372
|
+
send: useCallback((text, context, attachments = []) => {
|
|
6282
6373
|
if (!text.trim() || sendingRef.current) return false;
|
|
6283
6374
|
sendingRef.current = true;
|
|
6284
6375
|
const myTurn = turnRef.current;
|
|
@@ -6289,7 +6380,8 @@ function useChat() {
|
|
|
6289
6380
|
msgId: `local-${newId()}`,
|
|
6290
6381
|
role: "user",
|
|
6291
6382
|
text,
|
|
6292
|
-
ts: Math.floor(Date.now() / 1e3)
|
|
6383
|
+
ts: Math.floor(Date.now() / 1e3),
|
|
6384
|
+
...attachments.length ? { attachments } : {}
|
|
6293
6385
|
}]);
|
|
6294
6386
|
setPending("");
|
|
6295
6387
|
const ctl = new AbortController();
|
|
@@ -6303,6 +6395,7 @@ function useChat() {
|
|
|
6303
6395
|
text,
|
|
6304
6396
|
replyTo: headRef.current,
|
|
6305
6397
|
context,
|
|
6398
|
+
attachments,
|
|
6306
6399
|
signal: ctl.signal,
|
|
6307
6400
|
onDelta: (t) => {
|
|
6308
6401
|
if (turnRef.current !== myTurn) return;
|
|
@@ -6358,8 +6451,8 @@ function useChat() {
|
|
|
6358
6451
|
}
|
|
6359
6452
|
//#endregion
|
|
6360
6453
|
//#region src/ui/use-page-context.ts
|
|
6361
|
-
/**
|
|
6362
|
-
*
|
|
6454
|
+
/** Current-page context for the agent. SPA routes: `/:library/:type[/:id]`;
|
|
6455
|
+
* system branches (settings, browse) are returned by path only. */
|
|
6363
6456
|
function usePageContext() {
|
|
6364
6457
|
const { pathname } = useLocation();
|
|
6365
6458
|
const parts = pathname.split("/").filter(Boolean);
|
|
@@ -29005,9 +29098,8 @@ function remarkGfm(options) {
|
|
|
29005
29098
|
//#endregion
|
|
29006
29099
|
//#region src/ui/internal-link.ts
|
|
29007
29100
|
var SEGMENT = /^[A-Za-z0-9._~%-]+$/;
|
|
29008
|
-
/**
|
|
29009
|
-
*
|
|
29010
|
-
* не дочитуючи got до кінця. */
|
|
29101
|
+
/** Returns captured ':param' values on a match, or null. '*' is legal only as the
|
|
29102
|
+
* last pattern segment, so this branch may return immediately without reading got to the end. */
|
|
29011
29103
|
function matchesPattern(pathname, pattern) {
|
|
29012
29104
|
const got = pathname.split("/").filter(Boolean);
|
|
29013
29105
|
const want = pattern.split("/").filter(Boolean);
|
|
@@ -29053,9 +29145,9 @@ function classifyHref(href, origin, routes, pairs = []) {
|
|
|
29053
29145
|
}
|
|
29054
29146
|
//#endregion
|
|
29055
29147
|
//#region src/ui/chat-nav.tsx
|
|
29056
|
-
/**
|
|
29057
|
-
*
|
|
29058
|
-
*
|
|
29148
|
+
/** What to do with the widget when the user follows a link from a response.
|
|
29149
|
+
* ChatWidget supplies the callback (it owns mode/open), avoiding a prop
|
|
29150
|
+
* threaded through ChatMessages and Bubble. */
|
|
29059
29151
|
var ChatNavContext = createContext(() => {});
|
|
29060
29152
|
function ChatNavProvider({ onNavigate, children }) {
|
|
29061
29153
|
return /* @__PURE__ */ jsx(ChatNavContext.Provider, {
|
|
@@ -29115,31 +29207,37 @@ function ChatMessages({ messages, pending, pendingReasoning }) {
|
|
|
29115
29207
|
children: [messages.map((m) => /* @__PURE__ */ jsx(Bubble, {
|
|
29116
29208
|
role: m.role,
|
|
29117
29209
|
text: m.text,
|
|
29118
|
-
reasoning: m.reasoning
|
|
29210
|
+
reasoning: m.reasoning,
|
|
29211
|
+
attachments: m.attachments
|
|
29119
29212
|
}, m.msgId)), pending !== null && /* @__PURE__ */ jsx(Bubble, {
|
|
29120
29213
|
role: "assistant",
|
|
29121
29214
|
text: pending || t("webchat.thinking"),
|
|
29122
|
-
reasoning: pendingReasoning
|
|
29123
|
-
live: true
|
|
29215
|
+
reasoning: pendingReasoning
|
|
29124
29216
|
})]
|
|
29125
29217
|
});
|
|
29126
29218
|
}
|
|
29127
29219
|
/**
|
|
29128
|
-
* memo:
|
|
29129
|
-
*
|
|
29130
|
-
*
|
|
29131
|
-
*
|
|
29220
|
+
* memo: during streaming, the parent re-renders on EVERY delta (several times per
|
|
29221
|
+
* second). Without memo, each tick would re-render (and therefore re-parse markdown)
|
|
29222
|
+
* for every completed message while only one bubble at the bottom is changing.
|
|
29223
|
+
* The props are primitives, so the default shallow comparison is sufficient.
|
|
29132
29224
|
*/
|
|
29133
|
-
var Bubble = memo(function Bubble({ role, text, reasoning,
|
|
29225
|
+
var Bubble = memo(function Bubble({ role, text, reasoning, attachments }) {
|
|
29134
29226
|
const { t } = useTranslation();
|
|
29135
|
-
if (role === "user") return /* @__PURE__ */
|
|
29227
|
+
if (role === "user") return /* @__PURE__ */ jsxs("div", {
|
|
29136
29228
|
className: "self-end max-w-[85%] rounded-2xl rounded-br-sm bg-accent/15 px-3 py-2 text-sm whitespace-pre-wrap break-words",
|
|
29137
|
-
children:
|
|
29229
|
+
children: [attachments?.length ? /* @__PURE__ */ jsx("div", {
|
|
29230
|
+
className: "mb-1.5 flex flex-wrap gap-1",
|
|
29231
|
+
children: attachments.map((a) => /* @__PURE__ */ jsx("span", {
|
|
29232
|
+
className: "inline-flex max-w-full items-center rounded-md bg-bg/60 px-1.5 py-0.5 text-xs",
|
|
29233
|
+
title: a.name,
|
|
29234
|
+
children: a.label ?? a.name
|
|
29235
|
+
}, a.name))
|
|
29236
|
+
}) : null, text]
|
|
29138
29237
|
});
|
|
29139
29238
|
return /* @__PURE__ */ jsxs("div", {
|
|
29140
29239
|
className: "self-start max-w-[95%] min-w-0 rounded-2xl rounded-bl-sm bg-elevated px-3 py-2 text-sm break-words",
|
|
29141
29240
|
children: [reasoning ? /* @__PURE__ */ jsxs("details", {
|
|
29142
|
-
open: live,
|
|
29143
29241
|
className: "mb-2 rounded-lg border border-border px-2 py-1",
|
|
29144
29242
|
children: [/* @__PURE__ */ jsx("summary", {
|
|
29145
29243
|
className: "cursor-pointer select-none text-xs text-muted",
|
|
@@ -29156,37 +29254,91 @@ var Bubble = memo(function Bubble({ role, text, reasoning, live }) {
|
|
|
29156
29254
|
});
|
|
29157
29255
|
//#endregion
|
|
29158
29256
|
//#region src/ui/ChatComposer.tsx
|
|
29159
|
-
function ChatComposer({ streaming, onSend, onStop }) {
|
|
29257
|
+
function ChatComposer({ streaming, onSend, onStop, attachments, uploading, uploadError, onPickFiles, onRemoveAttachment }) {
|
|
29160
29258
|
const { t } = useTranslation();
|
|
29161
29259
|
const [text, setText] = useState("");
|
|
29162
29260
|
function submit() {
|
|
29163
29261
|
if (!text.trim() || streaming) return;
|
|
29164
|
-
if (onSend(text.trim())) setText("");
|
|
29262
|
+
if (onSend(text.trim(), attachments)) setText("");
|
|
29165
29263
|
}
|
|
29166
29264
|
return /* @__PURE__ */ jsxs("div", {
|
|
29167
|
-
className: "flex shrink-0
|
|
29168
|
-
children: [/* @__PURE__ */
|
|
29169
|
-
className: "flex
|
|
29170
|
-
|
|
29171
|
-
|
|
29172
|
-
|
|
29173
|
-
|
|
29174
|
-
|
|
29175
|
-
|
|
29176
|
-
|
|
29177
|
-
|
|
29178
|
-
|
|
29179
|
-
|
|
29180
|
-
|
|
29181
|
-
|
|
29182
|
-
|
|
29183
|
-
|
|
29184
|
-
|
|
29185
|
-
|
|
29186
|
-
|
|
29187
|
-
|
|
29188
|
-
|
|
29189
|
-
|
|
29265
|
+
className: "flex shrink-0 flex-col gap-2 border-t border-border p-2",
|
|
29266
|
+
children: [(attachments.length > 0 || uploadError) && /* @__PURE__ */ jsxs("div", {
|
|
29267
|
+
className: "flex flex-wrap items-center gap-1.5 px-1",
|
|
29268
|
+
"data-chat-attachments": true,
|
|
29269
|
+
children: [
|
|
29270
|
+
attachments.map((a, i) => /* @__PURE__ */ jsxs("span", {
|
|
29271
|
+
className: "inline-flex min-w-0 max-w-full items-center gap-1 rounded-md bg-elevated px-2 py-1 text-xs",
|
|
29272
|
+
children: [
|
|
29273
|
+
/* @__PURE__ */ jsx(FileText, {
|
|
29274
|
+
size: 13,
|
|
29275
|
+
className: "shrink-0 text-muted"
|
|
29276
|
+
}),
|
|
29277
|
+
/* @__PURE__ */ jsx("span", {
|
|
29278
|
+
className: "max-w-48 truncate",
|
|
29279
|
+
children: a.label ?? a.name
|
|
29280
|
+
}),
|
|
29281
|
+
/* @__PURE__ */ jsx("button", {
|
|
29282
|
+
type: "button",
|
|
29283
|
+
"aria-label": t("webchat.removeAttachment", { name: a.label ?? a.name }),
|
|
29284
|
+
className: "shrink-0 rounded p-0.5 text-muted hover:text-danger",
|
|
29285
|
+
onClick: () => onRemoveAttachment(i),
|
|
29286
|
+
children: /* @__PURE__ */ jsx(X, { size: 13 })
|
|
29287
|
+
})
|
|
29288
|
+
]
|
|
29289
|
+
}, `${a.name}-${i}`)),
|
|
29290
|
+
uploading && /* @__PURE__ */ jsx(LoaderCircle, {
|
|
29291
|
+
size: 14,
|
|
29292
|
+
className: "animate-spin text-muted",
|
|
29293
|
+
"aria-label": t("webchat.uploading")
|
|
29294
|
+
}),
|
|
29295
|
+
uploadError && /* @__PURE__ */ jsx("span", {
|
|
29296
|
+
className: "text-xs text-danger",
|
|
29297
|
+
children: t(uploadError)
|
|
29298
|
+
})
|
|
29299
|
+
]
|
|
29300
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
29301
|
+
className: "flex items-end gap-2",
|
|
29302
|
+
children: [
|
|
29303
|
+
/* @__PURE__ */ jsxs("label", {
|
|
29304
|
+
"aria-label": t("webchat.attach"),
|
|
29305
|
+
className: `cursor-pointer rounded-lg p-2 text-muted hover:bg-elevated hover:text-accent ${uploading ? "pointer-events-none opacity-60" : ""}`,
|
|
29306
|
+
children: [/* @__PURE__ */ jsx(Paperclip, { size: 18 }), /* @__PURE__ */ jsx("input", {
|
|
29307
|
+
type: "file",
|
|
29308
|
+
multiple: true,
|
|
29309
|
+
className: "hidden",
|
|
29310
|
+
disabled: uploading,
|
|
29311
|
+
onChange: (e) => {
|
|
29312
|
+
if (e.target.files?.length) onPickFiles(e.target.files);
|
|
29313
|
+
e.currentTarget.value = "";
|
|
29314
|
+
}
|
|
29315
|
+
})]
|
|
29316
|
+
}),
|
|
29317
|
+
/* @__PURE__ */ jsx("textarea", {
|
|
29318
|
+
className: "flex-1 resize-none rounded-lg border border-border bg-bg px-3 py-2 text-sm max-h-32",
|
|
29319
|
+
rows: 1,
|
|
29320
|
+
value: text,
|
|
29321
|
+
placeholder: t("webchat.placeholder"),
|
|
29322
|
+
onChange: (e) => setText(e.target.value),
|
|
29323
|
+
onKeyDown: (e) => {
|
|
29324
|
+
if (e.key === "Enter" && !e.shiftKey) {
|
|
29325
|
+
e.preventDefault();
|
|
29326
|
+
submit();
|
|
29327
|
+
}
|
|
29328
|
+
}
|
|
29329
|
+
}),
|
|
29330
|
+
streaming ? /* @__PURE__ */ jsx("button", {
|
|
29331
|
+
"aria-label": t("webchat.stop"),
|
|
29332
|
+
className: "rounded-lg p-2 hover:bg-elevated",
|
|
29333
|
+
onClick: onStop,
|
|
29334
|
+
children: /* @__PURE__ */ jsx(Square, { size: 18 })
|
|
29335
|
+
}) : /* @__PURE__ */ jsx("button", {
|
|
29336
|
+
"aria-label": t("webchat.send"),
|
|
29337
|
+
className: "rounded-lg p-2 hover:bg-elevated",
|
|
29338
|
+
onClick: submit,
|
|
29339
|
+
children: /* @__PURE__ */ jsx(Send, { size: 18 })
|
|
29340
|
+
})
|
|
29341
|
+
]
|
|
29190
29342
|
})]
|
|
29191
29343
|
});
|
|
29192
29344
|
}
|
|
@@ -29217,9 +29369,69 @@ function ChatThreadList({ threads, activeId, onOpen, onNew, className = "w-[220p
|
|
|
29217
29369
|
}
|
|
29218
29370
|
//#endregion
|
|
29219
29371
|
//#region src/ui/ChatWidget.tsx
|
|
29220
|
-
|
|
29221
|
-
|
|
29222
|
-
|
|
29372
|
+
var MAX_CHAT_UPLOAD_BYTES = 25 * 1024 * 1024;
|
|
29373
|
+
function isFileDrag(e) {
|
|
29374
|
+
return Array.from(e.dataTransfer.types).includes("Files");
|
|
29375
|
+
}
|
|
29376
|
+
/** One drop surface per popup. The overlay deliberately covers the header, feed, and composer. */
|
|
29377
|
+
function ChatDropSurface({ children, onFiles, className }) {
|
|
29378
|
+
const { t } = useTranslation();
|
|
29379
|
+
const [dragging, setDragging] = useState(false);
|
|
29380
|
+
const depth = useRef(0);
|
|
29381
|
+
function enter(e) {
|
|
29382
|
+
if (!isFileDrag(e)) return;
|
|
29383
|
+
e.preventDefault();
|
|
29384
|
+
depth.current += 1;
|
|
29385
|
+
setDragging(true);
|
|
29386
|
+
}
|
|
29387
|
+
function leave(e) {
|
|
29388
|
+
if (!isFileDrag(e)) return;
|
|
29389
|
+
e.preventDefault();
|
|
29390
|
+
depth.current = Math.max(0, depth.current - 1);
|
|
29391
|
+
if (depth.current === 0) setDragging(false);
|
|
29392
|
+
}
|
|
29393
|
+
function drop(e) {
|
|
29394
|
+
if (!isFileDrag(e)) return;
|
|
29395
|
+
e.preventDefault();
|
|
29396
|
+
depth.current = 0;
|
|
29397
|
+
setDragging(false);
|
|
29398
|
+
onFiles(Array.from(e.dataTransfer.files));
|
|
29399
|
+
}
|
|
29400
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
29401
|
+
className,
|
|
29402
|
+
onDragEnter: enter,
|
|
29403
|
+
onDragLeave: leave,
|
|
29404
|
+
onDragOver: (e) => {
|
|
29405
|
+
if (isFileDrag(e)) {
|
|
29406
|
+
e.preventDefault();
|
|
29407
|
+
e.dataTransfer.dropEffect = "copy";
|
|
29408
|
+
}
|
|
29409
|
+
},
|
|
29410
|
+
onDrop: drop,
|
|
29411
|
+
children: [children, dragging && /* @__PURE__ */ jsx("div", {
|
|
29412
|
+
"data-chat-drop-overlay": true,
|
|
29413
|
+
"data-testid": "chat-drop-overlay",
|
|
29414
|
+
className: "pointer-events-none absolute inset-0 z-50 grid place-items-center bg-surface/95 p-6 text-center backdrop-blur-sm",
|
|
29415
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
29416
|
+
className: "flex h-full w-full flex-col items-center justify-center rounded-2xl border-2 border-dashed border-accent bg-accent/10 text-accent",
|
|
29417
|
+
children: [
|
|
29418
|
+
/* @__PURE__ */ jsx(Upload, { size: 34 }),
|
|
29419
|
+
/* @__PURE__ */ jsx("span", {
|
|
29420
|
+
className: "mt-3 text-base font-medium",
|
|
29421
|
+
children: t("webchat.dropFiles")
|
|
29422
|
+
}),
|
|
29423
|
+
/* @__PURE__ */ jsx("span", {
|
|
29424
|
+
className: "mt-1 text-xs text-muted",
|
|
29425
|
+
children: t("webchat.dropFilesHint")
|
|
29426
|
+
})
|
|
29427
|
+
]
|
|
29428
|
+
})
|
|
29429
|
+
})]
|
|
29430
|
+
});
|
|
29431
|
+
}
|
|
29432
|
+
/** What to do with the widget after following a link from a response. On desktop a 380px
|
|
29433
|
+
* panel does not obstruct the page (a darkened modal does, so it collapses into it);
|
|
29434
|
+
* below the nav breakpoint the panel consumes nearly the whole screen. */
|
|
29223
29435
|
function nextModeAfterNav(wideNav) {
|
|
29224
29436
|
return wideNav ? "panel" : "bubble";
|
|
29225
29437
|
}
|
|
@@ -29229,6 +29441,9 @@ function ChatWidget() {
|
|
|
29229
29441
|
const page = usePageContext();
|
|
29230
29442
|
const [mode, setMode] = useState("bubble");
|
|
29231
29443
|
const [threadsOpen, setThreadsOpen] = useState(false);
|
|
29444
|
+
const [attachments, setAttachments] = useState([]);
|
|
29445
|
+
const [uploading, setUploading] = useState(false);
|
|
29446
|
+
const [uploadError, setUploadError] = useState(null);
|
|
29232
29447
|
const wide = useMinWidth("sm", 640);
|
|
29233
29448
|
const wideNav = useMinWidth("nav", 768);
|
|
29234
29449
|
const onChatNavigate = useCallback(() => {
|
|
@@ -29263,8 +29478,36 @@ function ChatWidget() {
|
|
|
29263
29478
|
}, [isOpen, chat.loadThreads]);
|
|
29264
29479
|
function startNew() {
|
|
29265
29480
|
chat.newConversation();
|
|
29481
|
+
setAttachments([]);
|
|
29482
|
+
setUploadError(null);
|
|
29266
29483
|
setThreadsOpen(false);
|
|
29267
29484
|
}
|
|
29485
|
+
const pickFiles = useCallback(async (files) => {
|
|
29486
|
+
if (!files.length) return;
|
|
29487
|
+
setUploadError(null);
|
|
29488
|
+
setUploading(true);
|
|
29489
|
+
const added = [];
|
|
29490
|
+
let error = null;
|
|
29491
|
+
for (const file of files) {
|
|
29492
|
+
if (file.size > MAX_CHAT_UPLOAD_BYTES) {
|
|
29493
|
+
error ??= "webchat.uploadTooLarge";
|
|
29494
|
+
continue;
|
|
29495
|
+
}
|
|
29496
|
+
try {
|
|
29497
|
+
const rec = await uploadFile(file);
|
|
29498
|
+
if (rec) added.push({
|
|
29499
|
+
...rec,
|
|
29500
|
+
label: file.name
|
|
29501
|
+
});
|
|
29502
|
+
else error ??= "webchat.uploadFailed";
|
|
29503
|
+
} catch {
|
|
29504
|
+
error ??= "webchat.uploadFailed";
|
|
29505
|
+
}
|
|
29506
|
+
}
|
|
29507
|
+
if (added.length) setAttachments((prev) => [...prev, ...added]);
|
|
29508
|
+
setUploading(false);
|
|
29509
|
+
if (error) setUploadError(error);
|
|
29510
|
+
}, []);
|
|
29268
29511
|
const hasConversation = chat.messages.length > 0 || chat.pending !== null;
|
|
29269
29512
|
const header = /* @__PURE__ */ jsxs("div", {
|
|
29270
29513
|
className: "flex shrink-0 items-center justify-between border-b border-border px-3 py-2",
|
|
@@ -29319,7 +29562,19 @@ function ChatWidget() {
|
|
|
29319
29562
|
}),
|
|
29320
29563
|
/* @__PURE__ */ jsx(ChatComposer, {
|
|
29321
29564
|
streaming: chat.pending !== null,
|
|
29322
|
-
|
|
29565
|
+
attachments,
|
|
29566
|
+
uploading,
|
|
29567
|
+
uploadError,
|
|
29568
|
+
onPickFiles: (files) => void pickFiles(Array.from(files)),
|
|
29569
|
+
onRemoveAttachment: (index) => setAttachments((prev) => prev.filter((_, i) => i !== index)),
|
|
29570
|
+
onSend: (text, refs) => {
|
|
29571
|
+
const accepted = refs.length ? chat.send(text, page, refs) : chat.send(text, page);
|
|
29572
|
+
if (accepted) {
|
|
29573
|
+
setAttachments([]);
|
|
29574
|
+
setUploadError(null);
|
|
29575
|
+
}
|
|
29576
|
+
return accepted;
|
|
29577
|
+
},
|
|
29323
29578
|
onStop: chat.stop
|
|
29324
29579
|
})
|
|
29325
29580
|
]
|
|
@@ -29331,7 +29586,8 @@ function ChatWidget() {
|
|
|
29331
29586
|
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",
|
|
29332
29587
|
children: /* @__PURE__ */ jsx(MessageCircle, { size: 24 })
|
|
29333
29588
|
});
|
|
29334
|
-
if (mode === "panel") return /* @__PURE__ */ jsxs(
|
|
29589
|
+
if (mode === "panel") return /* @__PURE__ */ jsxs(ChatDropSurface, {
|
|
29590
|
+
onFiles: pickFiles,
|
|
29335
29591
|
className: "fixed bottom-20 right-4 z-40 flex h-[560px] max-h-[80dvh] w-[380px] max-w-[calc(100vw-2rem)] flex-col overflow-hidden rounded-2xl border border-border bg-surface shadow-xl nav:bottom-4",
|
|
29336
29592
|
children: [header, conversation]
|
|
29337
29593
|
});
|
|
@@ -29346,21 +29602,27 @@ function ChatWidget() {
|
|
|
29346
29602
|
}), /* @__PURE__ */ jsx(DialogContent, {
|
|
29347
29603
|
hideClose: true,
|
|
29348
29604
|
className: "h-[85dvh] w-full p-0 md:h-[85dvh] md:w-[80vw] md:max-w-[1040px] md:overflow-y-hidden",
|
|
29349
|
-
children: /* @__PURE__ */
|
|
29350
|
-
|
|
29351
|
-
|
|
29352
|
-
|
|
29353
|
-
|
|
29354
|
-
|
|
29355
|
-
|
|
29356
|
-
|
|
29357
|
-
chat.
|
|
29358
|
-
|
|
29359
|
-
|
|
29360
|
-
|
|
29361
|
-
|
|
29362
|
-
|
|
29363
|
-
|
|
29605
|
+
children: /* @__PURE__ */ jsx(ChatDropSurface, {
|
|
29606
|
+
onFiles: pickFiles,
|
|
29607
|
+
className: "h-full",
|
|
29608
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
29609
|
+
className: "flex h-full min-h-0 flex-col",
|
|
29610
|
+
children: [header, /* @__PURE__ */ jsxs("div", {
|
|
29611
|
+
className: "flex min-h-0 flex-1",
|
|
29612
|
+
children: [(wide || threadsOpen) && /* @__PURE__ */ jsx(ChatThreadList, {
|
|
29613
|
+
threads: chat.threads,
|
|
29614
|
+
activeId: chat.convId,
|
|
29615
|
+
onOpen: (id) => {
|
|
29616
|
+
chat.open(id);
|
|
29617
|
+
setAttachments([]);
|
|
29618
|
+
setUploadError(null);
|
|
29619
|
+
setThreadsOpen(false);
|
|
29620
|
+
},
|
|
29621
|
+
onNew: startNew,
|
|
29622
|
+
className: wide ? "w-[220px] shrink-0" : "w-full"
|
|
29623
|
+
}), (wide || !threadsOpen) && conversation]
|
|
29624
|
+
})]
|
|
29625
|
+
})
|
|
29364
29626
|
})
|
|
29365
29627
|
})]
|
|
29366
29628
|
});
|
|
@@ -11,6 +11,12 @@ export interface HistoryMsg {
|
|
|
11
11
|
text: string;
|
|
12
12
|
ts: number;
|
|
13
13
|
reasoning?: string;
|
|
14
|
+
attachments?: {
|
|
15
|
+
name: string;
|
|
16
|
+
mime?: string;
|
|
17
|
+
size?: number;
|
|
18
|
+
label?: string;
|
|
19
|
+
}[];
|
|
14
20
|
}
|
|
15
21
|
export declare function threadsAction(_body: Record<string, unknown>, caller: ActionCaller): Promise<{
|
|
16
22
|
threads: ThreadSummary[];
|
package/dist/runtime/actions.js
CHANGED
|
@@ -32,6 +32,7 @@ export async function historyAction(body, caller) {
|
|
|
32
32
|
text: m.text,
|
|
33
33
|
ts: m.ts,
|
|
34
34
|
...(reasoningByBot.has(m.msgId) ? { reasoning: reasoningByBot.get(m.msgId) } : {}),
|
|
35
|
+
...(m.attachments?.length ? { attachments: m.attachments } : {}),
|
|
35
36
|
})),
|
|
36
37
|
headMsgId: visible.at(-1)?.msgId ?? null,
|
|
37
38
|
};
|
|
@@ -10,7 +10,17 @@ export function chatPrefixFor(userId) {
|
|
|
10
10
|
return `u:${userId}:`;
|
|
11
11
|
}
|
|
12
12
|
export async function recordUser(m) {
|
|
13
|
-
await putThreadMessage({
|
|
13
|
+
await putThreadMessage({
|
|
14
|
+
connector: CONNECTOR,
|
|
15
|
+
chatId: m.chatId,
|
|
16
|
+
msgId: m.msgId,
|
|
17
|
+
role: 'user',
|
|
18
|
+
sender: m.sender ?? null,
|
|
19
|
+
...(m.attachments?.length ? { attachments: m.attachments } : {}),
|
|
20
|
+
text: m.text,
|
|
21
|
+
ts: m.ts,
|
|
22
|
+
replyToId: m.replyToId,
|
|
23
|
+
});
|
|
14
24
|
}
|
|
15
25
|
export async function recordAssistant(m) {
|
|
16
26
|
if (m.botMsgId == null || !m.text.trim())
|
|
@@ -43,7 +53,14 @@ export async function buildChain(headMsgId, opts) {
|
|
|
43
53
|
if (!stored)
|
|
44
54
|
break;
|
|
45
55
|
if (stored.role !== 'reasoning') {
|
|
46
|
-
acc.push({
|
|
56
|
+
acc.push({
|
|
57
|
+
role: stored.role,
|
|
58
|
+
content: stored.text,
|
|
59
|
+
sender: stored.sender,
|
|
60
|
+
...(stored.attachments ? { attachments: stored.attachments } : {}),
|
|
61
|
+
msgId: stored.msgId,
|
|
62
|
+
ts: stored.ts,
|
|
63
|
+
});
|
|
47
64
|
}
|
|
48
65
|
cur = stored.replyToId;
|
|
49
66
|
}
|
package/dist/runtime/send.js
CHANGED
|
@@ -34,9 +34,10 @@ export async function sendAction(body, ctx, deps = {}) {
|
|
|
34
34
|
const msgId = randomUUID();
|
|
35
35
|
const botMsgId = randomUUID();
|
|
36
36
|
const nowSec = Math.floor(Date.now() / 1000);
|
|
37
|
+
const attachments = parseAttachments(body['attachments']);
|
|
37
38
|
const explicitReplyTo = typeof body['replyTo'] === 'string' && body['replyTo'] ? body['replyTo'] : null;
|
|
38
39
|
const replyTo = explicitReplyTo ?? (await history(chatId, 1)).at(-1)?.msgId ?? null;
|
|
39
|
-
await recordUser({ chatId, msgId, sender: ctx.caller.id, text, ts: nowSec, replyToId: replyTo });
|
|
40
|
+
await recordUser({ chatId, msgId, sender: ctx.caller.id, text, attachments, ts: nowSec, replyToId: replyTo });
|
|
40
41
|
const messages = await buildChain(msgId, { chatId });
|
|
41
42
|
const { connector, recorded } = makeStreamingConnector({
|
|
42
43
|
chatId,
|
|
@@ -55,3 +56,21 @@ export async function sendAction(body, ctx, deps = {}) {
|
|
|
55
56
|
}, { policy: policy() });
|
|
56
57
|
ctx.emit('done', { msgId: recorded(), parentMsgId: msgId });
|
|
57
58
|
}
|
|
59
|
+
function parseAttachments(value) {
|
|
60
|
+
if (!Array.isArray(value))
|
|
61
|
+
return undefined;
|
|
62
|
+
const refs = value.flatMap((item) => {
|
|
63
|
+
if (typeof item !== 'object' || item === null)
|
|
64
|
+
return [];
|
|
65
|
+
const r = item;
|
|
66
|
+
if (typeof r['name'] !== 'string' || !r['name'])
|
|
67
|
+
return [];
|
|
68
|
+
return [{
|
|
69
|
+
name: r['name'],
|
|
70
|
+
...(typeof r['mime'] === 'string' ? { mime: r['mime'] } : {}),
|
|
71
|
+
...(typeof r['size'] === 'number' ? { size: r['size'] } : {}),
|
|
72
|
+
...(typeof r['label'] === 'string' ? { label: r['label'] } : {}),
|
|
73
|
+
}];
|
|
74
|
+
});
|
|
75
|
+
return refs.length ? refs : undefined;
|
|
76
|
+
}
|
package/dist/schema.js
CHANGED
|
@@ -4391,11 +4391,11 @@ function resolveUnits(u) {
|
|
|
4391
4391
|
//#endregion
|
|
4392
4392
|
//#region ../sdk/src/currencies.ts
|
|
4393
4393
|
/**
|
|
4394
|
-
*
|
|
4395
|
-
*
|
|
4394
|
+
* Currencies from built-in `Intl` (ISO 4217), with NO hand list or npm dependency.
|
|
4395
|
+
* Codes come from `Intl.supportedValuesOf('currency')`; symbols/names are localized through
|
|
4396
4396
|
* `Intl.NumberFormat`/`Intl.DisplayNames`.
|
|
4397
4397
|
*/
|
|
4398
|
-
/**
|
|
4398
|
+
/** All ISO 4217 codes. Fallback: several major codes for older runtimes. */
|
|
4399
4399
|
var CURRENCY_CODES = (() => {
|
|
4400
4400
|
try {
|
|
4401
4401
|
return Intl.supportedValuesOf("currency");
|
|
@@ -4410,26 +4410,26 @@ var CURRENCY_CODES = (() => {
|
|
|
4410
4410
|
}
|
|
4411
4411
|
})();
|
|
4412
4412
|
var CODE_SET = new Set(CURRENCY_CODES);
|
|
4413
|
-
/**
|
|
4413
|
+
/** Whether an ISO 4217 code is valid. */
|
|
4414
4414
|
var isCurrencyCode = (c) => CODE_SET.has(c);
|
|
4415
4415
|
//#endregion
|
|
4416
4416
|
//#region ../sdk/src/fields/validation.ts
|
|
4417
|
-
/**
|
|
4417
|
+
/** Structured message for zod: JSON {code, params}. Decoded by mutate.ts. */
|
|
4418
4418
|
function vmsg(code, params) {
|
|
4419
4419
|
return JSON.stringify(params ? {
|
|
4420
4420
|
code,
|
|
4421
4421
|
params
|
|
4422
4422
|
} : { code });
|
|
4423
4423
|
}
|
|
4424
|
-
/** v4 error-map:
|
|
4424
|
+
/** v4 error-map: message for a missing value (formerly required_error). */
|
|
4425
4425
|
function reqErr(code = "required") {
|
|
4426
4426
|
return { error: (iss) => iss.input === void 0 ? vmsg(code) : void 0 };
|
|
4427
4427
|
}
|
|
4428
|
-
/** v4 error-map:
|
|
4428
|
+
/** v4 error-map: message for an invalid type (formerly invalid_type_error). */
|
|
4429
4429
|
function typeErr(code = "invalid_type") {
|
|
4430
4430
|
return { error: (iss) => iss.code === "invalid_type" ? vmsg(code) : void 0 };
|
|
4431
4431
|
}
|
|
4432
|
-
/** v4 error-map: required + invalid_type
|
|
4432
|
+
/** v4 error-map: required + invalid_type together (formerly required_error + invalid_type_error). */
|
|
4433
4433
|
function reqTypeErr() {
|
|
4434
4434
|
return { error: (iss) => iss.code === "invalid_type" ? iss.input === void 0 ? vmsg("required") : vmsg("invalid_type") : void 0 };
|
|
4435
4435
|
}
|
|
@@ -4447,10 +4447,10 @@ function jsonValue(raw) {
|
|
|
4447
4447
|
return raw;
|
|
4448
4448
|
}
|
|
4449
4449
|
/**
|
|
4450
|
-
*
|
|
4451
|
-
*
|
|
4452
|
-
* Single
|
|
4453
|
-
*
|
|
4450
|
+
* Factory for fields that store JSON and validate it with a nested zod schema.
|
|
4451
|
+
* Accepts a native object/array (native form state) OR a JSON string (legacy).
|
|
4452
|
+
* Single parse through jsonValue → inner.safeParse → issue `code`; an unparseable
|
|
4453
|
+
* string remains a string → code 'json'.
|
|
4454
4454
|
*/
|
|
4455
4455
|
function jsonRefined(inner, code) {
|
|
4456
4456
|
return unknown().superRefine((raw, ctx) => {
|
|
@@ -4522,16 +4522,16 @@ function normalizeOpts(rawIn) {
|
|
|
4522
4522
|
//#endregion
|
|
4523
4523
|
//#region ../sdk/src/field-presets.ts
|
|
4524
4524
|
/**
|
|
4525
|
-
*
|
|
4525
|
+
* Field presets are thin wrappers around the primitives in fields.ts.
|
|
4526
4526
|
*
|
|
4527
|
-
*
|
|
4528
|
-
*
|
|
4527
|
+
* Each preset = one kind (one widget). Presets do not accept `format`;
|
|
4528
|
+
* they are semantic types themselves. min/max/step go through `config`.
|
|
4529
4529
|
*
|
|
4530
|
-
*
|
|
4531
|
-
*
|
|
4530
|
+
* Presets are added to `f` through `composeF`, which guarantees no preset
|
|
4531
|
+
* overrides a primitive.
|
|
4532
4532
|
*
|
|
4533
|
-
*
|
|
4534
|
-
*
|
|
4533
|
+
* The cyclic import from fields.ts is safe: factories/helpers are hoisted declarations,
|
|
4534
|
+
* and presets call them only inside their function bodies.
|
|
4535
4535
|
*/
|
|
4536
4536
|
function email(raw) {
|
|
4537
4537
|
const o = normalizeOpts(raw);
|
|
@@ -4705,9 +4705,9 @@ function link(raw) {
|
|
|
4705
4705
|
}, o.multiple ?? false));
|
|
4706
4706
|
}
|
|
4707
4707
|
var TEL_RE = /^\+?[\d\s()-]{4,}$/;
|
|
4708
|
-
/** Loose URL:
|
|
4708
|
+
/** Loose URL: any scheme:// OR dotted host (optional port/path). No spaces. */
|
|
4709
4709
|
var LINK_RE = /^([a-z][a-z0-9+.-]*:\/\/\S+|[\w-]+(\.[\w-]+)+(:\d+)?(\/\S*)?)$/i;
|
|
4710
|
-
/** CSS named colors (CSS Color Module L4)
|
|
4710
|
+
/** CSS named colors (CSS Color Module L4) for f.colorname. */
|
|
4711
4711
|
var CSS_COLOR_NAMES = /* @__PURE__ */ new Set([
|
|
4712
4712
|
"aliceblue",
|
|
4713
4713
|
"antiquewhite",
|
|
@@ -4966,7 +4966,7 @@ function reminder(raw) {
|
|
|
4966
4966
|
}
|
|
4967
4967
|
var _real = real;
|
|
4968
4968
|
var _int = int;
|
|
4969
|
-
/**
|
|
4969
|
+
/** Percentage 0..100 — real with rules:{min:0,max:100}. */
|
|
4970
4970
|
function percent(o) {
|
|
4971
4971
|
return _real({
|
|
4972
4972
|
...o,
|
|
@@ -4977,7 +4977,7 @@ function percent(o) {
|
|
|
4977
4977
|
}
|
|
4978
4978
|
});
|
|
4979
4979
|
}
|
|
4980
|
-
/**
|
|
4980
|
+
/** Year — int with rules:{min:1900,max:2100}; bounds can be overridden via rules.min/max. */
|
|
4981
4981
|
function year(o) {
|
|
4982
4982
|
return _int({
|
|
4983
4983
|
...o,
|
|
@@ -6027,13 +6027,6 @@ function wrapKey(opts, meta) {
|
|
|
6027
6027
|
role: opts.role
|
|
6028
6028
|
}
|
|
6029
6029
|
};
|
|
6030
|
-
if (opts.pinned) m = {
|
|
6031
|
-
...m,
|
|
6032
|
-
hints: {
|
|
6033
|
-
...m.hints,
|
|
6034
|
-
pinned: true
|
|
6035
|
-
}
|
|
6036
|
-
};
|
|
6037
6030
|
if (opts.default !== void 0) m = {
|
|
6038
6031
|
...m,
|
|
6039
6032
|
default: opts.default
|
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-
|
|
14
|
+
load: () => import("./ChatWidget-Cew9znAf.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.
|
|
3
|
+
"version": "2.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -26,9 +26,9 @@
|
|
|
26
26
|
"test:ui": "vitest run --root ."
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@coffer-org/plugin-orchestrator": "^2.1.
|
|
30
|
-
"@coffer-org/sdk": "^2.1.
|
|
31
|
-
"@coffer-org/server": "^2.2.
|
|
29
|
+
"@coffer-org/plugin-orchestrator": "^2.1.1",
|
|
30
|
+
"@coffer-org/sdk": "^2.1.1",
|
|
31
|
+
"@coffer-org/server": "^2.2.2",
|
|
32
32
|
"react-markdown": "^10.1.0",
|
|
33
33
|
"remark-gfm": "^4.0.1"
|
|
34
34
|
},
|