@webskill/chatbot 0.9.0 → 0.10.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/index.js CHANGED
@@ -6,7 +6,7 @@ import { t as yaml } from "./yaml-C888tCbJ.js";
6
6
  import { t as json } from "./json-BOcU6nei.js";
7
7
  import { t as typescript } from "./typescript-DAau_Qyl.js";
8
8
  import { t as jsx$1 } from "./jsx-D2Jv0wvt.js";
9
- import { BEHAVIOR_RECORDS_KEY, DEFAULT_LOOP_LIMITS, DEFAULT_USER_PROFILE_LIMITS, EventBus, FsArtifactStore, FsMemoryStore, FsRunSnapshotStore, FsRunTraceStore, FsSessionStore, FsToolStepStore, FullDisclosureRouter, SerializingMemoryStore, SkillDiscovery, USER_PROFILE_KEY, WebSkillError, WebSkillRuntime, applyUserProfileImport, assertRemoteUrlAllowed, atomicWriteText, diffUserProfile, exportUserProfile, isUnsupportedRunSnapshot, messageOf, readBehaviorRecords, readUserProfile, refineUserProfile, summarizeRunUsage, summarizeToolCalls, textParts } from "@webskill/sdk";
9
+ import { BEHAVIOR_RECORDS_KEY, DEFAULT_LOOP_LIMITS, DEFAULT_USER_PROFILE_LIMITS, EventBus, FsArtifactStore, FsMemoryStore, FsRunSnapshotStore, FsRunTraceStore, FsSessionStore, FsToolStepStore, FullDisclosureRouter, SUPPORTED_DOCUMENT_MIME, SerializingMemoryStore, SkillDiscovery, USER_PROFILE_KEY, WebSkillError, WebSkillRuntime, applyUserProfileImport, assertRemoteUrlAllowed, atomicWriteText, diffUserProfile, exportUserProfile, isUnsupportedRunSnapshot, messageOf, readBehaviorRecords, readUserProfile, refineUserProfile, summarizeRunUsage, summarizeToolCalls, textParts } from "@webskill/sdk";
10
10
  import { BrowserWorkerScriptExecutor, checkDictationAvailability, compressImageToBudget, createEncryptedMemoryStore, createLlmClient, deleteMemoryEncryptionKey, openMemoryEncryptionKey, probeChromeBuiltinAvailability, startDictation } from "@webskill/sdk/browser";
11
11
  import { JsonRenderSpecSurface, NativeSpecSurface, OpenUiSpecSurface, ReactBridgeState, SpecInteractionChannel, SurfaceFormTextsProvider, UiSurfaceSnapshotList, probeJsonRenderAvailability } from "@webskill/sdk/ui-react";
12
12
  import { A2UI_SPEC_FORM_PATH, collectFormScopes, createUiCatalogToolSource, fromA2uiSpecAction, fromA2uiSurfaceAction, interactionToFormModel, interactionToUiSpec, loadOpenUiPeers, loadWebSkillLitCatalog, renderMiniChart, shapeInteractionValue, toA2uiSpecMessages, toA2uiSurfaceAction, toVercelToolInvocation, uiCatalog } from "@webskill/sdk/ui";
@@ -26,6 +26,63 @@ import { Derived, attachTransformScopes, useClientLookup, useClientResource } fr
26
26
  import { resource, useMemoCache, withKey } from "@assistant-ui/tap";
27
27
  import { POLICY_DENIAL_CODES } from "@webskill/sdk/governance";
28
28
 
29
+ //#region ../ui-kit/src/i18n/localizedText.ts
30
+ /**
31
+ * 键的声明顺序即回退顺序(0.13.0 分册 21 FR-21.2)。
32
+ * 写成 `Record<Locale, …>` 而不是 `['zh','en'] as const satisfies readonly Locale[]`:
33
+ * 后者只校验元素合法、不校验取值穷尽,新增语种时会安静地少一个。
34
+ */
35
+ const FALLBACK_ORDER = {
36
+ zh: null,
37
+ en: null
38
+ };
39
+ /**
40
+ * SDK 支持的语种,声明顺序同时是缺语种时的回退顺序与设置界面里的展示顺序。
41
+ * @experimental
42
+ */
43
+ const SUPPORTED_LOCALES = Object.keys(FALLBACK_ORDER);
44
+ const trimmed = (value) => {
45
+ if (typeof value !== "string") return void 0;
46
+ const text = value.trim();
47
+ return text === "" ? void 0 : text;
48
+ };
49
+ /**
50
+ * 按当前语种解析多语种文案(0.13.0 分册 21 FR-21.2):
51
+ * 当前语种非空 → 用它;否则按 {@link SUPPORTED_LOCALES} 顺序取第一个非空的;
52
+ * 全空返回 `undefined`——调用方据此整条丢弃,返回空串会一路流到渲染层变成空白卡片。
53
+ * @experimental
54
+ */
55
+ function resolveLocalizedText(text, locale) {
56
+ if (typeof text === "string") return trimmed(text);
57
+ if (typeof text !== "object" || text === null) return void 0;
58
+ const preferred = trimmed(text[locale]);
59
+ if (preferred !== void 0) return preferred;
60
+ for (const candidate of SUPPORTED_LOCALES) {
61
+ const value = trimmed(text[candidate]);
62
+ if (value !== void 0) return value;
63
+ }
64
+ }
65
+ /** 字符串简写填满全部语种:「这条不分语种,就这一句」。空白串得到空对象。 */
66
+ function normalizeLocalizedText(value) {
67
+ if (typeof value === "string") {
68
+ const text = trimmed(value);
69
+ if (text === void 0) return {};
70
+ return Object.fromEntries(SUPPORTED_LOCALES.map((locale) => [locale, text]));
71
+ }
72
+ if (typeof value !== "object" || value === null) return {};
73
+ const out = {};
74
+ for (const locale of SUPPORTED_LOCALES) {
75
+ const text = trimmed(value[locale]);
76
+ if (text !== void 0) out[locale] = text;
77
+ }
78
+ return out;
79
+ }
80
+ /** 是否有任何语种填了内容;全空的条目一律丢弃(FR-21.2 规则 3) */
81
+ function hasLocalizedText(text) {
82
+ return SUPPORTED_LOCALES.some((locale) => resolveLocalizedText(text, locale) !== void 0);
83
+ }
84
+
85
+ //#endregion
29
86
  //#region ../ui-kit/src/i18n/createI18n.ts
30
87
  /** 字典工厂:各包定义自己的双语字典(恒等返回,保留字面量类型) */
31
88
  function defineDictionary(dict) {
@@ -947,72 +1004,6 @@ const __iconNode$22 = [
947
1004
  ];
948
1005
  const Mic = createLucideIcon("mic", __iconNode$22);
949
1006
 
950
- //#endregion
951
- //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/monitor-off.mjs
952
- /**
953
- * @license lucide-react v1.27.0 - ISC
954
- *
955
- * This source code is licensed under the ISC license.
956
- * See the LICENSE file in the root directory of this source tree.
957
- */
958
- const __iconNode$21 = [
959
- ["path", {
960
- d: "M12 17v4",
961
- key: "1riwvh"
962
- }],
963
- ["path", {
964
- d: "M17 17H4a2 2 0 0 1-2-2V5a2 2 0 0 1 1.184-1.826",
965
- key: "cv7jms"
966
- }],
967
- ["path", {
968
- d: "m2 2 20 20",
969
- key: "1ooewy"
970
- }],
971
- ["path", {
972
- d: "M8 21h8",
973
- key: "1ev6f3"
974
- }],
975
- ["path", {
976
- d: "M8.656 3H20a2 2 0 0 1 2 2v10a2 2 0 0 1-.293 1.042",
977
- key: "z8ni2w"
978
- }]
979
- ];
980
- const MonitorOff = createLucideIcon("monitor-off", __iconNode$21);
981
-
982
- //#endregion
983
- //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/monitor.mjs
984
- /**
985
- * @license lucide-react v1.27.0 - ISC
986
- *
987
- * This source code is licensed under the ISC license.
988
- * See the LICENSE file in the root directory of this source tree.
989
- */
990
- const __iconNode$20 = [
991
- ["rect", {
992
- width: "20",
993
- height: "14",
994
- x: "2",
995
- y: "3",
996
- rx: "2",
997
- key: "48i651"
998
- }],
999
- ["line", {
1000
- x1: "8",
1001
- x2: "16",
1002
- y1: "21",
1003
- y2: "21",
1004
- key: "1svkeh"
1005
- }],
1006
- ["line", {
1007
- x1: "12",
1008
- x2: "12",
1009
- y1: "17",
1010
- y2: "21",
1011
- key: "vw1qmm"
1012
- }]
1013
- ];
1014
- const Monitor = createLucideIcon("monitor", __iconNode$20);
1015
-
1016
1007
  //#endregion
1017
1008
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/octagon-x.mjs
1018
1009
  /**
@@ -1021,7 +1012,7 @@ const Monitor = createLucideIcon("monitor", __iconNode$20);
1021
1012
  * This source code is licensed under the ISC license.
1022
1013
  * See the LICENSE file in the root directory of this source tree.
1023
1014
  */
1024
- const __iconNode$19 = [
1015
+ const __iconNode$21 = [
1025
1016
  ["path", {
1026
1017
  d: "m15 9-6 6",
1027
1018
  key: "1uzhvr"
@@ -1035,7 +1026,7 @@ const __iconNode$19 = [
1035
1026
  key: "z0biqf"
1036
1027
  }]
1037
1028
  ];
1038
- const OctagonX = createLucideIcon("octagon-x", __iconNode$19);
1029
+ const OctagonX = createLucideIcon("octagon-x", __iconNode$21);
1039
1030
 
1040
1031
  //#endregion
1041
1032
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/panel-left.mjs
@@ -1045,7 +1036,7 @@ const OctagonX = createLucideIcon("octagon-x", __iconNode$19);
1045
1036
  * This source code is licensed under the ISC license.
1046
1037
  * See the LICENSE file in the root directory of this source tree.
1047
1038
  */
1048
- const __iconNode$18 = [["rect", {
1039
+ const __iconNode$20 = [["rect", {
1049
1040
  width: "18",
1050
1041
  height: "18",
1051
1042
  x: "3",
@@ -1056,7 +1047,7 @@ const __iconNode$18 = [["rect", {
1056
1047
  d: "M9 3v18",
1057
1048
  key: "fh3hqa"
1058
1049
  }]];
1059
- const PanelLeft = createLucideIcon("panel-left", __iconNode$18);
1050
+ const PanelLeft = createLucideIcon("panel-left", __iconNode$20);
1060
1051
 
1061
1052
  //#endregion
1062
1053
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/paperclip.mjs
@@ -1066,11 +1057,11 @@ const PanelLeft = createLucideIcon("panel-left", __iconNode$18);
1066
1057
  * This source code is licensed under the ISC license.
1067
1058
  * See the LICENSE file in the root directory of this source tree.
1068
1059
  */
1069
- const __iconNode$17 = [["path", {
1060
+ const __iconNode$19 = [["path", {
1070
1061
  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",
1071
1062
  key: "1miecu"
1072
1063
  }]];
1073
- const Paperclip = createLucideIcon("paperclip", __iconNode$17);
1064
+ const Paperclip = createLucideIcon("paperclip", __iconNode$19);
1074
1065
 
1075
1066
  //#endregion
1076
1067
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/pencil.mjs
@@ -1080,14 +1071,59 @@ const Paperclip = createLucideIcon("paperclip", __iconNode$17);
1080
1071
  * This source code is licensed under the ISC license.
1081
1072
  * See the LICENSE file in the root directory of this source tree.
1082
1073
  */
1083
- const __iconNode$16 = [["path", {
1074
+ const __iconNode$18 = [["path", {
1084
1075
  d: "M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z",
1085
1076
  key: "1a8usu"
1086
1077
  }], ["path", {
1087
1078
  d: "m15 5 4 4",
1088
1079
  key: "1mk7zo"
1089
1080
  }]];
1090
- const Pencil = createLucideIcon("pencil", __iconNode$16);
1081
+ const Pencil = createLucideIcon("pencil", __iconNode$18);
1082
+
1083
+ //#endregion
1084
+ //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/pin-off.mjs
1085
+ /**
1086
+ * @license lucide-react v1.27.0 - ISC
1087
+ *
1088
+ * This source code is licensed under the ISC license.
1089
+ * See the LICENSE file in the root directory of this source tree.
1090
+ */
1091
+ const __iconNode$17 = [
1092
+ ["path", {
1093
+ d: "M12 17v5",
1094
+ key: "bb1du9"
1095
+ }],
1096
+ ["path", {
1097
+ d: "M15 9.34V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H7.89",
1098
+ key: "znwnzq"
1099
+ }],
1100
+ ["path", {
1101
+ d: "m2 2 20 20",
1102
+ key: "1ooewy"
1103
+ }],
1104
+ ["path", {
1105
+ d: "M9 9v1.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h11",
1106
+ key: "c9qhm2"
1107
+ }]
1108
+ ];
1109
+ const PinOff = createLucideIcon("pin-off", __iconNode$17);
1110
+
1111
+ //#endregion
1112
+ //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/pin.mjs
1113
+ /**
1114
+ * @license lucide-react v1.27.0 - ISC
1115
+ *
1116
+ * This source code is licensed under the ISC license.
1117
+ * See the LICENSE file in the root directory of this source tree.
1118
+ */
1119
+ const __iconNode$16 = [["path", {
1120
+ d: "M12 17v5",
1121
+ key: "bb1du9"
1122
+ }], ["path", {
1123
+ d: "M9 10.76a2 2 0 0 1-1.11 1.79l-1.78.9A2 2 0 0 0 5 15.24V16a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1v-.76a2 2 0 0 0-1.11-1.79l-1.78-.9A2 2 0 0 1 15 10.76V7a1 1 0 0 1 1-1 2 2 0 0 0 0-4H8a2 2 0 0 0 0 4 1 1 0 0 1 1 1z",
1124
+ key: "1nkz8b"
1125
+ }]];
1126
+ const Pin = createLucideIcon("pin", __iconNode$16);
1091
1127
 
1092
1128
  //#endregion
1093
1129
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/play.mjs
@@ -7967,7 +8003,7 @@ const DIALOG_WIDTHS = {
7967
8003
  * 基类锁定 `h` `rounded` `z`(className 覆盖不生效):
7968
8004
  * `max-h` 是视口上限,圆角与层级属 ui-kit 的整体契约。
7969
8005
  */
7970
- function Dialog({ open, onClose, title, description, footer, hideCloseButton = false, className = "", size, children }) {
8006
+ function Dialog({ open, onClose, title, description, footer, hideCloseButton = false, dismissOnOutsideClick = true, className = "", size, children }) {
7971
8007
  const restoreFocusRef = useRef(null);
7972
8008
  const wasOpenRef = useRef(false);
7973
8009
  if (open && !wasOpenRef.current && typeof document !== "undefined") restoreFocusRef.current = document.activeElement;
@@ -7978,6 +8014,7 @@ function Dialog({ open, onClose, title, description, footer, hideCloseButton = f
7978
8014
  children: /* @__PURE__ */ jsxs(DialogPortal, {
7979
8015
  container: usePortalContainer(),
7980
8016
  children: [/* @__PURE__ */ jsx(DialogOverlay, { className: "fixed inset-0 z-[var(--webskill-z-scrim)] bg-scrim" }), /* @__PURE__ */ jsxs(DialogContent, {
8017
+ ...dismissOnOutsideClick ? {} : { onInteractOutside: (event) => event.preventDefault() },
7981
8018
  onCloseAutoFocus: (event) => {
7982
8019
  const source = restoreFocusRef.current;
7983
8020
  if (source instanceof HTMLElement && source.isConnected) {
@@ -12748,7 +12785,7 @@ function merge(definitions, space) {
12748
12785
  * Value that can be used to look up the properly cased property on a
12749
12786
  * `Schema`.
12750
12787
  */
12751
- function normalize$1(value) {
12788
+ function normalize$2(value) {
12752
12789
  return value.toLowerCase();
12753
12790
  }
12754
12791
 
@@ -12899,8 +12936,8 @@ function create(definition) {
12899
12936
  const info = new DefinedInfo(property, definition.transform(definition.attributes || {}, property), value, definition.space);
12900
12937
  if (definition.mustUseProperty && definition.mustUseProperty.includes(property)) info.mustUseProperty = true;
12901
12938
  properties[property] = info;
12902
- normals[normalize$1(property)] = property;
12903
- normals[normalize$1(info.attribute)] = property;
12939
+ normals[normalize$2(property)] = property;
12940
+ normals[normalize$2(info.attribute)] = property;
12904
12941
  }
12905
12942
  return new Schema(properties, normals, definition.space);
12906
12943
  }
@@ -13992,7 +14029,7 @@ const valid = /^data[-\w.:]+$/i;
13992
14029
  * Info.
13993
14030
  */
13994
14031
  function find(schema, value) {
13995
- const normal = normalize$1(value);
14032
+ const normal = normalize$2(value);
13996
14033
  let property = value;
13997
14034
  let Type = Info;
13998
14035
  if (normal in schema.normal) return schema.property[schema.normal[normal]];
@@ -18879,7 +18916,7 @@ function join(...segments) {
18879
18916
  assertPath$1(segments[index]);
18880
18917
  if (segments[index]) joined = joined === void 0 ? segments[index] : joined + "/" + segments[index];
18881
18918
  }
18882
- return joined === void 0 ? "." : normalize(joined);
18919
+ return joined === void 0 ? "." : normalize$1(joined);
18883
18920
  }
18884
18921
  /**
18885
18922
  * Normalize a basic file path.
@@ -18889,7 +18926,7 @@ function join(...segments) {
18889
18926
  * @returns {string}
18890
18927
  * File path.
18891
18928
  */
18892
- function normalize(path) {
18929
+ function normalize$1(path) {
18893
18930
  assertPath$1(path);
18894
18931
  const absolute = path.codePointAt(0) === 47;
18895
18932
  let value = normalizeString(path, !absolute);
@@ -27475,7 +27512,7 @@ function addChild(nodes, value) {
27475
27512
  function parsePrimitive(info, name, value) {
27476
27513
  if (typeof value === "string") {
27477
27514
  if (info.number && value && !Number.isNaN(Number(value))) return Number(value);
27478
- if ((info.boolean || info.overloadedBoolean) && (value === "" || normalize$1(value) === normalize$1(name))) return true;
27515
+ if ((info.boolean || info.overloadedBoolean) && (value === "" || normalize$2(value) === normalize$2(name))) return true;
27479
27516
  }
27480
27517
  return value;
27481
27518
  }
@@ -30348,6 +30385,101 @@ const DEFAULT_LOOP_LIMITS$1 = {
30348
30385
  maxHistoryMessages: 1e3
30349
30386
  };
30350
30387
 
30388
+ //#endregion
30389
+ //#region ../ui-kit/src/runtime-config/quickPrompts.ts
30390
+ /**
30391
+ * 图标表。这张表不进任何包的公开面:宿主只认名字,具体用哪套图标是实现细节。
30392
+ */
30393
+ const ICONS = {
30394
+ chart: ChartColumn,
30395
+ document: FileText,
30396
+ report: ClipboardList,
30397
+ search: Search,
30398
+ list: List,
30399
+ bug: Bug,
30400
+ test: FlaskConical,
30401
+ metric: Gauge,
30402
+ compare: GitCompare,
30403
+ page: AppWindow,
30404
+ run: Play,
30405
+ warn: TriangleAlert,
30406
+ sparkles: Sparkles,
30407
+ settings: Settings
30408
+ };
30409
+ /** 受控枚举的全部取值(console 的图标下拉数据源) */
30410
+ const QUICK_PROMPT_ICON_NAMES = Object.keys(ICONS);
30411
+ /** 空态网格是两列,8 条即四行(FR-17.4 / D-17-6);用户可在 console 里上调 */
30412
+ const DEFAULT_QUICK_PROMPT_LIMIT = 8;
30413
+ /** 可调范围的天花板:20 条已经把欢迎区撑成十行,再多就满屏都是按钮 */
30414
+ const MAX_QUICK_PROMPT_LIMIT = 20;
30415
+ /** 未知名字每个只提示一次,免得每次渲染都刷屏 */
30416
+ const warned$1 = /* @__PURE__ */ new Set();
30417
+ /**
30418
+ * 解析图标名;取值不在枚举内时返回 undefined(按无图标渲染),
30419
+ * 并在控制台提示一次可用取值——宿主拼错时不至于只看到「图标没出来」。
30420
+ */
30421
+ function resolveQuickPromptIcon(icon) {
30422
+ if (icon === void 0) return void 0;
30423
+ const resolved = ICONS[icon];
30424
+ if (resolved !== void 0) return resolved;
30425
+ if (!warned$1.has(icon)) {
30426
+ warned$1.add(icon);
30427
+ console.debug("[webskill] unknown quick prompt icon", {
30428
+ icon,
30429
+ available: Object.keys(ICONS)
30430
+ });
30431
+ }
30432
+ }
30433
+ /** 统一成对象形态:宿主可以只给字符串,该字符串填满全部语种(FR-21.1) */
30434
+ const normalize = (entry) => typeof entry === "string" ? { text: normalizeLocalizedText(entry) } : entry;
30435
+ /**
30436
+ * 拼接动态与静态两批快捷指令(FR-17.4)。
30437
+ *
30438
+ * 动态在前——它是上下文相关的,更容易点到的位置留给它。
30439
+ * 去重**只发生在跨界处**:静态清单里与某条动态指令同文案的会让位;
30440
+ * 静态清单**内部**的重复原样保留(0.12.0 AC-21.6:宿主自定义时文案本就无唯一性约束,
30441
+ * 混排 `['A', {text:'A', icon:'bug'}]` 是合法的两张卡)。
30442
+ *
30443
+ * 0.13.0 分册 21 FR-21.4:去重口径是**按 `locale` 解析后的文案**,
30444
+ * 因此同一对条目在一个语种下会合并、在另一个语种下可能各自保留。
30445
+ * 按 `id` 去重不可行:动态清单传的是 `QuickPrompt`,本来就没有 `id`。
30446
+ *
30447
+ * 结果按 `limit` 截断(默认 {@link DEFAULT_QUICK_PROMPT_LIMIT})。
30448
+ */
30449
+ function mergeQuickPrompts(dynamic, statics, locale, limit = 8) {
30450
+ const resolve = (entry) => {
30451
+ const prompt = normalize(entry);
30452
+ const text = resolveLocalizedText(prompt.text, locale);
30453
+ if (text === void 0) return void 0;
30454
+ return {
30455
+ text,
30456
+ ...prompt.icon !== void 0 ? { icon: prompt.icon } : {}
30457
+ };
30458
+ };
30459
+ const head = [];
30460
+ const claimed = /* @__PURE__ */ new Set();
30461
+ for (const entry of dynamic ?? []) {
30462
+ const prompt = resolve(entry);
30463
+ if (prompt === void 0 || claimed.has(prompt.text)) continue;
30464
+ claimed.add(prompt.text);
30465
+ head.push(prompt);
30466
+ }
30467
+ const tail = [];
30468
+ for (const entry of statics) {
30469
+ const prompt = resolve(entry);
30470
+ if (prompt === void 0 || claimed.has(prompt.text)) continue;
30471
+ tail.push(prompt);
30472
+ }
30473
+ return [...head, ...tail].slice(0, clampQuickPromptLimit(limit));
30474
+ }
30475
+ /** 存储里的脏值不能让空态变成空白或一堵墙;非正整数一律回默认 */
30476
+ function clampQuickPromptLimit(value) {
30477
+ if (typeof value !== "number" || !Number.isFinite(value)) return 8;
30478
+ const floored = Math.floor(value);
30479
+ if (floored < 1) return 8;
30480
+ return Math.min(floored, 20);
30481
+ }
30482
+
30351
30483
  //#endregion
30352
30484
  //#region ../ui-kit/src/runtime-config/types.ts
30353
30485
  /**
@@ -30427,7 +30559,8 @@ function defaultRuntimeConfig() {
30427
30559
  imageAttachments: false,
30428
30560
  pageImageCapture: false,
30429
30561
  maxImageBytes: 10 * 1024 * 1024,
30430
- maxImagesPerMessage: 5
30562
+ maxImagesPerMessage: 50,
30563
+ minImageArea: 1024
30431
30564
  },
30432
30565
  documentSurface: { enabled: false },
30433
30566
  userProfile: {
@@ -30437,6 +30570,9 @@ function defaultRuntimeConfig() {
30437
30570
  encrypted: true
30438
30571
  },
30439
30572
  skillState: { quarantineThreshold: 5 },
30573
+ quickPrompts: [],
30574
+ quickPromptsSeeded: false,
30575
+ quickPromptLimit: 8,
30440
30576
  dismissedAutoEntries: []
30441
30577
  };
30442
30578
  }
@@ -30521,6 +30657,9 @@ function mergeRuntimeConfigDefaults(partial) {
30521
30657
  documentSurface: mergeDocumentSurface(p["documentSurface"], d.documentSurface),
30522
30658
  userProfile: mergeUserProfile(p["userProfile"], d.userProfile),
30523
30659
  skillState: mergeSkillState(p["skillState"], d.skillState),
30660
+ quickPrompts: readQuickPrompts(p["quickPrompts"]),
30661
+ quickPromptsSeeded: p["quickPromptsSeeded"] === true,
30662
+ quickPromptLimit: clampQuickPromptLimit(p["quickPromptLimit"]),
30524
30663
  dismissedAutoEntries: readStringList$1(p["dismissedAutoEntries"], [])
30525
30664
  };
30526
30665
  }
@@ -30543,6 +30682,35 @@ function readStringList$1(raw, fallback) {
30543
30682
  if (!Array.isArray(raw)) return [...fallback];
30544
30683
  return raw.filter((item) => typeof item === "string");
30545
30684
  }
30685
+ /**
30686
+ * 快捷指令逐条校验(FR-17.2):缺 `id` 或文案全语种皆空的条目**单条丢弃**,
30687
+ * 不因为一条脏数据把整段回退——用户其余的配置不该被连坐。
30688
+ * `icon` 取值不在枚举内时抹掉该字段(渲染侧按无图标处理)。
30689
+ *
30690
+ * 0.13.0 分册 25 FR-25.3:`text: string` 的存量条目一并丢弃,不再升级为语种对象——
30691
+ * 它们在 console 的快捷指令页里没有身份可言,留着就是「看得见、删不掉」。
30692
+ */
30693
+ function readQuickPrompts(raw) {
30694
+ if (!Array.isArray(raw)) return [];
30695
+ const out = [];
30696
+ for (const item of raw) {
30697
+ if (typeof item !== "object" || item === null) continue;
30698
+ const entry = item;
30699
+ const id = typeof entry["id"] === "string" ? entry["id"].trim() : "";
30700
+ const rawText = entry["text"];
30701
+ if (id === "" || typeof rawText !== "object" || rawText === null) continue;
30702
+ const text = normalizeLocalizedText(rawText);
30703
+ if (!hasLocalizedText(text)) continue;
30704
+ const icon = entry["icon"];
30705
+ const valid = typeof icon === "string" && QUICK_PROMPT_ICON_NAMES.includes(icon) ? icon : void 0;
30706
+ out.push({
30707
+ id,
30708
+ text,
30709
+ ...valid !== void 0 ? { icon: valid } : {}
30710
+ });
30711
+ }
30712
+ return out;
30713
+ }
30546
30714
  /** 上限字段非正数时回退默认值:0 或负数会把整条通道变成永远拒收 */
30547
30715
  function mergeMultimodal(raw, d) {
30548
30716
  const p = typeof raw === "object" && raw !== null ? raw : {};
@@ -30551,9 +30719,14 @@ function mergeMultimodal(raw, d) {
30551
30719
  imageAttachments: typeof p["imageAttachments"] === "boolean" ? p["imageAttachments"] : d.imageAttachments,
30552
30720
  pageImageCapture: typeof p["pageImageCapture"] === "boolean" ? p["pageImageCapture"] : d.pageImageCapture,
30553
30721
  maxImageBytes: positive(p["maxImageBytes"], d.maxImageBytes),
30554
- maxImagesPerMessage: positive(p["maxImagesPerMessage"], d.maxImagesPerMessage)
30722
+ maxImagesPerMessage: positive(p["maxImagesPerMessage"], d.maxImagesPerMessage),
30723
+ minImageArea: nonNegative(p["minImageArea"], d.minImageArea)
30555
30724
  };
30556
30725
  }
30726
+ /** 非有限数 / 负数回退默认值,`0` 原样保留 */
30727
+ function nonNegative(value, fallback) {
30728
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? Math.floor(value) : fallback;
30729
+ }
30557
30730
  /**
30558
30731
  * CSP 白名单只收字符串数组;存储被污染时回退默认值(空),
30559
30732
  * **不能**把非法值原样带进 CSP —— `viewerCspHeader` 会抛,等于整条投放面瘫掉。
@@ -31292,6 +31465,9 @@ const chatbotDictionary = defineDictionary({
31292
31465
  "app.title": "WebSkill Chat",
31293
31466
  "header.sessions": "Sessions",
31294
31467
  "header.settings": "Settings",
31468
+ "header.dock": "Dock to page",
31469
+ "header.undock": "Undock from page",
31470
+ "header.close": "Close chat",
31295
31471
  "header.console": "Open Console",
31296
31472
  "header.theme.light": "Switch to light theme",
31297
31473
  "header.theme.dark": "Switch to dark theme",
@@ -31337,10 +31513,7 @@ const chatbotDictionary = defineDictionary({
31337
31513
  "welcome.capability.transparency.title": "Run transparency",
31338
31514
  "welcome.capability.transparency.description": "Routing, skill activation, tool calls and results are visible live, step by step.",
31339
31515
  "welcome.quickPrompts": "Try an example",
31340
- "welcome.prompt.1": "What skills are installed in this workspace?",
31341
- "welcome.prompt.2": "Draft a short product update announcement for me",
31342
- "welcome.prompt.3": "Show me a bar chart of this quarter’s sales",
31343
- "welcome.prompt.4": "Explain how WebSkill runs skill scripts safely",
31516
+ "welcome.dynamicPrompts": "Suggested for this page",
31344
31517
  "message.copy": "Copy",
31345
31518
  "message.copied": "Copied",
31346
31519
  "message.retry": "Retry",
@@ -31495,9 +31668,6 @@ const chatbotDictionary = defineDictionary({
31495
31668
  "composer.noModel.description": "A large language model has not been configured yet. Configure one on the model settings page before chatting.",
31496
31669
  "composer.noModel.configure": "Configure model",
31497
31670
  "capability.group": "Model capabilities",
31498
- "composer.pageCapture": "Read the page view",
31499
- "composer.pageCapture.disabled.setting": "page image capture is off in Settings → Multimodal",
31500
- "composer.pageCapture.disabled.model": "the selected model does not accept images",
31501
31671
  "composer.addAttachment.imagesOff": "images are off in Settings → Multimodal",
31502
31672
  "composer.addAttachment.modelNoImages": "the selected model does not accept images",
31503
31673
  "attachment.imagesDropped": "Only {limit} images can be sent per message. Not sent: {names}",
@@ -31648,6 +31818,9 @@ const chatbotDictionary = defineDictionary({
31648
31818
  "app.title": "WebSkill Chat",
31649
31819
  "header.sessions": "会话",
31650
31820
  "header.settings": "设置",
31821
+ "header.dock": "固定到页面",
31822
+ "header.undock": "取消固定",
31823
+ "header.close": "关闭对话框",
31651
31824
  "header.console": "打开 Console",
31652
31825
  "header.theme.light": "切换到亮色主题",
31653
31826
  "header.theme.dark": "切换到暗色主题",
@@ -31693,10 +31866,7 @@ const chatbotDictionary = defineDictionary({
31693
31866
  "welcome.capability.transparency.title": "运行透明",
31694
31867
  "welcome.capability.transparency.description": "路由、技能激活、工具调用与结果,逐步实时可见。",
31695
31868
  "welcome.quickPrompts": "试试这些示例",
31696
- "welcome.prompt.1": "这个工作区里安装了哪些技能?",
31697
- "welcome.prompt.2": "帮我起草一份简短的产品更新公告",
31698
- "welcome.prompt.3": "给我展示本季度销售额的柱状图",
31699
- "welcome.prompt.4": "解释一下 WebSkill 如何安全地运行技能脚本",
31869
+ "welcome.dynamicPrompts": "当前页面可用的快捷指令",
31700
31870
  "message.copy": "复制",
31701
31871
  "message.copied": "已复制",
31702
31872
  "message.edit": "编辑消息",
@@ -31851,9 +32021,6 @@ const chatbotDictionary = defineDictionary({
31851
32021
  "composer.noModel.description": "大模型尚未配置,请先前往大模型配置页面完成配置,再开始对话。",
31852
32022
  "composer.noModel.configure": "配置大模型",
31853
32023
  "capability.group": "模型能力",
31854
- "composer.pageCapture": "读取页面画面",
31855
- "composer.pageCapture.disabled.setting": "页面图像抓取已在「设置 → 多模态」中关闭",
31856
- "composer.pageCapture.disabled.model": "当前模型不接受图片",
31857
32024
  "composer.addAttachment.imagesOff": "图片已在「设置 → 多模态」中关闭",
31858
32025
  "composer.addAttachment.modelNoImages": "当前模型不接受图片",
31859
32026
  "attachment.imagesDropped": "每条消息最多发送 {limit} 张图片。未发送:{names}",
@@ -32787,7 +32954,7 @@ var ChatEngine = class {
32787
32954
  const rc = await this.#runtimeConfigStore()?.load();
32788
32955
  if (!(rc?.multimodal.imageAttachments ?? false)) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", "Image attachments are turned off. Enable them in settings before sending images.");
32789
32956
  if (!entryCapabilities(pickLlmEntry(rc, this.#selectedModelId)).image) throw new WebSkillError("MODEL_IMAGE_UNSUPPORTED", "Current model does not support image input. Switch model or remove the image.");
32790
- const limit = rc?.multimodal.maxImagesPerMessage ?? 5;
32957
+ const limit = rc?.multimodal.maxImagesPerMessage ?? defaultRuntimeConfig().multimodal.maxImagesPerMessage;
32791
32958
  if (images.length <= limit) return { accepted: attachments };
32792
32959
  const dropped = images.slice(limit);
32793
32960
  const droppedIds = new Set(dropped.map((a) => a.id));
@@ -33170,7 +33337,8 @@ var ChatEngine = class {
33170
33337
  return {
33171
33338
  enabled: (live?.multimodal.pageImageCapture ?? false) && entryCapabilities(pickLlmEntry(live, this.#selectedModelId)).image,
33172
33339
  maxImageBytes: live?.multimodal.maxImageBytes ?? 0,
33173
- maxImages: live?.multimodal.maxImagesPerMessage ?? 0
33340
+ maxImages: live?.multimodal.maxImagesPerMessage ?? 0,
33341
+ minImageArea: live?.multimodal.minImageArea ?? 0
33174
33342
  };
33175
33343
  }
33176
33344
  })] : [],
@@ -33211,6 +33379,8 @@ var ChatEngine = class {
33211
33379
  ...this.#options.fetchData ? { fetchData: this.#options.fetchData } : {},
33212
33380
  ...this.#adapter.linkedDocuments ? { linkedDocuments: this.#adapter.linkedDocuments } : {},
33213
33381
  ...this.#adapter.docxExtractor ? { docxExtractor: this.#adapter.docxExtractor } : {},
33382
+ ...this.#adapter.xlsxExtractor ? { xlsxExtractor: this.#adapter.xlsxExtractor } : {},
33383
+ ...this.#adapter.pdfExtractor ? { pdfExtractor: this.#adapter.pdfExtractor } : {},
33214
33384
  ...this.#adapter.documentAudit ? { documentAudit: this.#adapter.documentAudit } : {},
33215
33385
  ...toolStepStore ? { toolSteps: toolStepStore } : {}
33216
33386
  };
@@ -34129,47 +34299,6 @@ function useWebSkillAui({ messages, live, sessions, currentSessionId, sessionsLo
34129
34299
  }) });
34130
34300
  }
34131
34301
 
34132
- //#endregion
34133
- //#region src/react/quickPromptIcons.ts
34134
- /**
34135
- * 快捷指令图标表(0.12.0 分册 21)。
34136
- * 这张表不导出到包外:宿主只认名字,具体用哪套图标是实现细节。
34137
- */
34138
- const ICONS = {
34139
- chart: ChartColumn,
34140
- document: FileText,
34141
- report: ClipboardList,
34142
- search: Search,
34143
- list: List,
34144
- bug: Bug,
34145
- test: FlaskConical,
34146
- metric: Gauge,
34147
- compare: GitCompare,
34148
- page: AppWindow,
34149
- run: Play,
34150
- warn: TriangleAlert,
34151
- sparkles: Sparkles,
34152
- settings: Settings
34153
- };
34154
- /** 未知名字每个只提示一次,免得每次渲染都刷屏 */
34155
- const warned$1 = /* @__PURE__ */ new Set();
34156
- /**
34157
- * 解析图标名;取值不在枚举内时返回 undefined(按无图标渲染),
34158
- * 并在控制台提示一次可用取值——宿主拼错时不至于只看到「图标没出来」。
34159
- */
34160
- function resolveQuickPromptIcon(icon) {
34161
- if (icon === void 0) return void 0;
34162
- const resolved = ICONS[icon];
34163
- if (resolved !== void 0) return resolved;
34164
- if (!warned$1.has(icon)) {
34165
- warned$1.add(icon);
34166
- console.debug("[webskill] unknown quick prompt icon", {
34167
- icon,
34168
- available: Object.keys(ICONS)
34169
- });
34170
- }
34171
- }
34172
-
34173
34302
  //#endregion
34174
34303
  //#region src/react/AssistantUiShell.tsx
34175
34304
  /**
@@ -34411,63 +34540,94 @@ function AssistantUiSessionList({ className = "", onNavigate, onCollapse, hasEar
34411
34540
  });
34412
34541
  }
34413
34542
  /** Minimal Base toolbar: workspace controls stay secondary to the thread itself. */
34414
- function AssistantUiThreadToolbar({ title, onOpenSessions, onCreateSession, onOpenSettings }) {
34543
+ function AssistantUiThreadToolbar({ title, onOpenSessions, onCreateSession, onOpenSettings, docked, onDock, onClose }) {
34415
34544
  const t = useT();
34545
+ const dockLabel = docked === true ? t("header.undock") : t("header.dock");
34416
34546
  return /* @__PURE__ */ jsxs("header", {
34417
34547
  className: "flex h-14 shrink-0 items-center justify-between bg-background px-3",
34418
34548
  children: [/* @__PURE__ */ jsxs("div", {
34419
34549
  className: "flex min-w-0 items-center gap-1",
34420
- children: [/* @__PURE__ */ jsx("button", {
34421
- type: "button",
34422
- "aria-label": t("session.expand"),
34423
- title: t("session.expand"),
34424
- "data-testid": "chatbot-sessions-button",
34425
- onClick: onOpenSessions,
34426
- className: "webskill-aui-sessions-button inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34427
- children: /* @__PURE__ */ jsx(PanelLeft, {
34428
- className: "size-4",
34429
- "aria-hidden": true
34430
- })
34431
- }), title !== void 0 ? /* @__PURE__ */ jsx("span", {
34432
- className: "truncate px-2 text-sm font-medium text-ink",
34433
- children: title
34434
- }) : null]
34550
+ children: [
34551
+ /* @__PURE__ */ jsx("button", {
34552
+ type: "button",
34553
+ "aria-label": t("session.expand"),
34554
+ title: t("session.expand"),
34555
+ "data-testid": "chatbot-sessions-button",
34556
+ onClick: onOpenSessions,
34557
+ className: "webskill-aui-sessions-button inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34558
+ children: /* @__PURE__ */ jsx(PanelLeft, {
34559
+ className: "size-4",
34560
+ "aria-hidden": true
34561
+ })
34562
+ }),
34563
+ onDock ? /* @__PURE__ */ jsx("button", {
34564
+ type: "button",
34565
+ "data-testid": "header-dock",
34566
+ "aria-label": dockLabel,
34567
+ "aria-pressed": docked === true,
34568
+ title: dockLabel,
34569
+ onClick: () => onDock(docked !== true),
34570
+ className: "inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34571
+ children: docked === true ? /* @__PURE__ */ jsx(PinOff, {
34572
+ className: "size-4",
34573
+ "aria-hidden": true
34574
+ }) : /* @__PURE__ */ jsx(Pin, {
34575
+ className: "size-4",
34576
+ "aria-hidden": true
34577
+ })
34578
+ }) : null,
34579
+ title !== void 0 ? /* @__PURE__ */ jsx("span", {
34580
+ className: "truncate px-2 text-sm font-medium text-ink",
34581
+ children: title
34582
+ }) : null
34583
+ ]
34435
34584
  }), /* @__PURE__ */ jsxs("div", {
34436
34585
  className: "flex items-center gap-1",
34437
- children: [/* @__PURE__ */ jsx("button", {
34438
- type: "button",
34439
- "data-testid": "header-new-chat",
34440
- "aria-label": t("session.new"),
34441
- title: t("session.new"),
34442
- onClick: onCreateSession,
34443
- className: "inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34444
- children: /* @__PURE__ */ jsx(Plus, {
34445
- className: "size-4",
34446
- "aria-hidden": true
34447
- })
34448
- }), onOpenSettings ? /* @__PURE__ */ jsx("button", {
34449
- type: "button",
34450
- "data-testid": "header-settings",
34451
- "aria-label": t("header.settings"),
34452
- title: t("header.settings"),
34453
- onClick: onOpenSettings,
34454
- className: "inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34455
- children: /* @__PURE__ */ jsx(Settings, {
34456
- className: "size-4",
34457
- "aria-hidden": true
34458
- })
34459
- }) : null]
34586
+ children: [
34587
+ /* @__PURE__ */ jsx("button", {
34588
+ type: "button",
34589
+ "data-testid": "header-new-chat",
34590
+ "aria-label": t("session.new"),
34591
+ title: t("session.new"),
34592
+ onClick: onCreateSession,
34593
+ className: "inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34594
+ children: /* @__PURE__ */ jsx(Plus, {
34595
+ className: "size-4",
34596
+ "aria-hidden": true
34597
+ })
34598
+ }),
34599
+ onOpenSettings ? /* @__PURE__ */ jsx("button", {
34600
+ type: "button",
34601
+ "data-testid": "header-settings",
34602
+ "aria-label": t("header.settings"),
34603
+ title: t("header.settings"),
34604
+ onClick: onOpenSettings,
34605
+ className: "inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34606
+ children: /* @__PURE__ */ jsx(Settings, {
34607
+ className: "size-4",
34608
+ "aria-hidden": true
34609
+ })
34610
+ }) : null,
34611
+ onClose ? /* @__PURE__ */ jsx("button", {
34612
+ type: "button",
34613
+ "data-testid": "header-close",
34614
+ "aria-label": t("header.close"),
34615
+ title: t("header.close"),
34616
+ onClick: onClose,
34617
+ className: "inline-flex size-8 items-center justify-center rounded-md text-muted hover:bg-subtle hover:text-ink",
34618
+ children: /* @__PURE__ */ jsx(X, {
34619
+ className: "size-4",
34620
+ "aria-hidden": true
34621
+ })
34622
+ }) : null
34623
+ ]
34460
34624
  })]
34461
34625
  });
34462
34626
  }
34463
- function AssistantUiEmptyState({ title, prompts, disabled, onPrompt }) {
34627
+ function AssistantUiEmptyState({ title, prompts, dynamicPrompts, limit, disabled, onPrompt }) {
34464
34628
  const t = useT();
34465
- const starterPrompts = prompts ?? [
34466
- t("welcome.prompt.1"),
34467
- t("welcome.prompt.2"),
34468
- t("welcome.prompt.3"),
34469
- t("welcome.prompt.4")
34470
- ];
34629
+ const locale = useLocale();
34630
+ const starterPrompts = mergeQuickPrompts(dynamicPrompts, prompts ?? [], locale, limit);
34471
34631
  return /* @__PURE__ */ jsx("div", {
34472
34632
  "data-testid": "assistant-ui-empty-state",
34473
34633
  className: "flex min-h-48 flex-col justify-center py-8 sm:min-h-72 sm:py-10",
@@ -34478,8 +34638,7 @@ function AssistantUiEmptyState({ title, prompts, disabled, onPrompt }) {
34478
34638
  children: title ?? t("welcome.assistantTitle")
34479
34639
  }), starterPrompts.length > 0 ? /* @__PURE__ */ jsx("div", {
34480
34640
  className: "grid w-full grid-cols-1 gap-2 pt-3 sm:grid-cols-2",
34481
- children: starterPrompts.map((entry, index) => {
34482
- const item = typeof entry === "string" ? { text: entry } : entry;
34641
+ children: starterPrompts.map((item, index) => {
34483
34642
  const Icon = resolveQuickPromptIcon(item.icon);
34484
34643
  return /* @__PURE__ */ jsxs("button", {
34485
34644
  type: "button",
@@ -34496,6 +34655,34 @@ function AssistantUiEmptyState({ title, prompts, disabled, onPrompt }) {
34496
34655
  })
34497
34656
  });
34498
34657
  }
34658
+ /**
34659
+ * 对话进行中的动态快捷指令条(FR-17.6)。
34660
+ * 只接**动态**那一批:静态清单不随上下文变,常驻只会变成噪声。
34661
+ * 空列表时整条不渲染,DOM 里不留空容器。
34662
+ */
34663
+ function AssistantUiDynamicPrompts({ prompts, limit, onPrompt }) {
34664
+ const t = useT();
34665
+ const items = mergeQuickPrompts(prompts, [], useLocale(), limit);
34666
+ if (items.length === 0) return null;
34667
+ return /* @__PURE__ */ jsx("div", {
34668
+ "data-testid": "chatbot-dynamic-prompts",
34669
+ "aria-label": t("welcome.dynamicPrompts"),
34670
+ className: "flex gap-2 overflow-x-auto px-4 pb-2",
34671
+ children: items.map((item, index) => {
34672
+ const Icon = resolveQuickPromptIcon(item.icon);
34673
+ return /* @__PURE__ */ jsxs("button", {
34674
+ type: "button",
34675
+ "data-testid": "chatbot-dynamic-prompt",
34676
+ onClick: () => onPrompt(item.text),
34677
+ className: "inline-flex shrink-0 items-center gap-1.5 rounded-full border border-border bg-card px-3 py-1.5 text-xs text-muted transition-colors hover:bg-subtle hover:text-ink",
34678
+ children: [Icon ? /* @__PURE__ */ jsx(Icon, {
34679
+ className: "size-3.5 shrink-0",
34680
+ "aria-hidden": true
34681
+ }) : null, item.text]
34682
+ }, `${index}:${item.text}`);
34683
+ })
34684
+ });
34685
+ }
34499
34686
 
34500
34687
  //#endregion
34501
34688
  //#region src/react/format.ts
@@ -34649,12 +34836,10 @@ const DEFAULT_DICTATION_LANG = {
34649
34836
  zh: "zh-CN"
34650
34837
  };
34651
34838
  /**
34652
- * 三个图标共用一套外观规则(FR-28.4):明暗只由 supported 决定,
34653
- * 交互可供性由 interactive 决定——能力是状态,取像是动作。
34839
+ * 两个图标共用一套外观(FR-28.4):明暗只由 supported 决定。
34654
34840
  */
34655
- function CAPABILITY_ICON_CLASS(supported, interactive) {
34656
- const base = `inline-flex size-8 shrink-0 items-center justify-center rounded-full ${supported ? "text-ink" : "text-muted"}`;
34657
- return interactive ? `${base} transition-colors hover:bg-accent hover:text-ink disabled:cursor-not-allowed disabled:hover:bg-transparent` : base;
34841
+ function CAPABILITY_ICON_CLASS(supported) {
34842
+ return `inline-flex size-8 shrink-0 items-center justify-center rounded-full ${supported ? "text-ink" : "text-muted"}`;
34658
34843
  }
34659
34844
  function AttachmentMeta({ info }) {
34660
34845
  const t = useT();
@@ -34714,16 +34899,14 @@ function ModelMenuButton({ models }) {
34714
34899
  }, option.id))
34715
34900
  })] });
34716
34901
  }
34717
- function AssistantUiComposer({ running, disabled = false, waitTarget, placeholder, attachments = false, attachmentInfo, attachmentImages, centered = false, models, noModel, dictationLang, pageCapture, capabilities }) {
34902
+ function AssistantUiComposer({ running, disabled = false, waitTarget, placeholder, attachments = false, attachmentInfo, attachmentImages, centered = false, models, noModel, dictationLang, capabilities }) {
34718
34903
  const t = useT();
34719
34904
  const locale = useLocale();
34720
34905
  const dictation = useComposerDictation(dictationLang ?? DEFAULT_DICTATION_LANG[locale]);
34721
34906
  const dictationReason = dictation.availability.available ? void 0 : t(`composer.dictate.${dictation.availability.reason}`);
34722
34907
  const waitReason = disabled ? t("interaction.wait", { target: waitTarget ?? t("interaction.wait.fallback") }) : void 0;
34723
34908
  const attachmentReason = attachmentImages === void 0 || attachmentImages.enabled && attachmentImages.imageCapable ? void 0 : attachmentImages.enabled ? t("composer.addAttachment.modelNoImages") : t("composer.addAttachment.imagesOff");
34724
- const pageCaptureReason = pageCapture === void 0 || pageCapture.enabled && pageCapture.imageCapable ? void 0 : pageCapture.enabled ? t("composer.pageCapture.disabled.model") : t("composer.pageCapture.disabled.setting");
34725
34909
  const plainChatHintId = useId();
34726
- const pageCaptureHintId = useId();
34727
34910
  return /* @__PURE__ */ jsx(ComposerPrimitive.Root, {
34728
34911
  "data-testid": "assistant-ui-composer",
34729
34912
  "data-centered": centered,
@@ -34823,7 +35006,7 @@ function AssistantUiComposer({ running, disabled = false, waitTarget, placeholde
34823
35006
  "aria-label": t(capabilities.tools ? "capability.tools.on" : "capability.tools.off"),
34824
35007
  title: capabilities.tools ? t("capability.tools.on") : `${t("capability.tools.off")} — ${t("settings.capability.plainChat")}`,
34825
35008
  ...capabilities.tools ? {} : { "aria-describedby": plainChatHintId },
34826
- className: CAPABILITY_ICON_CLASS(capabilities.tools, false),
35009
+ className: CAPABILITY_ICON_CLASS(capabilities.tools),
34827
35010
  children: capabilities.tools ? /* @__PURE__ */ jsx(Wrench, {
34828
35011
  className: "size-4",
34829
35012
  "aria-hidden": true
@@ -34842,7 +35025,7 @@ function AssistantUiComposer({ running, disabled = false, waitTarget, placeholde
34842
35025
  "data-supported": capabilities.image,
34843
35026
  "aria-label": t(capabilities.image ? "capability.images.on" : "capability.images.off"),
34844
35027
  title: t(capabilities.image ? "capability.images.on" : "capability.images.off"),
34845
- className: CAPABILITY_ICON_CLASS(capabilities.image, false),
35028
+ className: CAPABILITY_ICON_CLASS(capabilities.image),
34846
35029
  children: capabilities.image ? /* @__PURE__ */ jsx(Image, {
34847
35030
  className: "size-4",
34848
35031
  "aria-hidden": true
@@ -34852,29 +35035,6 @@ function AssistantUiComposer({ running, disabled = false, waitTarget, placeholde
34852
35035
  })
34853
35036
  })
34854
35037
  ]
34855
- }) : null,
34856
- pageCapture ? /* @__PURE__ */ jsx("button", {
34857
- type: "button",
34858
- "data-testid": "capability-page-capture",
34859
- "data-supported": pageCaptureReason === void 0,
34860
- "aria-label": t("composer.pageCapture"),
34861
- title: pageCaptureReason === void 0 ? t("composer.pageCapture") : `${t("composer.pageCapture")} — ${pageCaptureReason}`,
34862
- disabled: pageCaptureReason !== void 0,
34863
- ...pageCaptureReason !== void 0 ? { "aria-describedby": pageCaptureHintId } : {},
34864
- onClick: pageCapture.onSelect,
34865
- className: CAPABILITY_ICON_CLASS(pageCaptureReason === void 0, true),
34866
- children: pageCaptureReason === void 0 ? /* @__PURE__ */ jsx(Monitor, {
34867
- className: "size-4",
34868
- "aria-hidden": true
34869
- }) : /* @__PURE__ */ jsx(MonitorOff, {
34870
- className: "size-4",
34871
- "aria-hidden": true
34872
- })
34873
- }) : null,
34874
- pageCapture && pageCaptureReason !== void 0 ? /* @__PURE__ */ jsx("span", {
34875
- id: pageCaptureHintId,
34876
- className: "sr-only",
34877
- children: pageCaptureReason
34878
35038
  }) : null
34879
35039
  ]
34880
35040
  }), /* @__PURE__ */ jsxs("div", {
@@ -46563,13 +46723,13 @@ const IMAGE_MIME_SET = new Set(IMAGE_MIME_TYPES);
46563
46723
  const FILE_MIME_TYPES = ["application/pdf"];
46564
46724
  const FILE_MIME_SET = new Set(FILE_MIME_TYPES);
46565
46725
  /**
46566
- * 需要**客户端先抽取成文本**才能外发的类型(FR-23.9)。
46567
- * 不走直通的 `file`:docx 发给 provider 会被当成二进制垃圾。
46726
+ * 需要**客户端先抽取成文本**才能外发的类型(FR-23.9 / 0.13.0 FR-12.5)。
46727
+ * 不走直通的 `file`:docx / xlsx 发给 provider 会被当成二进制垃圾。
46568
46728
  */
46569
- const EXTRACTED_MIME_TYPES = ["application/vnd.openxmlformats-officedocument.wordprocessingml.document"];
46570
- const EXTRACTED_MIME_SET = new Set(EXTRACTED_MIME_TYPES);
46571
- const EXTRACTED_EXTENSIONS = ["docx"];
46572
- const EXTRACTED_EXTENSION_SET = new Set(EXTRACTED_EXTENSIONS);
46729
+ const DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
46730
+ const XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
46731
+ const EXTRACTED_MIME_SET = /* @__PURE__ */ new Set([DOCX_MIME, XLSX_MIME]);
46732
+ const EXTRACTED_EXTENSION_SET = /* @__PURE__ */ new Set(["docx", "xlsx"]);
46573
46733
  function extensionOf(fileName) {
46574
46734
  return fileName.split(".").pop()?.toLowerCase() ?? "";
46575
46735
  }
@@ -46598,7 +46758,8 @@ function attachmentAccept(options) {
46598
46758
  const parts = [...TEXT_ACCEPT, ...TEXT_EXTENSIONS.map((ext) => `.${ext}`)];
46599
46759
  if (options.images) parts.push(...IMAGE_MIME_TYPES);
46600
46760
  if (options.files) parts.push(...FILE_MIME_TYPES);
46601
- if (options.documents) parts.push(...EXTRACTED_MIME_TYPES, ...EXTRACTED_EXTENSIONS.map((ext) => `.${ext}`));
46761
+ if (options.documents) parts.push(DOCX_MIME, ".docx");
46762
+ if (options.spreadsheets) parts.push(XLSX_MIME, ".xlsx");
46602
46763
  return parts.join(",");
46603
46764
  }
46604
46765
 
@@ -46607,17 +46768,19 @@ function attachmentAccept(options) {
46607
46768
  /** 非图片附件无法压缩,沿用固定上限;图片走 `RuntimeMultimodalConfig.maxImageBytes` */
46608
46769
  const MAX_BYTES = 10 * 1024 * 1024;
46609
46770
  const MAX_TEXT_CHARS = 32 * 1024;
46610
- const DEFAULT_MULTIMODAL = {
46611
- imageAttachments: false,
46612
- pageImageCapture: false,
46613
- maxImageBytes: MAX_BYTES,
46614
- maxImagesPerMessage: 5
46615
- };
46771
+ /** 0.13.0 FR-13.3:兜底值必须与 SDK 默认配置同源,各自写一份字面量必然静默漂移 */
46772
+ const DEFAULT_MULTIMODAL = defaultRuntimeConfig().multimodal;
46616
46773
  /** 去掉路径分隔符与 `..`,防止写出附件目录 */
46617
46774
  function safeName(name) {
46618
46775
  const cleaned = (name.split(/[\\/]/).pop() ?? "attachment").replace(/[^\w.\- ]+/g, "_").replace(/^\.+/, "");
46619
46776
  return cleaned === "" ? "attachment" : cleaned.slice(0, 80);
46620
46777
  }
46778
+ /** 两种待抽取格式共用 `document-text` 这一种 kind,选抽取器时要按格式再分一次 */
46779
+ function isSpreadsheet(file) {
46780
+ const type = file.type.toLowerCase();
46781
+ if (type !== "") return type === SUPPORTED_DOCUMENT_MIME.xlsx;
46782
+ return file.name.toLowerCase().endsWith(".xlsx");
46783
+ }
46621
46784
  /**
46622
46785
  * 附件落盘到 `<attachmentsRoot>/<sessionId>/`,`ChatEngine.send` 再按路径读回构造 `LlmContentPart[]`。
46623
46786
  * UI 只持有元数据,正文的唯一真相是存储。
@@ -46634,7 +46797,8 @@ var WebSkillAttachmentAdapter = class {
46634
46797
  return attachmentAccept({
46635
46798
  images: this.#imagesAllowed(),
46636
46799
  files: true,
46637
- documents: this.#options.docxExtractor !== void 0
46800
+ documents: this.#options.docxExtractor !== void 0,
46801
+ spreadsheets: this.#options.xlsxExtractor !== void 0
46638
46802
  });
46639
46803
  }
46640
46804
  #multimodal() {
@@ -46643,13 +46807,16 @@ var WebSkillAttachmentAdapter = class {
46643
46807
  #imagesAllowed() {
46644
46808
  return this.#multimodal().imageAttachments && (this.#options.imageCapable?.() ?? false);
46645
46809
  }
46810
+ #extractorFor(file) {
46811
+ return isSpreadsheet(file) ? this.#options.xlsxExtractor : this.#options.docxExtractor;
46812
+ }
46646
46813
  async add({ file }) {
46647
46814
  const kind = classifyAttachment(file.type, file.name);
46648
46815
  if (kind === void 0) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", `Attachment "${file.name}" has an unsupported type "${file.type === "" ? "unknown" : file.type}"`);
46649
46816
  if (kind === "image") {
46650
46817
  if (!this.#multimodal().imageAttachments) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", "Image attachments are turned off. Enable them in settings before sending images.");
46651
46818
  if (!(this.#options.imageCapable?.() ?? false)) throw new WebSkillError("MODEL_IMAGE_UNSUPPORTED", "Current model does not support image input. Switch model or remove the image.");
46652
- } else if (kind === "document-text" && this.#options.docxExtractor === void 0) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", "Word documents are not supported in this environment: no docx extractor is wired up.");
46819
+ } else if (kind === "document-text" && this.#extractorFor(file) === void 0) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", `${isSpreadsheet(file) ? "Excel workbooks" : "Word documents"} are not supported in this environment: no ${isSpreadsheet(file) ? "xlsx" : "docx"} extractor is wired up.`);
46653
46820
  else if (file.size > MAX_BYTES) throw new WebSkillError("ATTACHMENT_TOO_LARGE", `Attachment "${file.name}" is ${Math.round(file.size / 1024 / 1024)} MB, which exceeds the 10 MB limit`);
46654
46821
  const id = `att-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
46655
46822
  const relative = `${this.#options.sessionId() ?? "pending"}/${id}-${safeName(file.name)}`;
@@ -46692,8 +46859,8 @@ var WebSkillAttachmentAdapter = class {
46692
46859
  };
46693
46860
  }
46694
46861
  if (kind === "document-text") {
46695
- const extract = this.#options.docxExtractor;
46696
- if (extract === void 0) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", "No docx extractor is wired up in this environment.");
46862
+ const extract = this.#extractorFor(file);
46863
+ if (extract === void 0) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", "No document text extractor is wired up in this environment.");
46697
46864
  await this.#options.storage.writeText(path, await extract(new Uint8Array(await file.arrayBuffer())));
46698
46865
  return {
46699
46866
  ...base,
@@ -47666,8 +47833,35 @@ function LocalizedUndoToastProvider({ children }) {
47666
47833
  });
47667
47834
  }
47668
47835
  const toErrorMessage = (e) => e instanceof Error ? e.message : String(e);
47669
- /** 「读取页面画面」菜单项发出的提示词:说清要的是画面,模型才会走取像而非纯文本抽取 */
47670
- const PAGE_CAPTURE_PROMPT = "Read the current page view, including the images on it, and tell me what you see.";
47836
+ /** console 的快捷指令条目同款 id:时间戳 + 随机后缀 */
47837
+ const newQuickPromptId = () => `qp-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 6)}`;
47838
+ /**
47839
+ * 宿主种子只注入一次(0.13.0 分册 21 FR-21.6)。
47840
+ * 写盘会触发 subscribe 再跑一遍装载,届时 `quickPromptsSeeded` 已为 true,不会二次写入。
47841
+ * 宿主没传种子(`undefined`)时什么都不做——不该为「宿主没配」产生一次持久化;
47842
+ * 传空数组是「本应用不要快捷指令」,照样落盘打标记(FR-25.4)。
47843
+ * 返回已注入的条目(可能是空数组),调用方要立刻拿去更新界面:写盘是 fire-and-forget。
47844
+ */
47845
+ function seedQuickPrompts(store, config, seed) {
47846
+ if (config.quickPromptsSeeded || seed === void 0) return void 0;
47847
+ const entries = [];
47848
+ for (const item of seed) {
47849
+ const text = normalizeLocalizedText(typeof item === "string" ? item : item.text);
47850
+ if (Object.keys(text).length === 0) continue;
47851
+ const icon = typeof item === "string" ? void 0 : item.icon;
47852
+ entries.push({
47853
+ id: newQuickPromptId(),
47854
+ text,
47855
+ ...icon !== void 0 ? { icon } : {}
47856
+ });
47857
+ }
47858
+ store.save({
47859
+ ...config,
47860
+ quickPrompts: entries,
47861
+ quickPromptsSeeded: true
47862
+ }).catch(() => void 0);
47863
+ return entries;
47864
+ }
47671
47865
  /** 读 artifact 存储并触发浏览器下载(Blob + a[download]) */
47672
47866
  async function downloadArtifactFile(storage, chatRoot, file) {
47673
47867
  const fullPath = `${chatRoot}/artifacts/${file.runId ?? ""}/${file.path}`;
@@ -47689,7 +47883,7 @@ async function downloadArtifactFile(storage, chatRoot, file) {
47689
47883
  * presentation shell and accessible thread primitives.
47690
47884
  * @stable
47691
47885
  */
47692
- function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, renderer: rendererProp, dictationLang: dictationLangProp, surfaceRegistry, layout = "auto", sessionList = "auto", onOpenSettings, onEngineReady }) {
47886
+ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, renderer: rendererProp, dictationLang: dictationLangProp, surfaceRegistry, layout = "auto", sessionList = "auto", onOpenSettings, docked, onDock, onClose, onEngineReady }) {
47693
47887
  const engine = useMemo(() => new ChatEngine(adapter, {
47694
47888
  ...config?.chatRoot ? { chatRoot: config.chatRoot } : {},
47695
47889
  ...config?.llm ? { llm: config.llm } : {},
@@ -47770,6 +47964,29 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
47770
47964
  });
47771
47965
  /** 当前进行中 run 的 part 累积器(流式正文 / 工具 / 生命周期步进的唯一来源) */
47772
47966
  const [live, setLive] = useState();
47967
+ /** RuntimeConfig 里快捷指令的镜像(FR-21.5):console 改完经 subscribe 回来 */
47968
+ const [storedQuickPrompts, setStoredQuickPrompts] = useState([]);
47969
+ /** 空态与动态指令条最多展示多少条(console 可改) */
47970
+ const [quickPromptLimit, setQuickPromptLimit] = useState(8);
47971
+ /** 种子的 ref 镜像:props 每次渲染都是新数组,进 effect 依赖表会让配置订阅反复重建 */
47972
+ const seedRef = useRef(config?.quickPrompts);
47973
+ seedRef.current = config?.quickPrompts;
47974
+ /**
47975
+ * 静态清单(FR-21.6 / FR-25.2):存储里有什么就是什么。
47976
+ * 未接配置存储的宿主用 props;接了存储就以存储为准,两者皆空即一张卡都不渲染——
47977
+ * 内置示例已下线(分册 25),chatbot 显示的每一条都要在 console 的快捷指令页里管得到。
47978
+ */
47979
+ const staticQuickPrompts = useMemo(() => {
47980
+ if (storedQuickPrompts.length > 0) return storedQuickPrompts.map(({ text, icon }) => ({
47981
+ text,
47982
+ ...icon !== void 0 ? { icon } : {}
47983
+ }));
47984
+ return configStore === void 0 ? config?.quickPrompts : [];
47985
+ }, [
47986
+ storedQuickPrompts,
47987
+ config?.quickPrompts,
47988
+ configStore
47989
+ ]);
47773
47990
  const [interactionPending, setInteractionPending] = useState(false);
47774
47991
  /** 等待对象名称(interaction-requested 携带):等待指示器要说清在等什么 */
47775
47992
  const [interactionLabel, setInteractionLabel] = useState();
@@ -47823,7 +48040,8 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
47823
48040
  sessionId: () => sessionRef.current,
47824
48041
  multimodal: () => multimodalRef.current,
47825
48042
  imageCapable: () => imageCapableRef.current,
47826
- ...adapter.docxExtractor ? { docxExtractor: adapter.docxExtractor } : {}
48043
+ ...adapter.docxExtractor ? { docxExtractor: adapter.docxExtractor } : {},
48044
+ ...adapter.xlsxExtractor ? { xlsxExtractor: adapter.xlsxExtractor } : {}
47827
48045
  }), [adapter, engine]);
47828
48046
  useEffect(() => {
47829
48047
  let cancelled = false;
@@ -47850,6 +48068,9 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
47850
48068
  ...rc.llm.defaultId ? { defaultId: rc.llm.defaultId } : {}
47851
48069
  });
47852
48070
  setLlmSelectionLoaded(true);
48071
+ const seeded = seedQuickPrompts(merged, rc, seedRef.current);
48072
+ setStoredQuickPrompts(seeded ?? rc.quickPrompts);
48073
+ setQuickPromptLimit(rc.quickPromptLimit);
47853
48074
  multimodalRef.current = rc.multimodal;
47854
48075
  setMultimodal(rc.multimodal);
47855
48076
  setLoopLimits({
@@ -48087,6 +48308,7 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48087
48308
  setCurrentSessionId(id);
48088
48309
  setMessages(history.items);
48089
48310
  setMessagesCursor(history.nextCursor);
48311
+ setError(void 0);
48090
48312
  setModelId(engine.modelId);
48091
48313
  }).catch((e) => setError({ message: toErrorMessage(e) }));
48092
48314
  }, [engine, flushPendingDeletes]);
@@ -48146,6 +48368,7 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48146
48368
  setCurrentSessionId(meta.id);
48147
48369
  setMessages([]);
48148
48370
  setMessagesCursor(void 0);
48371
+ setError(void 0);
48149
48372
  setModelId(engine.modelId);
48150
48373
  await refreshSessions();
48151
48374
  }).catch((e) => setError({ message: toErrorMessage(e) }));
@@ -48181,6 +48404,7 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48181
48404
  if (id === currentSessionId) {
48182
48405
  setCurrentSessionId(void 0);
48183
48406
  setMessages([]);
48407
+ setError(void 0);
48184
48408
  }
48185
48409
  await refreshSessions();
48186
48410
  }).catch((e) => setError({ message: toErrorMessage(e) }));
@@ -48246,7 +48470,10 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48246
48470
  ...config?.title ? { title: config.title } : {},
48247
48471
  onOpenSessions: () => setSessionsOpen(true),
48248
48472
  onCreateSession: handleCreate,
48249
- ...onOpenSettings ? { onOpenSettings: () => onOpenSettings() } : {}
48473
+ ...onOpenSettings ? { onOpenSettings: () => onOpenSettings() } : {},
48474
+ ...docked !== void 0 ? { docked } : {},
48475
+ ...onDock ? { onDock } : {},
48476
+ ...onClose ? { onClose } : {}
48250
48477
  }),
48251
48478
  /* @__PURE__ */ jsx(InterruptedBanner, {
48252
48479
  engine,
@@ -48262,7 +48489,9 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48262
48489
  children: [
48263
48490
  messages.length === 0 && !sending ? /* @__PURE__ */ jsx(AssistantUiEmptyState, {
48264
48491
  ...config?.title ? { title: config.title } : {},
48265
- ...config?.quickPrompts ? { prompts: config.quickPrompts } : {},
48492
+ ...staticQuickPrompts ? { prompts: staticQuickPrompts } : {},
48493
+ ...config?.dynamicQuickPrompts ? { dynamicPrompts: config.dynamicQuickPrompts } : {},
48494
+ limit: quickPromptLimit,
48266
48495
  disabled: sending,
48267
48496
  onPrompt: handleSend
48268
48497
  }) : null,
@@ -48328,6 +48557,11 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48328
48557
  } : {},
48329
48558
  onLocate: handleLocateInteraction
48330
48559
  }),
48560
+ messages.length > 0 && !sending && config?.dynamicQuickPrompts ? /* @__PURE__ */ jsx(AssistantUiDynamicPrompts, {
48561
+ prompts: config.dynamicQuickPrompts,
48562
+ limit: quickPromptLimit,
48563
+ onPrompt: handleSend
48564
+ }) : null,
48331
48565
  /* @__PURE__ */ jsx(AssistantUiComposer, {
48332
48566
  ...modelCapabilities ? { capabilities: {
48333
48567
  tools: modelCapabilities.tools,
@@ -48343,11 +48577,6 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48343
48577
  enabled: multimodal.imageAttachments,
48344
48578
  imageCapable: modelTakesImages
48345
48579
  },
48346
- pageCapture: {
48347
- enabled: multimodal.pageImageCapture,
48348
- imageCapable: modelTakesImages,
48349
- onSelect: () => handleSend(PAGE_CAPTURE_PROMPT)
48350
- },
48351
48580
  ...dictationLang !== "" ? { dictationLang } : {},
48352
48581
  ...composerModels && !noUsableModel ? { models: composerModels } : {},
48353
48582
  ...noUsableModel ? { noModel: { onConfigure: () => setNoModelGuideOpen(true) } } : {},
@@ -48425,7 +48654,7 @@ function VercelPayloadPreview({ bridge }) {
48425
48654
  * Version of the published `@webskill/chatbot` package, injected at build time.
48426
48655
  * @stable
48427
48656
  */
48428
- const CHATBOT_VERSION = "0.8.0";
48657
+ const CHATBOT_VERSION = "0.10.0";
48429
48658
 
48430
48659
  //#endregion
48431
48660
  export { A2uiSurfaceHost, A2uiSurfaceSnapshotHost, CHATBOT_VERSION, ChatEngine, Chatbot, CompositeUiBridge, DEFAULT_RENDERER_CAPABILITIES, DEFAULT_RUNTIME_CONFIG_STORAGE_KEY, ErrorCard, InteractionCard, InterruptedBanner, OpenUiSurfaceHost, OpenUiSurfaceSnapshotHost, ResultBlockList, ResultBlocksPro, SkillBadges, SpecInteraction, VercelPayloadPreview, VercelSurfaceHost, VercelSurfaceSnapshotHost, chatbotDictionary, configureA2uiMarkdown, createLocalStorageRuntimeConfigStore, createMemoryRuntimeConfigStore, isLlmEntryUsable, pickUsableLlmEntry, probeA2uiAvailability, probeOpenUiAvailability, sandboxExecutorDeps, useT };