@coffer-org/plugin-webchat 4.0.0 → 6.0.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-Bs8zrQVZ.js → ChatWidget-DFVDexpD.js} +77 -132
- package/dist/index.js +1 -1
- package/dist/runtime/format.d.ts +1 -0
- package/dist/runtime/format.js +5 -0
- package/dist/runtime/send.js +4 -4
- package/dist/schema.js +136 -129
- package/dist/web.js +1 -1
- package/package.json +3 -3
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as __fedReact from "react";
|
|
2
|
-
import { Dialog, DialogContent,
|
|
2
|
+
import { Dialog, DialogContent, classifySiteHref, uploadFile, useMinWidth } from "@coffer-org/web-ui";
|
|
3
3
|
import { createContext, createElement, forwardRef, memo, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react";
|
|
4
4
|
import { useLocation, useNavigate } from "react-router-dom";
|
|
5
5
|
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -23722,54 +23722,6 @@ function remarkGfm(options) {
|
|
|
23722
23722
|
toMarkdownExtensions.push(gfmToMarkdown(settings));
|
|
23723
23723
|
}
|
|
23724
23724
|
//#endregion
|
|
23725
|
-
//#region src/ui/internal-link.ts
|
|
23726
|
-
var SEGMENT = /^[A-Za-z0-9._~%-]+$/;
|
|
23727
|
-
/** Returns captured ':param' values on a match, or null. '*' is legal only as the
|
|
23728
|
-
* last pattern segment, so this branch may return immediately without reading got to the end. */
|
|
23729
|
-
function matchesPattern(pathname, pattern) {
|
|
23730
|
-
const got = pathname.split("/").filter(Boolean);
|
|
23731
|
-
const want = pattern.split("/").filter(Boolean);
|
|
23732
|
-
const captured = {};
|
|
23733
|
-
for (let i = 0; i < want.length; i++) {
|
|
23734
|
-
const w = want[i];
|
|
23735
|
-
if (w === "*") return got.length >= i ? captured : null;
|
|
23736
|
-
const g = got[i];
|
|
23737
|
-
if (g === void 0) return null;
|
|
23738
|
-
if (w.startsWith(":")) {
|
|
23739
|
-
if (!SEGMENT.test(g)) return null;
|
|
23740
|
-
captured[w.slice(1)] = g;
|
|
23741
|
-
} else if (w !== g) return null;
|
|
23742
|
-
}
|
|
23743
|
-
return got.length === want.length ? captured : null;
|
|
23744
|
-
}
|
|
23745
|
-
function classifyHref(href, origin, routes, pairs = []) {
|
|
23746
|
-
const raw = href.trim();
|
|
23747
|
-
if (!raw || raw.startsWith("//")) return { kind: "text" };
|
|
23748
|
-
if (!raw.startsWith("/") && !/^https?:\/\//i.test(raw)) return { kind: "text" };
|
|
23749
|
-
let url;
|
|
23750
|
-
try {
|
|
23751
|
-
url = new URL(raw, origin);
|
|
23752
|
-
} catch {
|
|
23753
|
-
return { kind: "text" };
|
|
23754
|
-
}
|
|
23755
|
-
if (url.origin !== origin) return {
|
|
23756
|
-
kind: "external",
|
|
23757
|
-
href: url.href
|
|
23758
|
-
};
|
|
23759
|
-
for (const r of routes) {
|
|
23760
|
-
const captured = matchesPattern(url.pathname, r.pattern);
|
|
23761
|
-
if (!captured) continue;
|
|
23762
|
-
if (pairs.length && captured["library"] !== void 0 && captured["shelf"] !== void 0) {
|
|
23763
|
-
if (!pairs.some((p) => p.library === captured["library"] && p.shelf === captured["shelf"])) return { kind: "text" };
|
|
23764
|
-
}
|
|
23765
|
-
return {
|
|
23766
|
-
kind: "internal",
|
|
23767
|
-
to: `${url.pathname}${url.search}${url.hash}`
|
|
23768
|
-
};
|
|
23769
|
-
}
|
|
23770
|
-
return { kind: "text" };
|
|
23771
|
-
}
|
|
23772
|
-
//#endregion
|
|
23773
23725
|
//#region src/ui/chat-nav.tsx
|
|
23774
23726
|
/** What to do with the widget when the user follows a link from a response.
|
|
23775
23727
|
* ChatWidget supplies the callback (it owns mode/open), avoiding a prop
|
|
@@ -23811,8 +23763,7 @@ function ChatTable(props) {
|
|
|
23811
23763
|
function ChatLink({ href, children }) {
|
|
23812
23764
|
const onNavigate = useChatNav();
|
|
23813
23765
|
const navigate = useNavigate();
|
|
23814
|
-
const
|
|
23815
|
-
const kind = classifyHref(href ?? "", window.location.origin, routes, getSiteShelves());
|
|
23766
|
+
const kind = classifySiteHref(href ?? "");
|
|
23816
23767
|
if (kind.kind === "internal") return /* @__PURE__ */ jsx("a", {
|
|
23817
23768
|
href: kind.to,
|
|
23818
23769
|
onClick: (event) => {
|
|
@@ -23966,15 +23917,6 @@ function starLadder(rows, multi) {
|
|
|
23966
23917
|
}
|
|
23967
23918
|
return byLevel.every((r) => r !== void 0) ? byLevel : null;
|
|
23968
23919
|
}
|
|
23969
|
-
/** The stars as a picture: `filled` of `max` lit. Same palette as the `f.rating` widget
|
|
23970
|
-
* (filled yellow, empty at border colour), so the gesture reads the same in both places. */
|
|
23971
|
-
function Stars({ filled, max, size }) {
|
|
23972
|
-
return /* @__PURE__ */ jsx(Fragment, { children: Array.from({ length: max }, (_, i) => /* @__PURE__ */ jsx(Star, {
|
|
23973
|
-
size,
|
|
23974
|
-
"aria-hidden": true,
|
|
23975
|
-
className: i < filled ? "shrink-0 fill-yellow-400 text-yellow-400" : "shrink-0 text-border"
|
|
23976
|
-
}, i)) });
|
|
23977
|
-
}
|
|
23978
23920
|
/** Flatten the catalog to one list: with a single registered agent the agent
|
|
23979
23921
|
* level is noise, and with several the agent name simply prefixes its presets. */
|
|
23980
23922
|
function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
|
|
@@ -24001,9 +23943,6 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
|
|
|
24001
23943
|
document.removeEventListener("keydown", onKeyDown);
|
|
24002
23944
|
};
|
|
24003
23945
|
}, [open]);
|
|
24004
|
-
useEffect(() => {
|
|
24005
|
-
if (!open) setPreview(null);
|
|
24006
|
-
}, [open]);
|
|
24007
23946
|
const multi = agents.length > 1;
|
|
24008
23947
|
const rows = agents.flatMap((a) => a.presets.map((p) => ({
|
|
24009
23948
|
agentId: a.id,
|
|
@@ -24017,73 +23956,58 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
|
|
|
24017
23956
|
if (rows.length === 0) return null;
|
|
24018
23957
|
const current = rows.find((r) => r.agentId === selection.agentId && r.presetId === selection.presetId) ?? rows.find((r) => r.agentId === defaultAgentId && r.isDefault) ?? rows.find((r) => r.isDefault) ?? rows[0];
|
|
24019
23958
|
const ladder = starLadder(rows, multi);
|
|
24020
|
-
|
|
24021
|
-
|
|
24022
|
-
|
|
24023
|
-
|
|
24024
|
-
|
|
24025
|
-
|
|
24026
|
-
|
|
23959
|
+
if (ladder) {
|
|
23960
|
+
const level = ladder.indexOf(current) + 1;
|
|
23961
|
+
const shown = preview ?? level;
|
|
23962
|
+
const levelName = (row, n) => `${row.label} · ${t("webchat.level", {
|
|
23963
|
+
value: n,
|
|
23964
|
+
max: ladder.length
|
|
23965
|
+
})}`;
|
|
23966
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
23967
|
+
"aria-label": t("webchat.agentPicker"),
|
|
23968
|
+
className: "flex shrink-0 items-center",
|
|
23969
|
+
onPointerLeave: () => setPreview(null),
|
|
23970
|
+
children: ladder.map((r, i) => {
|
|
23971
|
+
const n = i + 1;
|
|
23972
|
+
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
|
|
23973
|
+
type: "button",
|
|
23974
|
+
...r === current ? { "aria-current": true } : {},
|
|
23975
|
+
"aria-label": levelName(r, n),
|
|
23976
|
+
className: "flex rounded p-1 focus-visible:outline-2 focus-visible:outline-accent",
|
|
23977
|
+
onPointerEnter: () => setPreview(n),
|
|
23978
|
+
onFocus: () => setPreview(n),
|
|
23979
|
+
onBlur: () => setPreview(null),
|
|
23980
|
+
onClick: () => onChange({
|
|
23981
|
+
agentId: r.agentId,
|
|
23982
|
+
presetId: r.presetId
|
|
23983
|
+
}),
|
|
23984
|
+
children: /* @__PURE__ */ jsx(Star, {
|
|
23985
|
+
size: 16,
|
|
23986
|
+
"aria-hidden": true,
|
|
23987
|
+
className: n <= shown ? "fill-yellow-400 text-yellow-400" : "text-border"
|
|
23988
|
+
})
|
|
23989
|
+
}) }, `${r.agentId}/${r.presetId}`);
|
|
23990
|
+
})
|
|
23991
|
+
});
|
|
23992
|
+
}
|
|
24027
23993
|
return /* @__PURE__ */ jsxs("div", {
|
|
24028
23994
|
className: "relative",
|
|
24029
23995
|
ref: rootRef,
|
|
24030
|
-
children: [/* @__PURE__ */
|
|
23996
|
+
children: [/* @__PURE__ */ jsxs("button", {
|
|
24031
23997
|
ref: triggerRef,
|
|
24032
23998
|
type: "button",
|
|
24033
23999
|
"aria-haspopup": "menu",
|
|
24034
24000
|
"aria-expanded": open,
|
|
24035
24001
|
className: "flex items-center gap-1 rounded-md px-2 py-1 text-xs text-muted hover:text-text focus-visible:outline-2 focus-visible:outline-accent",
|
|
24036
24002
|
onClick: () => setOpen((v) => !v),
|
|
24037
|
-
|
|
24038
|
-
children: ladder ? /* @__PURE__ */ jsx("span", {
|
|
24039
|
-
className: "flex items-center gap-0.5",
|
|
24040
|
-
children: /* @__PURE__ */ jsx(Stars, {
|
|
24041
|
-
filled: level,
|
|
24042
|
-
max: ladder.length,
|
|
24043
|
-
size: 14
|
|
24044
|
-
})
|
|
24045
|
-
}) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("span", {
|
|
24003
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
24046
24004
|
className: "max-w-40 truncate",
|
|
24047
24005
|
children: current.label
|
|
24048
24006
|
}), /* @__PURE__ */ jsx(ChevronDown, {
|
|
24049
24007
|
size: 12,
|
|
24050
24008
|
className: "shrink-0"
|
|
24051
|
-
})] })
|
|
24052
|
-
}), open && (ladder ? /* @__PURE__ */ jsxs("div", {
|
|
24053
|
-
className: "absolute bottom-full left-0 z-10 mb-1 rounded-md border border-border bg-elevated px-2 py-1.5 shadow-lg",
|
|
24054
|
-
children: [/* @__PURE__ */ jsx("ul", {
|
|
24055
|
-
"aria-label": t("webchat.agentPicker"),
|
|
24056
|
-
className: "flex items-center",
|
|
24057
|
-
onPointerLeave: () => setPreview(null),
|
|
24058
|
-
children: ladder.map((r, i) => {
|
|
24059
|
-
const n = i + 1;
|
|
24060
|
-
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx("button", {
|
|
24061
|
-
type: "button",
|
|
24062
|
-
...r === current ? { "aria-current": true } : {},
|
|
24063
|
-
"aria-label": levelName(r, n),
|
|
24064
|
-
className: "flex p-0.5 focus-visible:outline-2 focus-visible:outline-accent",
|
|
24065
|
-
onPointerEnter: () => setPreview(n),
|
|
24066
|
-
onFocus: () => setPreview(n),
|
|
24067
|
-
onBlur: () => setPreview(null),
|
|
24068
|
-
onClick: () => {
|
|
24069
|
-
setOpen(false);
|
|
24070
|
-
onChange({
|
|
24071
|
-
agentId: r.agentId,
|
|
24072
|
-
presetId: r.presetId
|
|
24073
|
-
});
|
|
24074
|
-
},
|
|
24075
|
-
children: /* @__PURE__ */ jsx(Star, {
|
|
24076
|
-
size: 20,
|
|
24077
|
-
"aria-hidden": true,
|
|
24078
|
-
className: n <= shown ? "fill-yellow-400 text-yellow-400" : "text-border"
|
|
24079
|
-
})
|
|
24080
|
-
}) }, `${r.agentId}/${r.presetId}`);
|
|
24081
|
-
})
|
|
24082
|
-
}), /* @__PURE__ */ jsx("p", {
|
|
24083
|
-
className: "mt-0.5 text-center text-xs text-muted",
|
|
24084
|
-
children: shownRow?.hint || shownRow?.label
|
|
24085
24009
|
})]
|
|
24086
|
-
})
|
|
24010
|
+
}), open && /* @__PURE__ */ jsx("ul", {
|
|
24087
24011
|
"aria-label": t("webchat.agentPicker"),
|
|
24088
24012
|
className: "absolute bottom-full left-0 z-10 mb-1 max-h-64 w-64 max-w-[calc(100vw-2rem)] overflow-y-auto rounded-md border border-border bg-elevated py-1 shadow-lg",
|
|
24089
24013
|
children: rows.map((r) => {
|
|
@@ -24116,7 +24040,7 @@ function AgentPicker({ agents, defaultAgentId, selection, onChange }) {
|
|
|
24116
24040
|
]
|
|
24117
24041
|
}) }, `${r.agentId}/${r.presetId}`);
|
|
24118
24042
|
})
|
|
24119
|
-
})
|
|
24043
|
+
})]
|
|
24120
24044
|
});
|
|
24121
24045
|
}
|
|
24122
24046
|
//#endregion
|
|
@@ -24363,12 +24287,31 @@ function ChatDropSurface({ children, onFiles, className }) {
|
|
|
24363
24287
|
})]
|
|
24364
24288
|
});
|
|
24365
24289
|
}
|
|
24366
|
-
/**
|
|
24367
|
-
*
|
|
24368
|
-
*
|
|
24369
|
-
*
|
|
24370
|
-
function
|
|
24371
|
-
return
|
|
24290
|
+
/** The states the VIEWPORT offers, ordered from least to most expanded. `panel` — the 380px
|
|
24291
|
+
* floating window — exists only where it does not obstruct the page: on a narrow screen the
|
|
24292
|
+
* widget has exactly two states, the bubble and the full sheet. Every mode transition is a
|
|
24293
|
+
* move along this ladder, so no call site can put the widget into a mode its screen lacks. */
|
|
24294
|
+
function chatModes(wide) {
|
|
24295
|
+
return wide ? [
|
|
24296
|
+
"bubble",
|
|
24297
|
+
"panel",
|
|
24298
|
+
"modal"
|
|
24299
|
+
] : ["bubble", "modal"];
|
|
24300
|
+
}
|
|
24301
|
+
/** Where the widget rests once it is opened — expanded, but as little as the viewport allows.
|
|
24302
|
+
* Also where it returns after following a link from a response: on a wide screen the panel
|
|
24303
|
+
* does not obstruct the page the user just navigated to, while on a narrow one the sheet is
|
|
24304
|
+
* the only visible state, so the active conversation survives the navigation either way. */
|
|
24305
|
+
function restingMode(wide) {
|
|
24306
|
+
return chatModes(wide)[1];
|
|
24307
|
+
}
|
|
24308
|
+
/** One step along the ladder, clamped at both ends: `-1` is what collapse, Esc and a dialog
|
|
24309
|
+
* dismiss do, `+1` is expand. A mode this viewport no longer offers is re-homed by the effect
|
|
24310
|
+
* in the widget, not by stepping — the step always returns a mode from the current ladder. */
|
|
24311
|
+
function stepMode(mode, wide, delta) {
|
|
24312
|
+
const ladder = chatModes(wide);
|
|
24313
|
+
const next = ladder.indexOf(mode) + delta;
|
|
24314
|
+
return ladder[Math.min(ladder.length - 1, Math.max(0, next))];
|
|
24372
24315
|
}
|
|
24373
24316
|
function ChatWidget() {
|
|
24374
24317
|
const { t } = useTranslation();
|
|
@@ -24382,20 +24325,22 @@ function ChatWidget() {
|
|
|
24382
24325
|
const [uploadError, setUploadError] = useState(null);
|
|
24383
24326
|
const [retryFiles, setRetryFiles] = useState([]);
|
|
24384
24327
|
const wide = useMinWidth("sm", 640);
|
|
24385
|
-
const wideNav = useMinWidth("nav", 768);
|
|
24386
24328
|
const onChatNavigate = useCallback(() => {
|
|
24387
|
-
setMode(
|
|
24388
|
-
}, [
|
|
24329
|
+
setMode(restingMode(wide));
|
|
24330
|
+
}, [wide]);
|
|
24389
24331
|
const expandBtnRef = useRef(null);
|
|
24390
24332
|
const prevModeRef = useRef("bubble");
|
|
24391
24333
|
useEffect(() => {
|
|
24392
24334
|
if (mode !== "modal") return;
|
|
24393
24335
|
const onKey = (e) => {
|
|
24394
|
-
if (e.key === "Escape") setMode("
|
|
24336
|
+
if (e.key === "Escape") setMode(stepMode("modal", wide, -1));
|
|
24395
24337
|
};
|
|
24396
24338
|
window.addEventListener("keydown", onKey);
|
|
24397
24339
|
return () => window.removeEventListener("keydown", onKey);
|
|
24398
|
-
}, [mode]);
|
|
24340
|
+
}, [mode, wide]);
|
|
24341
|
+
useEffect(() => {
|
|
24342
|
+
if (!chatModes(wide).includes(mode)) setMode(restingMode(wide));
|
|
24343
|
+
}, [mode, wide]);
|
|
24399
24344
|
useEffect(() => {
|
|
24400
24345
|
if (mode === "panel" && prevModeRef.current === "modal") expandBtnRef.current?.focus();
|
|
24401
24346
|
if (mode !== "modal") setThreadsOpen(false);
|
|
@@ -24498,11 +24443,11 @@ function ChatWidget() {
|
|
|
24498
24443
|
onClick: startNew,
|
|
24499
24444
|
children: /* @__PURE__ */ jsx(Plus, { size: 16 })
|
|
24500
24445
|
}),
|
|
24501
|
-
/* @__PURE__ */ jsx("button", {
|
|
24446
|
+
chatModes(wide).includes("panel") && /* @__PURE__ */ jsx("button", {
|
|
24502
24447
|
ref: expandBtnRef,
|
|
24503
24448
|
"aria-label": mode === "modal" ? t("webchat.collapse") : t("webchat.expand"),
|
|
24504
24449
|
className: "rounded p-1 hover:bg-elevated",
|
|
24505
|
-
onClick: () => setMode(mode === "modal" ?
|
|
24450
|
+
onClick: () => setMode(stepMode(mode, wide, mode === "modal" ? -1 : 1)),
|
|
24506
24451
|
children: mode === "modal" ? /* @__PURE__ */ jsx(Minimize2, { size: 16 }) : /* @__PURE__ */ jsx(Maximize2, { size: 16 })
|
|
24507
24452
|
}),
|
|
24508
24453
|
/* @__PURE__ */ jsx("button", {
|
|
@@ -24558,7 +24503,7 @@ function ChatWidget() {
|
|
|
24558
24503
|
});
|
|
24559
24504
|
if (mode === "bubble") return /* @__PURE__ */ jsx("button", {
|
|
24560
24505
|
"aria-label": t("webchat.open"),
|
|
24561
|
-
onClick: () => setMode(wide
|
|
24506
|
+
onClick: () => setMode(restingMode(wide)),
|
|
24562
24507
|
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",
|
|
24563
24508
|
children: /* @__PURE__ */ jsx(MessageCircle, { size: 24 })
|
|
24564
24509
|
});
|
|
@@ -24570,7 +24515,7 @@ function ChatWidget() {
|
|
|
24570
24515
|
return /* @__PURE__ */ jsxs(Dialog, {
|
|
24571
24516
|
open: true,
|
|
24572
24517
|
modal: false,
|
|
24573
|
-
onOpenChange: (open) => !open && setMode("
|
|
24518
|
+
onOpenChange: (open) => !open && setMode(stepMode("modal", wide, -1)),
|
|
24574
24519
|
children: [/* @__PURE__ */ jsx("div", {
|
|
24575
24520
|
"data-chat-backdrop": true,
|
|
24576
24521
|
"aria-hidden": "true",
|
|
@@ -24605,4 +24550,4 @@ function ChatWidget() {
|
|
|
24605
24550
|
});
|
|
24606
24551
|
}
|
|
24607
24552
|
//#endregion
|
|
24608
|
-
export { ChatWidget, ChatWidget as default,
|
|
24553
|
+
export { ChatWidget, ChatWidget as default, chatModes, restingMode, stepMode };
|
package/dist/index.js
CHANGED
|
@@ -12,7 +12,7 @@ export default definePlugin({
|
|
|
12
12
|
label: 'webchat.settings.reasoning_display',
|
|
13
13
|
default: 'live',
|
|
14
14
|
strict: true,
|
|
15
|
-
|
|
15
|
+
noSearch: true,
|
|
16
16
|
options: [
|
|
17
17
|
{ value: 'live', title: 'webchat.settings.reasoning_display_live' },
|
|
18
18
|
{ value: 'off', title: 'webchat.settings.reasoning_display_off' },
|
package/dist/runtime/format.d.ts
CHANGED
package/dist/runtime/format.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { frontendInstructions } from '@coffer-org/server/frontend-agent';
|
|
1
2
|
export const WEB_FORMAT = [
|
|
2
3
|
'CRITICAL OUTPUT FORMAT — MARKDOWN ONLY. Format the reply strictly as Markdown (GFM). NEVER emit raw HTML tags — they are stripped before rendering and their content may be lost.',
|
|
3
4
|
'Inline: **bold**, *italic*, `inline code`, ~~strikethrough~~. Links: [text](https://…).',
|
|
@@ -8,3 +9,7 @@ export const WEB_FORMAT = [
|
|
|
8
9
|
'The reply is read in a narrow chat panel (~360px) — keep it short and skimmable: short paragraphs, lists over prose, narrow tables.',
|
|
9
10
|
'Use Unicode symbols and emoji to structure replies: arrows (→), status marks (✅ ⚠️ ❌ ⏳), topic emoji (📅 💰 🏥 🛒 🌱 …).',
|
|
10
11
|
].join('\n');
|
|
12
|
+
export async function webChannelSystem() {
|
|
13
|
+
const site = await frontendInstructions('');
|
|
14
|
+
return [WEB_FORMAT, site].filter(Boolean).join('\n\n');
|
|
15
|
+
}
|
package/dist/runtime/send.js
CHANGED
|
@@ -3,7 +3,7 @@ import { handleIncoming, liveAgentId } from '@coffer-org/server/orchestrator';
|
|
|
3
3
|
import { chatIdFor, recordUser, buildChain, history, readSelectionForTurn } from "./chain-store.js";
|
|
4
4
|
import { policy } from "./config.js";
|
|
5
5
|
import { makeStreamingConnector } from "./connector.js";
|
|
6
|
-
import {
|
|
6
|
+
import { webChannelSystem } from "./format.js";
|
|
7
7
|
import { loadReasoningDisplay } from "./settings.js";
|
|
8
8
|
export function pageContext(ctx) {
|
|
9
9
|
if (typeof ctx !== 'object' || ctx === null)
|
|
@@ -47,11 +47,11 @@ export async function sendAction(body, ctx, deps = {}) {
|
|
|
47
47
|
emit: ctx.emit,
|
|
48
48
|
display: await loadReasoningDisplay(),
|
|
49
49
|
});
|
|
50
|
-
const
|
|
50
|
+
const turnContext = pageContext(body['context']);
|
|
51
51
|
await doHandle(connector, {
|
|
52
52
|
connectorId: 'webchat',
|
|
53
|
-
channelSystem:
|
|
54
|
-
...(
|
|
53
|
+
channelSystem: await webChannelSystem(),
|
|
54
|
+
...(turnContext ? { turnContext } : {}),
|
|
55
55
|
...(agentId ? { agentId } : {}),
|
|
56
56
|
...(selection.presetId ? { presetId: selection.presetId } : {}),
|
|
57
57
|
chatId,
|
package/dist/schema.js
CHANGED
|
@@ -4082,6 +4082,23 @@ function typeErr(code = "invalid_type") {
|
|
|
4082
4082
|
function reqTypeErr() {
|
|
4083
4083
|
return { error: (iss) => iss.code === "invalid_type" ? iss.input === void 0 ? vmsg("required") : vmsg("invalid_type") : void 0 };
|
|
4084
4084
|
}
|
|
4085
|
+
/**
|
|
4086
|
+
* A string schema whose CONTENT checks only speak once the value is known to be a string.
|
|
4087
|
+
*
|
|
4088
|
+
* zod's length checks are not type-guarded: `min`/`max` read `input.length`, and an ARRAY has
|
|
4089
|
+
* one. So `z.string().min(1)` answers `[]` with `invalid_type` AND `too_small` — two messages
|
|
4090
|
+
* about a single wrongness, both of which reach the user, since `mutate.ts` and `extend-io.ts`
|
|
4091
|
+
* turn every issue into its own `ValidationError`. (A number answers with `invalid_type`
|
|
4092
|
+
* alone, having no `length` to read — so the redundancy was array-shaped and invisible until
|
|
4093
|
+
* the golden matrix started recording the whole issue list instead of `issues[0]`.)
|
|
4094
|
+
*
|
|
4095
|
+
* Piping puts the type question first and alone: `add` never runs on a non-string. Pass the
|
|
4096
|
+
* content checks as a builder rather than chaining them onto the result, because a `ZodPipe`
|
|
4097
|
+
* has no `.min`/`.max`/`.regex` to chain.
|
|
4098
|
+
*/
|
|
4099
|
+
function stringContent(add) {
|
|
4100
|
+
return string$1(reqTypeErr()).pipe(add(string$1()));
|
|
4101
|
+
}
|
|
4085
4102
|
/** Parse a JSON string, else return the native value (object/array) as-is.
|
|
4086
4103
|
* Tolerant input for native form-state AND legacy JSON-string payloads.
|
|
4087
4104
|
* A string that fails JSON.parse is returned unchanged — callers detect
|
|
@@ -4337,6 +4354,13 @@ function wrapKey(key, opts, meta) {
|
|
|
4337
4354
|
editor: opts.editor
|
|
4338
4355
|
}
|
|
4339
4356
|
};
|
|
4357
|
+
if (opts.activate) m = {
|
|
4358
|
+
...m,
|
|
4359
|
+
hints: {
|
|
4360
|
+
...m.hints,
|
|
4361
|
+
activate: opts.activate
|
|
4362
|
+
}
|
|
4363
|
+
};
|
|
4340
4364
|
if (opts.span !== void 0) m = {
|
|
4341
4365
|
...m,
|
|
4342
4366
|
hints: {
|
|
@@ -4709,18 +4733,37 @@ var UNITS_MAP = {
|
|
|
4709
4733
|
label: "core.units.ppm"
|
|
4710
4734
|
}]
|
|
4711
4735
|
};
|
|
4736
|
+
/**
|
|
4737
|
+
* Resolve a unit spec — a named scale or an explicit option list — to its options.
|
|
4738
|
+
*
|
|
4739
|
+
* Throws rather than returning `undefined`, because both ways of reaching `undefined` here
|
|
4740
|
+
* produce a field that CONSTRUCTS and then crashes when someone tries to save through it:
|
|
4741
|
+
* `measured`'s unit role closes over these options and calls `.some(…)` on them inside its
|
|
4742
|
+
* refine, so a missing spec or a typo'd scale name turns a user's save into a
|
|
4743
|
+
* `Cannot read properties of undefined` instead of a validation message. The golden matrix
|
|
4744
|
+
* recorded exactly that for `f.measured({})`.
|
|
4745
|
+
*
|
|
4746
|
+
* A declaration error belongs at declaration time, where the plugin author sees it and the
|
|
4747
|
+
* message can name the scale they meant — the same reason the host-services seam fails at
|
|
4748
|
+
* registration rather than at first render.
|
|
4749
|
+
*/
|
|
4712
4750
|
function resolveUnits(u) {
|
|
4713
|
-
|
|
4751
|
+
if (typeof u === "string") {
|
|
4752
|
+
const known = UNITS_MAP[u];
|
|
4753
|
+
if (!known) throw new Error(`units: unknown scale '${u}' (known: ${Object.keys(UNITS_MAP).sort().join(", ")})`);
|
|
4754
|
+
return known;
|
|
4755
|
+
}
|
|
4756
|
+
if (!Array.isArray(u)) throw new Error("units: a unit spec is required — a named scale, or an explicit list of options");
|
|
4757
|
+
return u;
|
|
4714
4758
|
}
|
|
4715
4759
|
//#endregion
|
|
4716
4760
|
//#region ../sdk/src/fields/normalize.ts
|
|
4717
4761
|
function normalizeOpts(rawIn) {
|
|
4718
4762
|
const raw = rawIn;
|
|
4763
|
+
if (raw["view"] !== void 0) throw new Error("[normalizeOpts] `view` is gone — presentational options live in `ui: {}` (see CLAUDE.md § field author options and the field-types skill).");
|
|
4764
|
+
if (raw.ui?.["agent"] !== void 0) throw new Error("[normalizeOpts] `agent` is not presentation — declare it at the top level of the field call, not in `ui: {}`.");
|
|
4719
4765
|
const r = raw.rules ?? {};
|
|
4720
|
-
const v = {
|
|
4721
|
-
...raw.view ?? {},
|
|
4722
|
-
...raw.ui ?? {}
|
|
4723
|
-
};
|
|
4766
|
+
const v = raw.ui ?? {};
|
|
4724
4767
|
const config = {};
|
|
4725
4768
|
if (r.min != null && typeof r.min === "number") config.min = r.min;
|
|
4726
4769
|
if (r.max != null && typeof r.max === "number") config.max = r.max;
|
|
@@ -4729,7 +4772,7 @@ function normalizeOpts(rawIn) {
|
|
|
4729
4772
|
if (r.messageKey != null) config.messageKey = r.messageKey;
|
|
4730
4773
|
if (r.granularity != null) config.granularity = r.granularity;
|
|
4731
4774
|
if (r.language != null) config.language = r.language;
|
|
4732
|
-
const { rules: _r,
|
|
4775
|
+
const { rules: _r, ui: _ui, ...flat } = raw;
|
|
4733
4776
|
const result = {
|
|
4734
4777
|
...flat,
|
|
4735
4778
|
...Object.keys(config).length ? { config } : {},
|
|
@@ -4744,7 +4787,6 @@ function normalizeOpts(rawIn) {
|
|
|
4744
4787
|
step: r.step,
|
|
4745
4788
|
hidden: v.hidden,
|
|
4746
4789
|
noEditControl: v.noEditControl,
|
|
4747
|
-
noSearch: v.noSearch,
|
|
4748
4790
|
display: v.display,
|
|
4749
4791
|
kind: v.kind,
|
|
4750
4792
|
icon: v.icon,
|
|
@@ -4752,7 +4794,12 @@ function normalizeOpts(rawIn) {
|
|
|
4752
4794
|
...v.keyLabel !== void 0 ? { keyLabel: v.keyLabel } : {},
|
|
4753
4795
|
...v.valueLabel !== void 0 ? { valueLabel: v.valueLabel } : {},
|
|
4754
4796
|
compareWith: v.compareWith,
|
|
4755
|
-
span: v.span
|
|
4797
|
+
span: v.span,
|
|
4798
|
+
emphasis: v.emphasis,
|
|
4799
|
+
noLabel: v.noLabel,
|
|
4800
|
+
role: v.role,
|
|
4801
|
+
editor: v.editor,
|
|
4802
|
+
activate: v.activate
|
|
4756
4803
|
};
|
|
4757
4804
|
return Object.fromEntries(Object.entries(result).filter(([, val]) => val !== void 0));
|
|
4758
4805
|
}
|
|
@@ -5002,7 +5049,7 @@ function internalApiToken(o) {
|
|
|
5002
5049
|
label: o.label,
|
|
5003
5050
|
multiple: true,
|
|
5004
5051
|
rules: { unique: ["name"] },
|
|
5005
|
-
|
|
5052
|
+
ui: { kind: "internalApiToken" },
|
|
5006
5053
|
fields: {
|
|
5007
5054
|
name: string({}),
|
|
5008
5055
|
token: password({}),
|
|
@@ -5028,7 +5075,7 @@ function internalOauthGrants(o) {
|
|
|
5028
5075
|
scope: "nest",
|
|
5029
5076
|
label: o.label,
|
|
5030
5077
|
multiple: true,
|
|
5031
|
-
|
|
5078
|
+
ui: { kind: "internalOauthGrants" },
|
|
5032
5079
|
fields: {
|
|
5033
5080
|
clientName: string({}),
|
|
5034
5081
|
createdAt: string({}),
|
|
@@ -5655,10 +5702,8 @@ function country(raw) {
|
|
|
5655
5702
|
...raw,
|
|
5656
5703
|
options: "countries",
|
|
5657
5704
|
strict: true,
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
noSearch: true
|
|
5661
|
-
}
|
|
5705
|
+
ui: { ...raw.ui ?? {} },
|
|
5706
|
+
noSearch: true
|
|
5662
5707
|
});
|
|
5663
5708
|
}
|
|
5664
5709
|
/**
|
|
@@ -5682,10 +5727,8 @@ function currency(raw) {
|
|
|
5682
5727
|
...raw,
|
|
5683
5728
|
options: "currencies",
|
|
5684
5729
|
strict: true,
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
noSearch: true
|
|
5688
|
-
}
|
|
5730
|
+
ui: { ...raw.ui ?? {} },
|
|
5731
|
+
noSearch: true
|
|
5689
5732
|
});
|
|
5690
5733
|
}
|
|
5691
5734
|
/**
|
|
@@ -5845,7 +5888,7 @@ function bento(opts) {
|
|
|
5845
5888
|
label: opts.label,
|
|
5846
5889
|
icon: opts.icon,
|
|
5847
5890
|
fields,
|
|
5848
|
-
|
|
5891
|
+
ui: { kind: "bento" }
|
|
5849
5892
|
}),
|
|
5850
5893
|
view: {
|
|
5851
5894
|
kind: "bento",
|
|
@@ -5874,7 +5917,7 @@ function split(opts) {
|
|
|
5874
5917
|
return {
|
|
5875
5918
|
...group({
|
|
5876
5919
|
fields: merge("split", opts.columns[0], opts.columns[1]),
|
|
5877
|
-
|
|
5920
|
+
ui: { kind: "split" }
|
|
5878
5921
|
}),
|
|
5879
5922
|
view: {
|
|
5880
5923
|
kind: "split",
|
|
@@ -5898,7 +5941,7 @@ function grid(opts) {
|
|
|
5898
5941
|
label: opts.label,
|
|
5899
5942
|
icon: opts.icon,
|
|
5900
5943
|
fields: opts.fields,
|
|
5901
|
-
|
|
5944
|
+
ui: { kind: "grid" }
|
|
5902
5945
|
}),
|
|
5903
5946
|
view: {
|
|
5904
5947
|
kind: "grid",
|
|
@@ -5923,7 +5966,7 @@ function spread(opts) {
|
|
|
5923
5966
|
return {
|
|
5924
5967
|
...group({
|
|
5925
5968
|
fields: merge("spread", visual, quote, opts.facts),
|
|
5926
|
-
|
|
5969
|
+
ui: { kind: "spread" }
|
|
5927
5970
|
}),
|
|
5928
5971
|
view: {
|
|
5929
5972
|
kind: "spread",
|
|
@@ -5948,7 +5991,7 @@ function aside(opts) {
|
|
|
5948
5991
|
return {
|
|
5949
5992
|
...group({
|
|
5950
5993
|
fields: merge("aside", opts.fields, opts.aside),
|
|
5951
|
-
|
|
5994
|
+
ui: { kind: "aside" }
|
|
5952
5995
|
}),
|
|
5953
5996
|
view: {
|
|
5954
5997
|
kind: "aside",
|
|
@@ -5980,7 +6023,7 @@ function tabs(opts) {
|
|
|
5980
6023
|
return {
|
|
5981
6024
|
...group({
|
|
5982
6025
|
fields,
|
|
5983
|
-
|
|
6026
|
+
ui: { kind: "tabs" }
|
|
5984
6027
|
}),
|
|
5985
6028
|
view: {
|
|
5986
6029
|
kind: "tabs",
|
|
@@ -6012,7 +6055,7 @@ function accordion(opts) {
|
|
|
6012
6055
|
return {
|
|
6013
6056
|
...group({
|
|
6014
6057
|
fields,
|
|
6015
|
-
|
|
6058
|
+
ui: { kind: "accordion" }
|
|
6016
6059
|
}),
|
|
6017
6060
|
view: {
|
|
6018
6061
|
kind: "accordion",
|
|
@@ -6042,7 +6085,7 @@ function prose(opts) {
|
|
|
6042
6085
|
label: opts.label,
|
|
6043
6086
|
icon: opts.icon,
|
|
6044
6087
|
fields: opts.fields,
|
|
6045
|
-
|
|
6088
|
+
ui: { kind: "prose" }
|
|
6046
6089
|
}),
|
|
6047
6090
|
view: {
|
|
6048
6091
|
kind: "prose",
|
|
@@ -6068,7 +6111,7 @@ function plaque(opts) {
|
|
|
6068
6111
|
label: opts.label,
|
|
6069
6112
|
icon: opts.icon,
|
|
6070
6113
|
fields: merge("plaque", title, opts.subtitle),
|
|
6071
|
-
|
|
6114
|
+
ui: { kind: "plaque" }
|
|
6072
6115
|
}),
|
|
6073
6116
|
view: {
|
|
6074
6117
|
kind: "plaque",
|
|
@@ -6091,7 +6134,7 @@ function ledger(opts) {
|
|
|
6091
6134
|
label: opts.label,
|
|
6092
6135
|
icon: opts.icon,
|
|
6093
6136
|
fields: opts.fields,
|
|
6094
|
-
|
|
6137
|
+
ui: { kind: "ledger" }
|
|
6095
6138
|
}),
|
|
6096
6139
|
view: { kind: "ledger" }
|
|
6097
6140
|
};
|
|
@@ -6110,7 +6153,7 @@ function stats(opts) {
|
|
|
6110
6153
|
label: opts.label,
|
|
6111
6154
|
icon: opts.icon,
|
|
6112
6155
|
fields: opts.fields,
|
|
6113
|
-
|
|
6156
|
+
ui: { kind: "stats" }
|
|
6114
6157
|
}),
|
|
6115
6158
|
view: { kind: "stats" }
|
|
6116
6159
|
};
|
|
@@ -6129,7 +6172,7 @@ function facets(opts) {
|
|
|
6129
6172
|
label: opts.label,
|
|
6130
6173
|
icon: opts.icon,
|
|
6131
6174
|
fields: opts.fields,
|
|
6132
|
-
|
|
6175
|
+
ui: { kind: "facets" }
|
|
6133
6176
|
}),
|
|
6134
6177
|
view: { kind: "facets" }
|
|
6135
6178
|
};
|
|
@@ -6148,7 +6191,7 @@ function terminal(opts) {
|
|
|
6148
6191
|
label: opts.label,
|
|
6149
6192
|
icon: opts.icon,
|
|
6150
6193
|
fields: opts.fields,
|
|
6151
|
-
|
|
6194
|
+
ui: { kind: "terminal" }
|
|
6152
6195
|
}),
|
|
6153
6196
|
view: {
|
|
6154
6197
|
kind: "terminal",
|
|
@@ -6170,7 +6213,7 @@ function callout(opts) {
|
|
|
6170
6213
|
label: opts.label,
|
|
6171
6214
|
icon: opts.icon,
|
|
6172
6215
|
fields: opts.fields,
|
|
6173
|
-
|
|
6216
|
+
ui: { kind: "callout" }
|
|
6174
6217
|
}),
|
|
6175
6218
|
view: {
|
|
6176
6219
|
kind: "callout",
|
|
@@ -6195,7 +6238,7 @@ function compare(opts) {
|
|
|
6195
6238
|
label: opts.label,
|
|
6196
6239
|
icon: opts.icon,
|
|
6197
6240
|
fields: merge("compare", opts.left, opts.right),
|
|
6198
|
-
|
|
6241
|
+
ui: { kind: "compare" }
|
|
6199
6242
|
}),
|
|
6200
6243
|
view: {
|
|
6201
6244
|
kind: "compare",
|
|
@@ -6226,7 +6269,7 @@ function figure(opts) {
|
|
|
6226
6269
|
return {
|
|
6227
6270
|
...group({
|
|
6228
6271
|
fields: merge("figure", image, caption),
|
|
6229
|
-
|
|
6272
|
+
ui: { kind: "figure" }
|
|
6230
6273
|
}),
|
|
6231
6274
|
view: {
|
|
6232
6275
|
kind: "figure",
|
|
@@ -6251,7 +6294,7 @@ function epigraph(opts) {
|
|
|
6251
6294
|
return {
|
|
6252
6295
|
...group({
|
|
6253
6296
|
fields: merge("epigraph", source, attribution),
|
|
6254
|
-
|
|
6297
|
+
ui: { kind: "epigraph" }
|
|
6255
6298
|
}),
|
|
6256
6299
|
view: {
|
|
6257
6300
|
kind: "epigraph",
|
|
@@ -6277,7 +6320,7 @@ function masthead(opts) {
|
|
|
6277
6320
|
return {
|
|
6278
6321
|
...group({
|
|
6279
6322
|
fields: merge("masthead", overline, title, opts.meta),
|
|
6280
|
-
|
|
6323
|
+
ui: { kind: "masthead" }
|
|
6281
6324
|
}),
|
|
6282
6325
|
view: {
|
|
6283
6326
|
kind: "masthead",
|
|
@@ -6301,7 +6344,7 @@ function timeline(opts) {
|
|
|
6301
6344
|
label: opts.label,
|
|
6302
6345
|
icon: opts.icon,
|
|
6303
6346
|
fields: opts.fields,
|
|
6304
|
-
|
|
6347
|
+
ui: { kind: "timeline" }
|
|
6305
6348
|
}),
|
|
6306
6349
|
view: { kind: "timeline" }
|
|
6307
6350
|
};
|
|
@@ -6320,7 +6363,7 @@ function countdown(opts) {
|
|
|
6320
6363
|
...group({
|
|
6321
6364
|
label: opts.label,
|
|
6322
6365
|
fields: source,
|
|
6323
|
-
|
|
6366
|
+
ui: { kind: "countdown" }
|
|
6324
6367
|
}),
|
|
6325
6368
|
view: {
|
|
6326
6369
|
kind: "countdown",
|
|
@@ -6343,7 +6386,7 @@ function deck(opts) {
|
|
|
6343
6386
|
...group({
|
|
6344
6387
|
label: opts.label,
|
|
6345
6388
|
fields: source,
|
|
6346
|
-
|
|
6389
|
+
ui: { kind: "deck" }
|
|
6347
6390
|
}),
|
|
6348
6391
|
view: {
|
|
6349
6392
|
kind: "deck",
|
|
@@ -6365,7 +6408,7 @@ function people(opts) {
|
|
|
6365
6408
|
...group({
|
|
6366
6409
|
label: opts.label,
|
|
6367
6410
|
fields: source,
|
|
6368
|
-
|
|
6411
|
+
ui: { kind: "people" }
|
|
6369
6412
|
}),
|
|
6370
6413
|
view: {
|
|
6371
6414
|
kind: "people",
|
|
@@ -6391,7 +6434,7 @@ function identity(opts) {
|
|
|
6391
6434
|
return {
|
|
6392
6435
|
...group({
|
|
6393
6436
|
fields: merge("identity", avatar, name, role, opts.channels),
|
|
6394
|
-
|
|
6437
|
+
ui: { kind: "identity" }
|
|
6395
6438
|
}),
|
|
6396
6439
|
view: {
|
|
6397
6440
|
kind: "identity",
|
|
@@ -6417,7 +6460,7 @@ function score(opts) {
|
|
|
6417
6460
|
...group({
|
|
6418
6461
|
label: opts.label,
|
|
6419
6462
|
fields: merge("score", source, verdict),
|
|
6420
|
-
|
|
6463
|
+
ui: { kind: "score" }
|
|
6421
6464
|
}),
|
|
6422
6465
|
view: {
|
|
6423
6466
|
kind: "score",
|
|
@@ -6442,7 +6485,7 @@ function status(opts) {
|
|
|
6442
6485
|
...group({
|
|
6443
6486
|
label: opts.label,
|
|
6444
6487
|
fields: merge("status", source, since),
|
|
6445
|
-
|
|
6488
|
+
ui: { kind: "status" }
|
|
6446
6489
|
}),
|
|
6447
6490
|
view: {
|
|
6448
6491
|
kind: "status",
|
|
@@ -6466,7 +6509,7 @@ function meter(opts) {
|
|
|
6466
6509
|
...group({
|
|
6467
6510
|
label: opts.label,
|
|
6468
6511
|
fields: merge("meter", source, of),
|
|
6469
|
-
|
|
6512
|
+
ui: { kind: "meter" }
|
|
6470
6513
|
}),
|
|
6471
6514
|
view: {
|
|
6472
6515
|
kind: "meter",
|
|
@@ -6493,7 +6536,7 @@ function receipt(opts) {
|
|
|
6493
6536
|
...group({
|
|
6494
6537
|
label: opts.label,
|
|
6495
6538
|
fields: merge("receipt", opts.fields, total),
|
|
6496
|
-
|
|
6539
|
+
ui: { kind: "receipt" }
|
|
6497
6540
|
}),
|
|
6498
6541
|
view: {
|
|
6499
6542
|
kind: "receipt",
|
|
@@ -6516,7 +6559,7 @@ function balance(opts) {
|
|
|
6516
6559
|
...group({
|
|
6517
6560
|
label: opts.label,
|
|
6518
6561
|
fields: source,
|
|
6519
|
-
|
|
6562
|
+
ui: { kind: "balance" }
|
|
6520
6563
|
}),
|
|
6521
6564
|
view: {
|
|
6522
6565
|
kind: "balance",
|
|
@@ -6544,7 +6587,7 @@ function route(opts) {
|
|
|
6544
6587
|
return {
|
|
6545
6588
|
...group({
|
|
6546
6589
|
fields: merge("route", from, to, depart, arrive, duration),
|
|
6547
|
-
|
|
6590
|
+
ui: { kind: "route" }
|
|
6548
6591
|
}),
|
|
6549
6592
|
view: {
|
|
6550
6593
|
kind: "route",
|
|
@@ -6693,7 +6736,7 @@ function nutrition(opts) {
|
|
|
6693
6736
|
...group({
|
|
6694
6737
|
label: opts.label,
|
|
6695
6738
|
fields: merge("nutrition", energy, opts.fields),
|
|
6696
|
-
|
|
6739
|
+
ui: { kind: "nutrition" }
|
|
6697
6740
|
}),
|
|
6698
6741
|
view: {
|
|
6699
6742
|
kind: "nutrition",
|
|
@@ -6719,7 +6762,7 @@ function specimen(opts) {
|
|
|
6719
6762
|
return {
|
|
6720
6763
|
...group({
|
|
6721
6764
|
fields: merge("specimen", title, subtitle, opts.conditions),
|
|
6722
|
-
|
|
6765
|
+
ui: { kind: "specimen" }
|
|
6723
6766
|
}),
|
|
6724
6767
|
view: {
|
|
6725
6768
|
kind: "specimen",
|
|
@@ -6796,7 +6839,7 @@ function idcard(opts) {
|
|
|
6796
6839
|
return {
|
|
6797
6840
|
...group({
|
|
6798
6841
|
fields: merge("idcard", overline, number, opts.meta),
|
|
6799
|
-
|
|
6842
|
+
ui: { kind: "idcard" }
|
|
6800
6843
|
}),
|
|
6801
6844
|
view: {
|
|
6802
6845
|
kind: "idcard",
|
|
@@ -6822,7 +6865,7 @@ function properties(opts) {
|
|
|
6822
6865
|
label: opts.label,
|
|
6823
6866
|
icon: opts.icon,
|
|
6824
6867
|
fields: opts.fields,
|
|
6825
|
-
|
|
6868
|
+
ui: { kind: "properties" }
|
|
6826
6869
|
}),
|
|
6827
6870
|
view: {
|
|
6828
6871
|
kind: "properties",
|
|
@@ -6844,7 +6887,7 @@ function stack(opts) {
|
|
|
6844
6887
|
label: opts.label,
|
|
6845
6888
|
icon: opts.icon,
|
|
6846
6889
|
fields: opts.fields,
|
|
6847
|
-
|
|
6890
|
+
ui: { kind: "stack" }
|
|
6848
6891
|
}),
|
|
6849
6892
|
view: { kind: "stack" }
|
|
6850
6893
|
};
|
|
@@ -7725,8 +7768,9 @@ var hasChildren = (x) => Array.isArray(x.fields);
|
|
|
7725
7768
|
* @example f.group({ scope: 'nest', label: 'mod.fields.contact', fields: { name: f.string({}) } })
|
|
7726
7769
|
*/
|
|
7727
7770
|
function group(o) {
|
|
7771
|
+
if (o["view"] !== void 0) throw new Error("[group] `view` is gone — presentational options live in `ui: {}` (see CLAUDE.md § field author options and the field-types skill).");
|
|
7728
7772
|
const r = o.rules ?? {};
|
|
7729
|
-
const v = o.
|
|
7773
|
+
const v = o.ui ?? {};
|
|
7730
7774
|
const fields = materializeTree(o.fields);
|
|
7731
7775
|
const scope = o.scope ?? "hoist";
|
|
7732
7776
|
if (scope === "nest") {
|
|
@@ -7772,7 +7816,7 @@ function row(o) {
|
|
|
7772
7816
|
multiple: o.multiple,
|
|
7773
7817
|
required: o.required,
|
|
7774
7818
|
rules: o.rules,
|
|
7775
|
-
|
|
7819
|
+
ui: { display: "scroll" }
|
|
7776
7820
|
});
|
|
7777
7821
|
}
|
|
7778
7822
|
/**
|
|
@@ -7800,7 +7844,7 @@ function sheet(o) {
|
|
|
7800
7844
|
label: o.label,
|
|
7801
7845
|
icon: o.icon,
|
|
7802
7846
|
fields: rows,
|
|
7803
|
-
|
|
7847
|
+
ui: { display: "sheet" }
|
|
7804
7848
|
});
|
|
7805
7849
|
}
|
|
7806
7850
|
/**
|
|
@@ -7819,7 +7863,7 @@ function url(o) {
|
|
|
7819
7863
|
scope: "nest",
|
|
7820
7864
|
label: o.label,
|
|
7821
7865
|
required: o.required,
|
|
7822
|
-
|
|
7866
|
+
ui: { kind: "url" },
|
|
7823
7867
|
fields: {
|
|
7824
7868
|
scheme: string({}),
|
|
7825
7869
|
username: string({}),
|
|
@@ -7961,13 +8005,15 @@ registerType("string", {
|
|
|
7961
8005
|
build(o) {
|
|
7962
8006
|
const cfg = o.config ?? {};
|
|
7963
8007
|
const min = cfg.min ?? (o.required === true ? 1 : 0);
|
|
7964
|
-
let s = string$1(reqTypeErr());
|
|
7965
|
-
if (min > 0) s = s.min(min, { message: vmsg("min_length", { min }) });
|
|
7966
|
-
if (cfg.max != null) s = s.max(cfg.max, { message: vmsg("max_length", { max: cfg.max }) });
|
|
7967
|
-
if (cfg.pattern) s = s.regex(new RegExp(cfg.pattern), { message: vmsg("pattern", cfg.messageKey ? { messageKey: cfg.messageKey } : void 0) });
|
|
7968
8008
|
return {
|
|
7969
8009
|
column: "text",
|
|
7970
|
-
zod:
|
|
8010
|
+
zod: stringContent((str) => {
|
|
8011
|
+
let c = str;
|
|
8012
|
+
if (min > 0) c = c.min(min, { message: vmsg("min_length", { min }) });
|
|
8013
|
+
if (cfg.max != null) c = c.max(cfg.max, { message: vmsg("max_length", { max: cfg.max }) });
|
|
8014
|
+
if (cfg.pattern) c = c.regex(new RegExp(cfg.pattern), { message: vmsg("pattern", cfg.messageKey ? { messageKey: cfg.messageKey } : void 0) });
|
|
8015
|
+
return c;
|
|
8016
|
+
}),
|
|
7971
8017
|
hints: {
|
|
7972
8018
|
minLength: cfg.min,
|
|
7973
8019
|
maxLength: cfg.max
|
|
@@ -7988,9 +8034,7 @@ function string(o) {
|
|
|
7988
8034
|
}
|
|
7989
8035
|
/** Shared by localDir/localFile's build(): a server path, min-length-1 when required. */
|
|
7990
8036
|
function pathZod(o) {
|
|
7991
|
-
|
|
7992
|
-
if (o.required === true) s = s.min(1, { message: vmsg("min_length", { min: 1 }) });
|
|
7993
|
-
return s;
|
|
8037
|
+
return stringContent((s) => o.required === true ? s.min(1, { message: vmsg("min_length", { min: 1 }) }) : s);
|
|
7994
8038
|
}
|
|
7995
8039
|
registerType("localDir", {
|
|
7996
8040
|
kind: "localDir",
|
|
@@ -8652,7 +8696,8 @@ var MEASURED_ROLES = (opts) => {
|
|
|
8652
8696
|
label,
|
|
8653
8697
|
required,
|
|
8654
8698
|
value,
|
|
8655
|
-
|
|
8699
|
+
noSearch: d?.opts.noSearch,
|
|
8700
|
+
ui: uiOf(d?.opts),
|
|
8656
8701
|
options: opts.units,
|
|
8657
8702
|
strict: true
|
|
8658
8703
|
});
|
|
@@ -9021,7 +9066,7 @@ function periodMkSubDecl(granularity) {
|
|
|
9021
9066
|
* (`mkSubDecl`), so it writes its own `normalize` rather than using `role()`. A CORRECTED
|
|
9022
9067
|
* filling still keeps the composite's own granularity (`canonical.opts`) — the same
|
|
9023
9068
|
* `date`/`datetime` config the role's own default carries — with the author's
|
|
9024
|
-
* label/required/
|
|
9069
|
+
* label/required/ui/value layered on top, exactly like `retypeTo`'s default.
|
|
9025
9070
|
*/
|
|
9026
9071
|
function periodRole(key, label, mkSubDecl) {
|
|
9027
9072
|
const def = {
|
|
@@ -9037,7 +9082,8 @@ function periodRole(key, label, mkSubDecl) {
|
|
|
9037
9082
|
label: l ?? canonical.opts.label,
|
|
9038
9083
|
required,
|
|
9039
9084
|
value,
|
|
9040
|
-
|
|
9085
|
+
noSearch: d.opts.noSearch,
|
|
9086
|
+
ui: uiOf(d.opts)
|
|
9041
9087
|
});
|
|
9042
9088
|
}
|
|
9043
9089
|
};
|
|
@@ -9505,10 +9551,11 @@ var field = new Proxy({}, {
|
|
|
9505
9551
|
has: (_t, k) => k in composedField()
|
|
9506
9552
|
});
|
|
9507
9553
|
function toClient(field) {
|
|
9508
|
-
const { kind, label, required, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
|
|
9554
|
+
const { kind, label, agent, required, prim, hints, options, strict, relation, json, hidden, derived, parts, cache } = field;
|
|
9509
9555
|
return {
|
|
9510
9556
|
kind,
|
|
9511
9557
|
label,
|
|
9558
|
+
...agent !== void 0 && { agent },
|
|
9512
9559
|
required,
|
|
9513
9560
|
prim,
|
|
9514
9561
|
hints,
|
|
@@ -9542,15 +9589,14 @@ function toClient(field) {
|
|
|
9542
9589
|
* open: a magnitude may be real/int/rating, a unit may be a select or a relation.
|
|
9543
9590
|
*/
|
|
9544
9591
|
/**
|
|
9545
|
-
* `NormalizedOpts` (what `FieldDecl.opts` actually is) has no single `
|
|
9546
|
-
* author's `
|
|
9547
|
-
* by `normalizeOpts`. A correction that wants to carry
|
|
9548
|
-
* flat fields back into the nested shape `declare()`'s own `normalizeOpts` call expects.
|
|
9592
|
+
* `NormalizedOpts` (what `FieldDecl.opts` actually is) has no single `ui` property — the
|
|
9593
|
+
* author's `ui` is flattened into individual fields (`hidden`, `display`, `kind`, …)
|
|
9594
|
+
* by `normalizeOpts`. A correction that wants to carry presentation forward has to collect
|
|
9595
|
+
* those flat fields back into the nested shape `declare()`'s own `normalizeOpts` call expects.
|
|
9549
9596
|
*/
|
|
9550
|
-
var
|
|
9597
|
+
var UI_KEYS = [
|
|
9551
9598
|
"hidden",
|
|
9552
9599
|
"noEditControl",
|
|
9553
|
-
"noSearch",
|
|
9554
9600
|
"display",
|
|
9555
9601
|
"kind",
|
|
9556
9602
|
"icon",
|
|
@@ -9561,11 +9607,11 @@ var VIEW_KEYS = [
|
|
|
9561
9607
|
"span"
|
|
9562
9608
|
];
|
|
9563
9609
|
/** The author's presentation opts, re-nested from `opts`'s flat fields — `undefined` when
|
|
9564
|
-
* none were set, so a correction never adds an empty `
|
|
9565
|
-
function
|
|
9610
|
+
* none were set, so a correction never adds an empty `ui: {}` no filling ever had. */
|
|
9611
|
+
function uiOf(opts) {
|
|
9566
9612
|
if (!opts) return void 0;
|
|
9567
9613
|
const v = {};
|
|
9568
|
-
for (const k of
|
|
9614
|
+
for (const k of UI_KEYS) if (opts[k] !== void 0) v[k] = opts[k];
|
|
9569
9615
|
return Object.keys(v).length ? v : void 0;
|
|
9570
9616
|
}
|
|
9571
9617
|
/** A filling built from a ported factory arrives as a FieldDecl — build it into the
|
|
@@ -9588,7 +9634,7 @@ function accepts(def, d) {
|
|
|
9588
9634
|
}
|
|
9589
9635
|
/**
|
|
9590
9636
|
* The correction every role gets for free: keep what the author said ABOUT the field
|
|
9591
|
-
* (`label`, `required`, `
|
|
9637
|
+
* (`label`, `required`, `ui`, `value` — a constant or a ComputeFn), replace the type, and
|
|
9592
9638
|
* drop what belonged to the OLD type (`rules` — a number's min/max mean nothing to a
|
|
9593
9639
|
* picture — and `multiple`, because a composite role owns exactly one column). A role whose
|
|
9594
9640
|
* own default needs more than a bare factory (`measured`'s unit, `period`'s endpoints)
|
|
@@ -9601,7 +9647,8 @@ function retypeTo(factory) {
|
|
|
9601
9647
|
label,
|
|
9602
9648
|
required,
|
|
9603
9649
|
value,
|
|
9604
|
-
|
|
9650
|
+
noSearch: d?.opts.noSearch,
|
|
9651
|
+
ui: uiOf(d?.opts)
|
|
9605
9652
|
});
|
|
9606
9653
|
};
|
|
9607
9654
|
}
|
|
@@ -9784,56 +9831,13 @@ function partColumns(parts) {
|
|
|
9784
9831
|
}
|
|
9785
9832
|
return cols;
|
|
9786
9833
|
}
|
|
9787
|
-
|
|
9788
|
-
* A composite's write-time row shape, DERIVED from its resolved parts. `shape` carries
|
|
9789
|
-
* the composite's DEFAULT schema per role — the literal the factory writes by hand
|
|
9790
|
-
* (`{ value: numSchema, unit: z.string().refine(…) }`) — and each part decides which
|
|
9791
|
-
* schema guards its (role-named) key:
|
|
9792
|
-
*
|
|
9793
|
-
* - a plain STORED part is keyed by its role name, matching the value object and the
|
|
9794
|
-
* column `<field>__<role>` that `flattenEmbeddedAt`/`nestEmbeddedAt` read. A role left
|
|
9795
|
-
* to its default filling keeps the default schema — byte-identical to the hand-written
|
|
9796
|
-
* literal, so every existing structure/unit/range code and message is preserved.
|
|
9797
|
-
* - an OVERRIDDEN stored part is validated by its OWN zod (`meta.zod`) instead of the
|
|
9798
|
-
* default schema for that role. This is what makes a RETYPED filling real: an
|
|
9799
|
-
* `f.int({})` magnitude rejects 5.5 (its own `int` check) and gets an integer column,
|
|
9800
|
-
* and an `f.relation({…})` currency accepts a record id instead of being measured
|
|
9801
|
-
* against an ISO-4217 string rule it was never meant to satisfy. The filling also
|
|
9802
|
-
* brings its own optionality and its own min/max — an author replacing a filling
|
|
9803
|
-
* takes over that role's constraints, so `f.real({ rules: { min: 0 } })` is how a
|
|
9804
|
-
* replaced magnitude keeps a bound.
|
|
9805
|
-
* - a COMPUTED-AND-STORED part ('computedStored') is keyed by its role name like any
|
|
9806
|
-
* other part, but its schema is `z.unknown().optional()`: the SERVER owns the value, so
|
|
9807
|
-
* whatever the client sends there is ignored — `stripServerOwnedParts` deletes it a
|
|
9808
|
-
* moment later and the write path recomputes the column. It is stripped rather than
|
|
9809
|
-
* `z.never()`-rejected because a read hands the client that key (it is a real column,
|
|
9810
|
-
* present in every response), so rejecting it would break a read → PATCH-the-whole-object
|
|
9811
|
-
* round trip.
|
|
9812
|
-
* - a CLIENT-OWNED stored part of a composite that is not `required: true` also accepts
|
|
9813
|
-
* an explicit `null` (`tolerateNull`), which is what closes the read → write round trip
|
|
9814
|
-
* for a PARTIALLY FILLED composite. A SELECT returns every sub-column, so a record whose
|
|
9815
|
-
* magnitude column is set and whose unit column is empty reads back as
|
|
9816
|
-
* `{value: 900, unit: null}` (`nestEmbeddedAt` drops the key only when EVERY column is
|
|
9817
|
-
* empty — `0` is a value), and feeding that exact object back used to fail with
|
|
9818
|
-
* `measured_structure`: the write path refused the shape its own read produced. The null
|
|
9819
|
-
* slot now parses through and is written back as NULL, so the round trip is closed and
|
|
9820
|
-
* clearing ONE role of a filled composite works instead of erroring.
|
|
9821
|
-
* NULL, not absence: a read emits every stored role (empty ones as `null`), so `null` is
|
|
9822
|
-
* how "this record has no value there" arrives, while an ABSENT key is a writer that
|
|
9823
|
-
* never mentioned a role it owns — which stays the structure error it has always been
|
|
9824
|
-
* (`{unit: 'kg'}` with no magnitude, `{lat: 50}` with no longitude). That is the
|
|
9825
|
-
* all-or-nothing rule for a DECLARATION, and it is untouched.
|
|
9826
|
-
* A `required: true` composite keeps every client-owned slot MANDATORY, null included —
|
|
9827
|
-
* "tolerate null" must not turn a required field optional, so an empty or half-empty
|
|
9828
|
-
* value still fails with the same structure code it fails with today. A filling the
|
|
9829
|
-
* author declared `required` keeps its own mandate too.
|
|
9830
|
-
*/
|
|
9831
|
-
function partsRowShape(shape, parts, tolerateNull = false) {
|
|
9834
|
+
function partsRowShape(shape, parts, tolerance = "strict") {
|
|
9832
9835
|
const out = { ...shape };
|
|
9833
9836
|
for (const p of parts) if (p.mode === "computedStored") out[p.key] = unknown().optional();
|
|
9834
9837
|
else {
|
|
9835
9838
|
const own = p.overridden ? p.meta.zod : shape[p.role] ?? unknown().optional();
|
|
9836
|
-
|
|
9839
|
+
const relaxed = tolerance !== "strict" && !(p.overridden && p.meta.required === true);
|
|
9840
|
+
out[p.key] = relaxed ? tolerance === "null-or-missing" ? own.nullish() : own.nullable() : own;
|
|
9837
9841
|
}
|
|
9838
9842
|
return out;
|
|
9839
9843
|
}
|
|
@@ -9874,16 +9878,17 @@ function stripServerOwnedParts(value, parts) {
|
|
|
9874
9878
|
function buildComposite(def, opts, parts) {
|
|
9875
9879
|
const required = opts.required ?? false;
|
|
9876
9880
|
const roleZod = def.roleZod(opts, parts);
|
|
9877
|
-
const rowSchema = object(partsRowShape(roleZod, parts, required
|
|
9878
|
-
const absentRowSchema = required === true ? object(partsRowShape(roleZod, parts,
|
|
9881
|
+
const rowSchema = object(partsRowShape(roleZod, parts, required === true ? "strict" : "null"));
|
|
9882
|
+
const absentRowSchema = required === true ? object(partsRowShape(roleZod, parts, "null-or-missing")) : rowSchema;
|
|
9879
9883
|
const roleKeys = (which) => parts.filter((p) => p.mode === "stored" && (which?.(p) ?? true)).map(partValueKey);
|
|
9880
9884
|
const absenceKeys = def.absenceRoles ? roleKeys((p) => def.absenceRoles.includes(p.role)) : void 0;
|
|
9881
9885
|
const clientKeys = def.absenceRoles ? roleKeys() : void 0;
|
|
9882
9886
|
const zod = unknown().transform((raw, ctx) => {
|
|
9883
9887
|
const parsed = jsonValue(raw);
|
|
9884
9888
|
const p = parsed;
|
|
9889
|
+
const isRow = p == null || typeof p === "object" && !Array.isArray(p);
|
|
9885
9890
|
/** Every one of `keys` empty in this value — `null`/absent alike. */
|
|
9886
|
-
const allEmpty = (keys) => p == null ||
|
|
9891
|
+
const allEmpty = (keys) => isRow && (p == null || keys.length > 0 && keys.every((k) => p[k] == null));
|
|
9887
9892
|
let absent = false;
|
|
9888
9893
|
if (absenceKeys && clientKeys) {
|
|
9889
9894
|
absent = allEmpty(absenceKeys);
|
|
@@ -9957,6 +9962,7 @@ function materialize(decl, name) {
|
|
|
9957
9962
|
const base = {
|
|
9958
9963
|
kind: type.kind,
|
|
9959
9964
|
label: opts.label ?? "",
|
|
9965
|
+
...opts.agent !== void 0 && { agent: opts.agent },
|
|
9960
9966
|
required,
|
|
9961
9967
|
prim: type.prim,
|
|
9962
9968
|
column: built.column,
|
|
@@ -10000,6 +10006,7 @@ function collectionGroup(key, opts, parts, collectionView) {
|
|
|
10000
10006
|
multiple: true,
|
|
10001
10007
|
required: opts.required ?? false,
|
|
10002
10008
|
label: opts.label ?? key,
|
|
10009
|
+
...opts.agent !== void 0 && { agent: opts.agent },
|
|
10003
10010
|
display: "wrap",
|
|
10004
10011
|
kind: collectionView,
|
|
10005
10012
|
fields: rowFields
|
|
@@ -10132,7 +10139,7 @@ var src_default = definePlugin({
|
|
|
10132
10139
|
label: "webchat.settings.reasoning_display",
|
|
10133
10140
|
default: "live",
|
|
10134
10141
|
strict: true,
|
|
10135
|
-
|
|
10142
|
+
noSearch: true,
|
|
10136
10143
|
options: [{
|
|
10137
10144
|
value: "live",
|
|
10138
10145
|
title: "webchat.settings.reasoning_display_live"
|
package/dist/web.js
CHANGED
|
@@ -11,7 +11,7 @@ import { definePluginUI } from "@coffer-org/web-ui";
|
|
|
11
11
|
var ui_default = definePluginUI({ slots: [{
|
|
12
12
|
slot: "overlay",
|
|
13
13
|
id: "webchat",
|
|
14
|
-
load: () => import("./ChatWidget-
|
|
14
|
+
load: () => import("./ChatWidget-DFVDexpD.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": "
|
|
3
|
+
"version": "6.0.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24"
|
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
"test:ui": "vitest run --root ."
|
|
28
28
|
},
|
|
29
29
|
"dependencies": {
|
|
30
|
-
"@coffer-org/sdk": "^
|
|
31
|
-
"@coffer-org/server": "^
|
|
30
|
+
"@coffer-org/sdk": "^6.0.0",
|
|
31
|
+
"@coffer-org/server": "^6.0.0",
|
|
32
32
|
"lucide-react": "^1.17.0",
|
|
33
33
|
"react-markdown": "^10.1.0",
|
|
34
34
|
"remark-gfm": "^4.0.1"
|