@webskill/chatbot 0.18.0 → 0.19.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/chatbot.css +15 -0
- package/dist/config.d.ts +2 -2
- package/dist/config.js +2 -2
- package/dist/index-DhXFYp0d.d.ts +1417 -0
- package/dist/index.d.ts +152 -8
- package/dist/index.js +1264 -322
- package/dist/{parse-Bo68XTn_.js → parse-BtdRevDk.js} +1 -1
- package/dist/{parse-B8D0fl8I.d.ts → parse-Cu0G_3gS.d.ts} +1 -1
- package/dist/{types-C4dzGOPp.js → types-D6kVxBO9.js} +15 -5
- package/dist/vite.d.ts +1 -1
- package/dist/vite.js +1 -1
- package/package.json +7 -3
- package/dist/index-Nnkn73aD.d.ts +0 -398
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as __toESM, r as __require, t as __commonJSMin } from "./rolldown-runtime-DZ8SGSJo.js";
|
|
2
|
-
import { i as withRuntimeConfigDefaults, n as defaultRuntimeConfig, o as mergeQuickPrompts, r as mergeRuntimeConfigDefaults, s as normalizeLocalizedText } from "./types-
|
|
2
|
+
import { i as withRuntimeConfigDefaults, n as defaultRuntimeConfig, o as mergeQuickPrompts, r as mergeRuntimeConfigDefaults, s as normalizeLocalizedText } from "./types-D6kVxBO9.js";
|
|
3
3
|
import { a as svg, i as html$1, n as refractor, o as find, r as stringify$1, s as stringify } from "./core-D3Tq37jA.js";
|
|
4
4
|
import { t as require_core } from "./core-DBetA176.js";
|
|
5
5
|
import { t as markup } from "./markup-D2NSmoAx.js";
|
|
@@ -19,11 +19,11 @@ import { fromMarkdown } from "mdast-util-from-markdown";
|
|
|
19
19
|
import { gfmFromMarkdown, gfmToMarkdown } from "mdast-util-gfm";
|
|
20
20
|
import { gfm } from "micromark-extension-gfm";
|
|
21
21
|
import { ActionBarPrimitive, AttachmentPrimitive, AuiProvider, ComposerPrimitive, ExternalThread, INTERNAL, MessagePrimitive, ThreadListItemPrimitive, ThreadListPrimitive, ThreadPrimitive, useAui, useAuiState, useMessagePartText } from "@assistant-ui/react";
|
|
22
|
-
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, assertSafePathSegment, atomicWriteText, diffUserProfile, exportUserProfile, formatAttachmentText, isUnsupportedRunSnapshot, messageOf, readBehaviorRecords, readUserProfile, refineUserProfile, resolveInsideRoot, sanitizeUntrustedLine, summarizeRunUsage, summarizeToolCalls, textParts } from "@webskill/sdk";
|
|
23
|
-
import { BrowserWorkerScriptExecutor, WebOfficePolicy, capturePhoto, checkCameraAvailability, checkDictationAvailability, compressImageToBudget, createEncryptedMemoryStore, createLlmClient, createWebOfficeToolSource, deleteMemoryEncryptionKey, openCamera, openMemoryEncryptionKey, probeChromeBuiltinAvailability, startDictation } from "@webskill/sdk/browser";
|
|
22
|
+
import { BEHAVIOR_RECORDS_KEY, DEFAULT_LOOP_LIMITS, DEFAULT_USER_PROFILE_LIMITS, EventBus, FsArtifactStore, FsMemoryStore, FsRunSnapshotStore, FsRunTraceStore, FsSessionStore, FsToolStepStore, FullDisclosureRouter, SPREADSHEET_LIMITS, SUPPORTED_DOCUMENT_MIME, SerializingMemoryStore, SkillDiscovery, USER_PROFILE_KEY, WebSkillError, WebSkillRuntime, applyUserProfileImport, assertRemoteUrlAllowed, assertSafePathSegment, atomicWriteText, diffUserProfile, exportUserProfile, extractedDocumentFormat, formatAttachmentText, isUnsupportedRunSnapshot, messageOf, readBehaviorRecords, readUserProfile, refineUserProfile, resolveInsideRoot, sanitizeUntrustedLine, summarizeRunUsage, summarizeToolCalls, textParts } from "@webskill/sdk";
|
|
23
|
+
import { BrowserWorkerScriptExecutor, WebOfficePolicy, capturePhoto, checkCameraAvailability, checkDictationAvailability, compressImageToBudget, createEncryptedMemoryStore, createLlmClient, createWebOfficeToolSource, deleteMemoryEncryptionKey, openCamera, openMemoryEncryptionKey, probeChromeBuiltinAvailability, startDictation, toDocumentImage } from "@webskill/sdk/browser";
|
|
24
24
|
import { JsonRenderSpecSurface, NativeSpecSurface, OpenUiSpecSurface, ReactBridgeState, SpecInteractionChannel, SurfaceFormTextsProvider, UiSurfaceSnapshotList, probeJsonRenderAvailability } from "@webskill/sdk/ui-react";
|
|
25
25
|
import { A2UI_SPEC_FORM_PATH, collectFormScopes, createUiCatalogToolSource, fromA2uiSpecAction, fromA2uiSurfaceAction, interactionToFormModel, interactionToUiSpec, loadOpenUiPeers, loadWebSkillLitCatalog, renderMiniChart, shapeInteractionValue, toA2uiSpecMessages, toA2uiSurfaceAction, toVercelToolInvocation, uiCatalog } from "@webskill/sdk/ui";
|
|
26
|
-
import { DownloadedFilePolicy, PAGE_ACTION_KINDS, TodoStore, createDelegationToolSource, createDocumentToolSource, createDownloadedFileToolSource, createPageActionToolSource, createPagePerceptionToolSource, createSkillGenerationToolSource, createSpreadsheetToolSource, createTabWorksetToolSource, createTodoToolSource, withDelegationOrigin } from "@webskill/sdk/agent";
|
|
26
|
+
import { DownloadedFilePolicy, PAGE_ACTION_KINDS, TodoStore, createDelegationToolSource, createDocumentToolSource, createDownloadedFileToolSource, createDwgToolSource, createPageActionToolSource, createPageImageToolSource, createPagePerceptionToolSource, createRemoteImageToolSource, createSkillGenerationToolSource, createSpreadsheetToolSource, createTabWorksetToolSource, createTemplateToolSource, createTodoToolSource, withDelegationOrigin } from "@webskill/sdk/agent";
|
|
27
27
|
import { DataRenderers, Tools } from "@assistant-ui/core/react";
|
|
28
28
|
import { ModelContext, Suggestions } from "@assistant-ui/core/store";
|
|
29
29
|
import { Derived, attachTransformScopes, useClientLookup, useClientResource } from "@assistant-ui/store";
|
|
@@ -19599,6 +19599,284 @@ function defaultUrlTransform(value) {
|
|
|
19599
19599
|
return "";
|
|
19600
19600
|
}
|
|
19601
19601
|
|
|
19602
|
+
//#endregion
|
|
19603
|
+
//#region ../ui-kit/src/markdown/image.tsx
|
|
19604
|
+
/** `<img>` 里的 data URL 不会执行脚本,SVG 同理——放行的前提就是这个上下文 */
|
|
19605
|
+
const DATA_IMAGE = /^data:image\/[a-z0-9.+-]+[;,]/i;
|
|
19606
|
+
/**
|
|
19607
|
+
* 仓内图片引用。模型输出不了 base64,所以正文里贴附件图只能靠短引用,
|
|
19608
|
+
* 由宿主注入的解析器换成可显示的 URL。与投放通道的 `data-webskill-image` 同一套引用形态。
|
|
19609
|
+
*/
|
|
19610
|
+
const IMAGE_REF = /^(upload|artifact):/i;
|
|
19611
|
+
/**
|
|
19612
|
+
* react-markdown 的默认 `urlTransform` 只放行 `https?|ircs?|mailto|xmpp`,
|
|
19613
|
+
* 于是 `` 与 `` 的 src 都被剥成空串。
|
|
19614
|
+
*
|
|
19615
|
+
* 这里**只对 `<img src>` 补放行**:`<a href="data:image/svg+xml,…">` 点开是导航,
|
|
19616
|
+
* 那个上下文里 SVG 的脚本会执行,所以 href 仍走默认策略。
|
|
19617
|
+
*/
|
|
19618
|
+
const markdownUrlTransform = (url, key, node) => {
|
|
19619
|
+
if (key === "src" && node.tagName === "img" && (DATA_IMAGE.test(url) || IMAGE_REF.test(url))) return url;
|
|
19620
|
+
return defaultUrlTransform(url);
|
|
19621
|
+
};
|
|
19622
|
+
const ResolverContext = createContext(void 0);
|
|
19623
|
+
/**
|
|
19624
|
+
* 注入引用解析器。
|
|
19625
|
+
*
|
|
19626
|
+
* `resolve` **必须是稳定引用**(`useCallback` 且不依赖每次渲染新建的值),
|
|
19627
|
+
* 否则每次渲染都会重新解析一遍图片。
|
|
19628
|
+
*/
|
|
19629
|
+
function MarkdownImageResolverProvider({ resolve, children }) {
|
|
19630
|
+
return /* @__PURE__ */ jsx(ResolverContext.Provider, {
|
|
19631
|
+
value: resolve,
|
|
19632
|
+
children
|
|
19633
|
+
});
|
|
19634
|
+
}
|
|
19635
|
+
/**
|
|
19636
|
+
* 正文里的图片。外链、data URL、同源相对路径直接显示;`upload:` / `artifact:` 引用
|
|
19637
|
+
* 走宿主注入的解析器。
|
|
19638
|
+
*
|
|
19639
|
+
* `referrerPolicy="no-referrer"`:外链图会向第三方发请求,不必把当前会话的 URL 一起送出去。
|
|
19640
|
+
* 失败时退回 `alt` 文本而不是留破图——alt 由内容方给,天然随内容语言,不需要另配文案。
|
|
19641
|
+
*/
|
|
19642
|
+
function MarkdownImage({ node: _node, className, src, alt, ...rest }) {
|
|
19643
|
+
const resolve = useContext(ResolverContext);
|
|
19644
|
+
const [failed, setFailed] = useState(false);
|
|
19645
|
+
const [resolved, setResolved] = useState();
|
|
19646
|
+
const isRef = typeof src === "string" && IMAGE_REF.test(src);
|
|
19647
|
+
useEffect(() => {
|
|
19648
|
+
if (!isRef || typeof src !== "string") return;
|
|
19649
|
+
if (resolve === void 0) {
|
|
19650
|
+
setFailed(true);
|
|
19651
|
+
return;
|
|
19652
|
+
}
|
|
19653
|
+
let cancelled = false;
|
|
19654
|
+
let created;
|
|
19655
|
+
setFailed(false);
|
|
19656
|
+
setResolved(void 0);
|
|
19657
|
+
resolve(src).then((url) => {
|
|
19658
|
+
if (url === void 0) {
|
|
19659
|
+
if (!cancelled) setFailed(true);
|
|
19660
|
+
return;
|
|
19661
|
+
}
|
|
19662
|
+
created = url;
|
|
19663
|
+
if (cancelled) {
|
|
19664
|
+
if (url.startsWith("blob:")) URL.revokeObjectURL(url);
|
|
19665
|
+
return;
|
|
19666
|
+
}
|
|
19667
|
+
setResolved(url);
|
|
19668
|
+
}).catch(() => {
|
|
19669
|
+
if (!cancelled) setFailed(true);
|
|
19670
|
+
});
|
|
19671
|
+
return () => {
|
|
19672
|
+
cancelled = true;
|
|
19673
|
+
if (created?.startsWith("blob:")) URL.revokeObjectURL(created);
|
|
19674
|
+
};
|
|
19675
|
+
}, [
|
|
19676
|
+
isRef,
|
|
19677
|
+
resolve,
|
|
19678
|
+
src
|
|
19679
|
+
]);
|
|
19680
|
+
const effective = isRef ? resolved : src;
|
|
19681
|
+
const text = typeof alt === "string" ? alt : "";
|
|
19682
|
+
if (failed || effective === void 0 || effective === "") {
|
|
19683
|
+
if (isRef && !failed) return null;
|
|
19684
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
19685
|
+
className: "my-2 inline-flex items-center gap-1.5 rounded-md bg-subtle px-2 py-1 text-xs text-muted",
|
|
19686
|
+
children: [/* @__PURE__ */ jsx(ImageOff, {
|
|
19687
|
+
className: "size-3.5 shrink-0",
|
|
19688
|
+
"aria-hidden": true
|
|
19689
|
+
}), text === "" ? null : /* @__PURE__ */ jsx("span", { children: text })]
|
|
19690
|
+
});
|
|
19691
|
+
}
|
|
19692
|
+
return /* @__PURE__ */ jsx("img", {
|
|
19693
|
+
src: effective,
|
|
19694
|
+
alt: text,
|
|
19695
|
+
loading: "lazy",
|
|
19696
|
+
decoding: "async",
|
|
19697
|
+
referrerPolicy: "no-referrer",
|
|
19698
|
+
onError: () => setFailed(true),
|
|
19699
|
+
className: cn("my-2 max-h-96 max-w-full rounded-card border border-border object-contain", className),
|
|
19700
|
+
...rest
|
|
19701
|
+
});
|
|
19702
|
+
}
|
|
19703
|
+
|
|
19704
|
+
//#endregion
|
|
19705
|
+
//#region ../ui-kit/src/theme/useDarkAncestor.ts
|
|
19706
|
+
/**
|
|
19707
|
+
* 声明了主题的祖先:`data-theme` 由 ThemeScope 挂(亮暗都挂),`.dark` 是宿主页面惯用的暗色类名。
|
|
19708
|
+
* 两者一起选,`closest` 才会停在**最近**的那一层,而不是越过亮色实例去命中外层的暗色宿主。
|
|
19709
|
+
*/
|
|
19710
|
+
const THEME_ANCESTOR = "[data-theme], .dark";
|
|
19711
|
+
function readDark(node) {
|
|
19712
|
+
const scope = node.closest(THEME_ANCESTOR);
|
|
19713
|
+
if (!scope) return false;
|
|
19714
|
+
const declared = scope.getAttribute("data-theme");
|
|
19715
|
+
return declared === null ? true : declared === "dark";
|
|
19716
|
+
}
|
|
19717
|
+
/**
|
|
19718
|
+
* 判定挂载点**最近的**主题祖先是否处于暗色(0.6.0 FR-20.4)。
|
|
19719
|
+
*
|
|
19720
|
+
* 不用 `document.querySelector('.dark')`:那是全局查询,宿主页面是暗色时,
|
|
19721
|
+
* 嵌在暗色宿主里的亮色 SDK 实例也会被判成暗色(反之亦然)。
|
|
19722
|
+
*
|
|
19723
|
+
* 返回 ref 回调而不是接受 ref 对象:ref 对象的赋值不触发渲染,拿不到「已挂载」这个时机。
|
|
19724
|
+
*
|
|
19725
|
+
* 首次判定在 ref 回调里**同步**完成,不等 effect:消费方(如按主题分叉的 markdown
|
|
19726
|
+
* 高亮器)可能在首帧就固化选择,晚一帧就会永久停在亮色(设计 15-01 §1.2 假设 H-c)。
|
|
19727
|
+
*/
|
|
19728
|
+
function useDarkAncestor() {
|
|
19729
|
+
const scoped = useThemeValue();
|
|
19730
|
+
const [node, setNode] = useState(null);
|
|
19731
|
+
const [probed, setProbed] = useState(false);
|
|
19732
|
+
const attach = useCallback((next) => {
|
|
19733
|
+
setNode(next);
|
|
19734
|
+
if (next) setProbed(readDark(next));
|
|
19735
|
+
}, []);
|
|
19736
|
+
useEffect(() => {
|
|
19737
|
+
if (!node || scoped !== void 0) return;
|
|
19738
|
+
const update = () => setProbed(readDark(node));
|
|
19739
|
+
update();
|
|
19740
|
+
const observer = new MutationObserver(update);
|
|
19741
|
+
observer.observe(document.documentElement, {
|
|
19742
|
+
attributes: true,
|
|
19743
|
+
attributeFilter: ["class", "data-theme"],
|
|
19744
|
+
subtree: true
|
|
19745
|
+
});
|
|
19746
|
+
return () => observer.disconnect();
|
|
19747
|
+
}, [node, scoped]);
|
|
19748
|
+
return [scoped !== void 0 ? scoped === "dark" : probed, attach];
|
|
19749
|
+
}
|
|
19750
|
+
|
|
19751
|
+
//#endregion
|
|
19752
|
+
//#region ../ui-kit/src/markdown/mermaid.tsx
|
|
19753
|
+
/** 代码围栏的语言标签;两个渲染入口共用这一份判定 */
|
|
19754
|
+
const MERMAID_LANGUAGE = "mermaid";
|
|
19755
|
+
/**
|
|
19756
|
+
* 一条消息里常有多张图,共用同一个模块 promise,避免每个挂载点各发一次动态 import。
|
|
19757
|
+
* mermaid 是可选 peer:没装时这个 promise 一直是 rejected,各挂载点一律退回代码块。
|
|
19758
|
+
*/
|
|
19759
|
+
let mermaidModule;
|
|
19760
|
+
const loadMermaid = () => mermaidModule ??= import("mermaid");
|
|
19761
|
+
/** mermaid 主题变量 → 本仓语义 token。配色一律从这里取,不写死色值 */
|
|
19762
|
+
const MERMAID_COLOR_TOKENS = {
|
|
19763
|
+
background: "--color-canvas",
|
|
19764
|
+
primaryColor: "--color-subtle",
|
|
19765
|
+
primaryTextColor: "--color-ink",
|
|
19766
|
+
primaryBorderColor: "--color-border",
|
|
19767
|
+
secondaryColor: "--color-surface",
|
|
19768
|
+
tertiaryColor: "--color-card",
|
|
19769
|
+
lineColor: "--color-muted",
|
|
19770
|
+
textColor: "--color-ink"
|
|
19771
|
+
};
|
|
19772
|
+
/**
|
|
19773
|
+
* token 的值可能是 `oklch()` / `color-mix()`,而 mermaid 的调色靠 khroma,只认 rgb 一族。
|
|
19774
|
+
* 画进 1×1 画布再读像素,是唯一不依赖具体色彩空间的归一化方式。
|
|
19775
|
+
*/
|
|
19776
|
+
function readThemeColors(node) {
|
|
19777
|
+
if (typeof document === "undefined") return void 0;
|
|
19778
|
+
const styles = getComputedStyle(node);
|
|
19779
|
+
const raw = Object.entries(MERMAID_COLOR_TOKENS).map(([variable, token]) => [variable, styles.getPropertyValue(token).trim()]).filter(([, value]) => value !== "");
|
|
19780
|
+
if (raw.length === 0) return void 0;
|
|
19781
|
+
const canvas = document.createElement("canvas");
|
|
19782
|
+
canvas.width = 1;
|
|
19783
|
+
canvas.height = 1;
|
|
19784
|
+
const ctx = canvas.getContext("2d");
|
|
19785
|
+
if (!ctx) return void 0;
|
|
19786
|
+
const out = {};
|
|
19787
|
+
for (const [variable, value] of raw) {
|
|
19788
|
+
ctx.clearRect(0, 0, 1, 1);
|
|
19789
|
+
ctx.fillStyle = value;
|
|
19790
|
+
ctx.fillRect(0, 0, 1, 1);
|
|
19791
|
+
const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
|
|
19792
|
+
out[variable] = `rgba(${r ?? 0}, ${g ?? 0}, ${b ?? 0}, ${(a ?? 255) / 255})`;
|
|
19793
|
+
}
|
|
19794
|
+
return out;
|
|
19795
|
+
}
|
|
19796
|
+
/**
|
|
19797
|
+
* mermaid 的配置是**全局**的(不像 echarts 是实例级),所以每次渲染前都整份重设:
|
|
19798
|
+
* 这样亮暗两个 ThemeScope 同页时,各自渲染各自那一次的配置仍然是对的。
|
|
19799
|
+
*/
|
|
19800
|
+
function buildConfig(dark, node) {
|
|
19801
|
+
const themeVariables = node ? readThemeColors(node) : void 0;
|
|
19802
|
+
return {
|
|
19803
|
+
startOnLoad: false,
|
|
19804
|
+
securityLevel: "strict",
|
|
19805
|
+
htmlLabels: false,
|
|
19806
|
+
suppressErrorRendering: true,
|
|
19807
|
+
theme: dark ? "dark" : "default",
|
|
19808
|
+
...themeVariables ? { themeVariables } : {}
|
|
19809
|
+
};
|
|
19810
|
+
}
|
|
19811
|
+
/**
|
|
19812
|
+
* `click X href "…"` 即便在 strict 档也只是被 sanitize-url 过一遍,链接本身照样渲染。
|
|
19813
|
+
* 模型产出的图不该带跳转,所以插进 DOM 之前先把 href 摘干净。
|
|
19814
|
+
*/
|
|
19815
|
+
function stripDiagramLinks(svg) {
|
|
19816
|
+
if (typeof DOMParser === "undefined" || !svg.includes("<a")) return svg;
|
|
19817
|
+
const doc = new DOMParser().parseFromString(svg, "text/html");
|
|
19818
|
+
const anchors = doc.querySelectorAll("a");
|
|
19819
|
+
if (anchors.length === 0) return svg;
|
|
19820
|
+
for (const anchor of anchors) {
|
|
19821
|
+
anchor.removeAttribute("href");
|
|
19822
|
+
anchor.removeAttribute("xlink:href");
|
|
19823
|
+
anchor.removeAttribute("target");
|
|
19824
|
+
}
|
|
19825
|
+
return doc.body.innerHTML;
|
|
19826
|
+
}
|
|
19827
|
+
/**
|
|
19828
|
+
* 代码围栏里的 mermaid 图。两个 markdown 渲染入口共用本组件,
|
|
19829
|
+
* 差别只在 `fallback` 由各自的代码块呈现提供。
|
|
19830
|
+
*
|
|
19831
|
+
* 三种情况一律退回 `fallback`:宿主没装 mermaid、语法不合法、渲染抛错。
|
|
19832
|
+
* 不加「渲染失败」之类的提示——降级物就是图的源码,用户看得见那是什么
|
|
19833
|
+
* (与 `MarkdownImage` 退回 alt 同理)。
|
|
19834
|
+
*/
|
|
19835
|
+
function MermaidDiagram({ code, fallback, className }) {
|
|
19836
|
+
const [dark, attachTheme] = useDarkAncestor();
|
|
19837
|
+
const [svg, setSvg] = useState(void 0);
|
|
19838
|
+
const nodeRef = useRef(null);
|
|
19839
|
+
const seqRef = useRef(0);
|
|
19840
|
+
const idBase = `webskill-mermaid-${useId().replace(/[^a-zA-Z0-9_-]/g, "")}`;
|
|
19841
|
+
const attach = useCallback((node) => {
|
|
19842
|
+
nodeRef.current = node;
|
|
19843
|
+
attachTheme(node);
|
|
19844
|
+
}, [attachTheme]);
|
|
19845
|
+
useEffect(() => {
|
|
19846
|
+
let cancelled = false;
|
|
19847
|
+
(async () => {
|
|
19848
|
+
try {
|
|
19849
|
+
const mermaid = (await loadMermaid()).default;
|
|
19850
|
+
if (cancelled) return;
|
|
19851
|
+
mermaid.initialize(buildConfig(dark, nodeRef.current));
|
|
19852
|
+
if (!await mermaid.parse(code, { suppressErrors: true })) return;
|
|
19853
|
+
if (cancelled) return;
|
|
19854
|
+
const { svg: rendered } = await mermaid.render(`${idBase}-${++seqRef.current}`, code);
|
|
19855
|
+
if (!cancelled) setSvg(stripDiagramLinks(rendered));
|
|
19856
|
+
} catch {}
|
|
19857
|
+
})();
|
|
19858
|
+
return () => {
|
|
19859
|
+
cancelled = true;
|
|
19860
|
+
};
|
|
19861
|
+
}, [
|
|
19862
|
+
code,
|
|
19863
|
+
dark,
|
|
19864
|
+
idBase
|
|
19865
|
+
]);
|
|
19866
|
+
if (svg === void 0) return /* @__PURE__ */ jsx("div", {
|
|
19867
|
+
ref: attach,
|
|
19868
|
+
className: "contents",
|
|
19869
|
+
children: fallback
|
|
19870
|
+
});
|
|
19871
|
+
return /* @__PURE__ */ jsx("div", {
|
|
19872
|
+
ref: attach,
|
|
19873
|
+
"data-webskill-mermaid": "",
|
|
19874
|
+
"data-testid": "mermaid-diagram",
|
|
19875
|
+
className: cn("my-3 flex justify-center overflow-x-auto rounded-card border border-border bg-card p-3", className),
|
|
19876
|
+
dangerouslySetInnerHTML: { __html: svg }
|
|
19877
|
+
});
|
|
19878
|
+
}
|
|
19879
|
+
|
|
19602
19880
|
//#endregion
|
|
19603
19881
|
//#region ../../node_modules/.pnpm/@babel+runtime@7.29.7/node_modules/@babel/runtime/helpers/esm/objectWithoutPropertiesLoose.js
|
|
19604
19882
|
function _objectWithoutPropertiesLoose(r, e) {
|
|
@@ -23792,53 +24070,6 @@ var typescript_default = typescript;
|
|
|
23792
24070
|
//#region ../../node_modules/.pnpm/react-syntax-highlighter@16.1.1_react@19.2.8/node_modules/react-syntax-highlighter/dist/esm/languages/prism/yaml.js
|
|
23793
24071
|
var yaml_default = yaml;
|
|
23794
24072
|
|
|
23795
|
-
//#endregion
|
|
23796
|
-
//#region ../ui-kit/src/theme/useDarkAncestor.ts
|
|
23797
|
-
/**
|
|
23798
|
-
* 声明了主题的祖先:`data-theme` 由 ThemeScope 挂(亮暗都挂),`.dark` 是宿主页面惯用的暗色类名。
|
|
23799
|
-
* 两者一起选,`closest` 才会停在**最近**的那一层,而不是越过亮色实例去命中外层的暗色宿主。
|
|
23800
|
-
*/
|
|
23801
|
-
const THEME_ANCESTOR = "[data-theme], .dark";
|
|
23802
|
-
function readDark(node) {
|
|
23803
|
-
const scope = node.closest(THEME_ANCESTOR);
|
|
23804
|
-
if (!scope) return false;
|
|
23805
|
-
const declared = scope.getAttribute("data-theme");
|
|
23806
|
-
return declared === null ? true : declared === "dark";
|
|
23807
|
-
}
|
|
23808
|
-
/**
|
|
23809
|
-
* 判定挂载点**最近的**主题祖先是否处于暗色(0.6.0 FR-20.4)。
|
|
23810
|
-
*
|
|
23811
|
-
* 不用 `document.querySelector('.dark')`:那是全局查询,宿主页面是暗色时,
|
|
23812
|
-
* 嵌在暗色宿主里的亮色 SDK 实例也会被判成暗色(反之亦然)。
|
|
23813
|
-
*
|
|
23814
|
-
* 返回 ref 回调而不是接受 ref 对象:ref 对象的赋值不触发渲染,拿不到「已挂载」这个时机。
|
|
23815
|
-
*
|
|
23816
|
-
* 首次判定在 ref 回调里**同步**完成,不等 effect:消费方(如按主题分叉的 markdown
|
|
23817
|
-
* 高亮器)可能在首帧就固化选择,晚一帧就会永久停在亮色(设计 15-01 §1.2 假设 H-c)。
|
|
23818
|
-
*/
|
|
23819
|
-
function useDarkAncestor() {
|
|
23820
|
-
const scoped = useThemeValue();
|
|
23821
|
-
const [node, setNode] = useState(null);
|
|
23822
|
-
const [probed, setProbed] = useState(false);
|
|
23823
|
-
const attach = useCallback((next) => {
|
|
23824
|
-
setNode(next);
|
|
23825
|
-
if (next) setProbed(readDark(next));
|
|
23826
|
-
}, []);
|
|
23827
|
-
useEffect(() => {
|
|
23828
|
-
if (!node || scoped !== void 0) return;
|
|
23829
|
-
const update = () => setProbed(readDark(node));
|
|
23830
|
-
update();
|
|
23831
|
-
const observer = new MutationObserver(update);
|
|
23832
|
-
observer.observe(document.documentElement, {
|
|
23833
|
-
attributes: true,
|
|
23834
|
-
attributeFilter: ["class", "data-theme"],
|
|
23835
|
-
subtree: true
|
|
23836
|
-
});
|
|
23837
|
-
return () => observer.disconnect();
|
|
23838
|
-
}, [node, scoped]);
|
|
23839
|
-
return [scoped !== void 0 ? scoped === "dark" : probed, attach];
|
|
23840
|
-
}
|
|
23841
|
-
|
|
23842
24073
|
//#endregion
|
|
23843
24074
|
//#region ../ui-kit/src/markdown/prism.ts
|
|
23844
24075
|
const languages = {
|
|
@@ -24475,7 +24706,16 @@ const CODE_TOKEN_VARS = Object.fromEntries(Object.keys(PRISM_THEME.light).filter
|
|
|
24475
24706
|
//#region ../ui-kit/src/markdown/Markdown.tsx
|
|
24476
24707
|
function CodeBlock({ node: _node, className, children, ...rest }) {
|
|
24477
24708
|
const [dark, themeRef] = useDarkAncestor();
|
|
24478
|
-
const
|
|
24709
|
+
const match = /language-(\w+)/.exec(className ?? "");
|
|
24710
|
+
const language = resolveLanguage(match?.[1]);
|
|
24711
|
+
if (match?.[1] === "mermaid") return /* @__PURE__ */ jsx(MermaidDiagram, {
|
|
24712
|
+
code: String(children).replace(/\n$/, ""),
|
|
24713
|
+
fallback: /* @__PURE__ */ jsx("code", {
|
|
24714
|
+
className: "rounded-md bg-subtle px-1.5 py-0.5 font-mono text-ink",
|
|
24715
|
+
...rest,
|
|
24716
|
+
children
|
|
24717
|
+
})
|
|
24718
|
+
});
|
|
24479
24719
|
if (language) return /* @__PURE__ */ jsxs("div", {
|
|
24480
24720
|
ref: themeRef,
|
|
24481
24721
|
className: "my-3 overflow-hidden rounded-card border border-border bg-card",
|
|
@@ -24502,35 +24742,42 @@ function CodeBlock({ node: _node, className, children, ...rest }) {
|
|
|
24502
24742
|
children
|
|
24503
24743
|
});
|
|
24504
24744
|
}
|
|
24745
|
+
/**
|
|
24746
|
+
* 模块级常量,**不可内联进 JSX**:react-markdown 按 `components` 的引用决定是否重建子树,
|
|
24747
|
+
* 每次渲染换新对象会让图片组件卸载重挂,于是流式输出期间同一张图被反复解析。
|
|
24748
|
+
*/
|
|
24749
|
+
const MARKDOWN_COMPONENTS = {
|
|
24750
|
+
code: CodeBlock,
|
|
24751
|
+
img: MarkdownImage,
|
|
24752
|
+
p: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("p", {
|
|
24753
|
+
className: "my-2",
|
|
24754
|
+
...props
|
|
24755
|
+
}),
|
|
24756
|
+
ul: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("ul", {
|
|
24757
|
+
className: "my-2 list-disc space-y-1 pl-5",
|
|
24758
|
+
...props
|
|
24759
|
+
}),
|
|
24760
|
+
ol: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("ol", {
|
|
24761
|
+
className: "my-2 list-decimal space-y-1 pl-5",
|
|
24762
|
+
...props
|
|
24763
|
+
}),
|
|
24764
|
+
a: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("a", {
|
|
24765
|
+
className: "font-medium text-ink underline underline-offset-4 hover:text-muted",
|
|
24766
|
+
target: "_blank",
|
|
24767
|
+
rel: "noreferrer",
|
|
24768
|
+
...props
|
|
24769
|
+
}),
|
|
24770
|
+
pre: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("pre", {
|
|
24771
|
+
className: "my-0",
|
|
24772
|
+
...props
|
|
24773
|
+
})
|
|
24774
|
+
};
|
|
24505
24775
|
function Markdown({ children, className = "" }) {
|
|
24506
24776
|
return /* @__PURE__ */ jsx("div", {
|
|
24507
24777
|
className: `text-sm leading-6 text-ink ${className}`,
|
|
24508
24778
|
children: /* @__PURE__ */ jsx(Markdown$1, {
|
|
24509
|
-
|
|
24510
|
-
|
|
24511
|
-
p: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("p", {
|
|
24512
|
-
className: "my-2",
|
|
24513
|
-
...props
|
|
24514
|
-
}),
|
|
24515
|
-
ul: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("ul", {
|
|
24516
|
-
className: "my-2 list-disc space-y-1 pl-5",
|
|
24517
|
-
...props
|
|
24518
|
-
}),
|
|
24519
|
-
ol: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("ol", {
|
|
24520
|
-
className: "my-2 list-decimal space-y-1 pl-5",
|
|
24521
|
-
...props
|
|
24522
|
-
}),
|
|
24523
|
-
a: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("a", {
|
|
24524
|
-
className: "font-medium text-ink underline underline-offset-4 hover:text-muted",
|
|
24525
|
-
target: "_blank",
|
|
24526
|
-
rel: "noreferrer",
|
|
24527
|
-
...props
|
|
24528
|
-
}),
|
|
24529
|
-
pre: ({ node: _node, ...props }) => /* @__PURE__ */ jsx("pre", {
|
|
24530
|
-
className: "my-0",
|
|
24531
|
-
...props
|
|
24532
|
-
})
|
|
24533
|
-
},
|
|
24779
|
+
urlTransform: markdownUrlTransform,
|
|
24780
|
+
components: MARKDOWN_COMPONENTS,
|
|
24534
24781
|
children
|
|
24535
24782
|
})
|
|
24536
24783
|
});
|
|
@@ -24857,16 +25104,41 @@ const preComponent = ({ className, ...props }) => /* @__PURE__ */ jsx("pre", {
|
|
|
24857
25104
|
className: cn("overflow-x-auto rounded-b-lg border border-border bg-card", className),
|
|
24858
25105
|
...props
|
|
24859
25106
|
});
|
|
25107
|
+
const highlighters = {
|
|
25108
|
+
light: makeHighlighter(false),
|
|
25109
|
+
dark: makeHighlighter(true)
|
|
25110
|
+
};
|
|
25111
|
+
/** 图接在 CodeHeader 下面,外框要与 `preComponent` 同形,否则成功与降级两态的版式会跳 */
|
|
25112
|
+
function makeMermaidHighlighter(dark) {
|
|
25113
|
+
const Highlighter = highlighters[dark ? "dark" : "light"];
|
|
25114
|
+
return function MermaidHighlighter(props) {
|
|
25115
|
+
return /* @__PURE__ */ jsx(MermaidDiagram, {
|
|
25116
|
+
code: props.code,
|
|
25117
|
+
className: "my-0 rounded-b-lg",
|
|
25118
|
+
fallback: /* @__PURE__ */ jsx(Highlighter, { ...props })
|
|
25119
|
+
});
|
|
25120
|
+
};
|
|
25121
|
+
}
|
|
25122
|
+
/**
|
|
25123
|
+
* 必须是模块级常量:`CodeOverride` 的 memo 比较里直接拿
|
|
25124
|
+
* `prev.componentsByLanguage === next.componentsByLanguage`,换新对象就是每次渲染重建子树。
|
|
25125
|
+
*/
|
|
25126
|
+
const mermaidByLanguage = {
|
|
25127
|
+
light: { [MERMAID_LANGUAGE]: { SyntaxHighlighter: makeMermaidHighlighter(false) } },
|
|
25128
|
+
dark: { [MERMAID_LANGUAGE]: { SyntaxHighlighter: makeMermaidHighlighter(true) } }
|
|
25129
|
+
};
|
|
24860
25130
|
const markdownComponents = {
|
|
24861
25131
|
light: memoizeMarkdownComponents({
|
|
24862
|
-
SyntaxHighlighter:
|
|
25132
|
+
SyntaxHighlighter: highlighters.light,
|
|
24863
25133
|
CodeHeader,
|
|
24864
|
-
pre: preComponent
|
|
25134
|
+
pre: preComponent,
|
|
25135
|
+
img: MarkdownImage
|
|
24865
25136
|
}),
|
|
24866
25137
|
dark: memoizeMarkdownComponents({
|
|
24867
|
-
SyntaxHighlighter:
|
|
25138
|
+
SyntaxHighlighter: highlighters.dark,
|
|
24868
25139
|
CodeHeader,
|
|
24869
|
-
pre: preComponent
|
|
25140
|
+
pre: preComponent,
|
|
25141
|
+
img: MarkdownImage
|
|
24870
25142
|
})
|
|
24871
25143
|
};
|
|
24872
25144
|
/**
|
|
@@ -24887,8 +25159,10 @@ const MarkdownText = memo(function MarkdownText({ className }) {
|
|
|
24887
25159
|
className: "contents",
|
|
24888
25160
|
children: /* @__PURE__ */ jsx(MarkdownTextPrimitive, {
|
|
24889
25161
|
remarkPlugins: [remarkGfm],
|
|
25162
|
+
urlTransform: markdownUrlTransform,
|
|
24890
25163
|
className: cn("aui-md", className),
|
|
24891
25164
|
components,
|
|
25165
|
+
componentsByLanguage: mermaidByLanguage[dark ? "dark" : "light"],
|
|
24892
25166
|
smooth: false,
|
|
24893
25167
|
defer: true
|
|
24894
25168
|
})
|
|
@@ -24948,28 +25222,60 @@ const TEXT_EXTENSIONS = [
|
|
|
24948
25222
|
/** 非文本非图片但仍可整体外发的类型(provider 的 file 分片) @experimental */
|
|
24949
25223
|
const FILE_MIME_TYPES = ["application/pdf"];
|
|
24950
25224
|
/**
|
|
24951
|
-
* 需要**客户端先抽取成文本**才能外发的类型(FR-23.9 / 0.13.0 FR-12.5)。
|
|
24952
|
-
* 不走直通的 `file`:docx / xlsx 发给 provider 会被当成二进制垃圾。
|
|
25225
|
+
* 需要**客户端先抽取成文本**才能外发的类型(FR-23.9 / 0.13.0 FR-12.5 / 0.22.0 分册 18)。
|
|
25226
|
+
* 不走直通的 `file`:docx / xlsx / pptx 发给 provider 会被当成二进制垃圾。
|
|
24953
25227
|
*/
|
|
24954
25228
|
const DOCX_MIME = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
|
|
24955
25229
|
const XLSX_MIME = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
|
|
25230
|
+
const PPTX_MIME = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
|
|
25231
|
+
/**
|
|
25232
|
+
* DWG 图纸的 MIME。没有官方注册值,各家各发一套,浏览器上传时更常见的是空串。
|
|
25233
|
+
* @experimental
|
|
25234
|
+
*/
|
|
25235
|
+
const DWG_MIME_TYPES = [
|
|
25236
|
+
"image/vnd.dwg",
|
|
25237
|
+
"application/acad",
|
|
25238
|
+
"application/x-acad",
|
|
25239
|
+
"application/dwg",
|
|
25240
|
+
"application/x-dwg",
|
|
25241
|
+
"drawing/dwg"
|
|
25242
|
+
];
|
|
25243
|
+
/**
|
|
25244
|
+
* 只存字节、不进上下文的扩展名。仍然是**白名单制**:
|
|
25245
|
+
* 新增 `binary` 类别不等于任意二进制放行(AC-19.18)。
|
|
25246
|
+
* @experimental
|
|
25247
|
+
*/
|
|
25248
|
+
const BINARY_EXTENSIONS = ["dwg"];
|
|
24956
25249
|
const TEXT_EXTENSION_SET = new Set(TEXT_EXTENSIONS);
|
|
24957
25250
|
const IMAGE_MIME_SET = new Set(IMAGE_MIME_TYPES);
|
|
24958
25251
|
const FILE_MIME_SET = new Set(FILE_MIME_TYPES);
|
|
24959
|
-
const EXTRACTED_MIME_SET = /* @__PURE__ */ new Set([
|
|
24960
|
-
|
|
25252
|
+
const EXTRACTED_MIME_SET = /* @__PURE__ */ new Set([
|
|
25253
|
+
DOCX_MIME,
|
|
25254
|
+
XLSX_MIME,
|
|
25255
|
+
PPTX_MIME
|
|
25256
|
+
]);
|
|
25257
|
+
const EXTRACTED_EXTENSION_SET = /* @__PURE__ */ new Set([
|
|
25258
|
+
"docx",
|
|
25259
|
+
"xlsx",
|
|
25260
|
+
"pptx"
|
|
25261
|
+
]);
|
|
25262
|
+
const BINARY_MIME_SET = new Set(DWG_MIME_TYPES);
|
|
25263
|
+
const BINARY_EXTENSION_SET = new Set(BINARY_EXTENSIONS);
|
|
25264
|
+
/** 下载与上传都常把认不出的类型标成它,此时只能看扩展名 */
|
|
25265
|
+
const OPAQUE_MIME = "application/octet-stream";
|
|
24961
25266
|
function extensionOf(fileName) {
|
|
24962
25267
|
return fileName.split(".").pop()?.toLowerCase() ?? "";
|
|
24963
25268
|
}
|
|
24964
|
-
/** 分类集中一处,不散落在各 UI 分支里 @experimental */
|
|
24965
|
-
function classifyAttachment(contentType, fileName) {
|
|
25269
|
+
/** 分类集中一处,不散落在各 UI 分支里 @experimental */ function classifyAttachment(contentType, fileName) {
|
|
24966
25270
|
const type = contentType.toLowerCase();
|
|
24967
25271
|
if (IMAGE_MIME_SET.has(type)) return "image";
|
|
25272
|
+
if (BINARY_MIME_SET.has(type)) return "binary";
|
|
24968
25273
|
if (EXTRACTED_MIME_SET.has(type)) return "document-text";
|
|
24969
25274
|
if (FILE_MIME_SET.has(type)) return "file";
|
|
24970
25275
|
if (type.startsWith("text/") || type === "application/json") return "text";
|
|
24971
25276
|
if (type === "" && TEXT_EXTENSION_SET.has(extensionOf(fileName))) return "text";
|
|
24972
25277
|
if (type === "" && EXTRACTED_EXTENSION_SET.has(extensionOf(fileName))) return "document-text";
|
|
25278
|
+
if ((type === "" || type === OPAQUE_MIME) && BINARY_EXTENSION_SET.has(extensionOf(fileName))) return "binary";
|
|
24973
25279
|
}
|
|
24974
25280
|
|
|
24975
25281
|
//#endregion
|
|
@@ -25516,6 +25822,89 @@ var ChatbotVisionDelegate = class {
|
|
|
25516
25822
|
}
|
|
25517
25823
|
};
|
|
25518
25824
|
|
|
25825
|
+
//#endregion
|
|
25826
|
+
//#region src/core/imageSource.ts
|
|
25827
|
+
/**
|
|
25828
|
+
* 授权卡的自述作用域(FR-11.10 / AC-G6)。
|
|
25829
|
+
*
|
|
25830
|
+
* 卡上写的范围与实际记忆的范围**由这一个函数同时导出**:
|
|
25831
|
+
* `key` 直接就是 `ImageSourceAuthorizer` 存进集合里的那个值,
|
|
25832
|
+
* `labelKey` 是复选框旁那句话。两者不可能分叉,因为只有一处。
|
|
25833
|
+
*
|
|
25834
|
+
* 出的是**字典键**而不是成品文案:这里是同步路径,读不到语言,
|
|
25835
|
+
* 写死一种语言就把复选框固定成英文了。
|
|
25836
|
+
* @experimental
|
|
25837
|
+
*/
|
|
25838
|
+
function describeImageSourceScope(kind) {
|
|
25839
|
+
return {
|
|
25840
|
+
key: kind,
|
|
25841
|
+
labelKey: kind === "page-image" ? "interaction.imageSource.rememberPageImage" : "interaction.imageSource.rememberRemoteImage"
|
|
25842
|
+
};
|
|
25843
|
+
}
|
|
25844
|
+
const MESSAGES = {
|
|
25845
|
+
"page-image": "Allow the assistant to copy a picture from the page you are on into the document it is preparing? The picture is saved with this conversation’s files.",
|
|
25846
|
+
"remote-image": "Allow the assistant to download an image from the web into the document it is preparing? The site hosting it will see that the request came from you."
|
|
25847
|
+
};
|
|
25848
|
+
/**
|
|
25849
|
+
* 图片来源的会话内授权(分册 11 §3.3)。
|
|
25850
|
+
*
|
|
25851
|
+
* 记忆随会话生灭:`forgetSessionDecisions()` 在新建 / 切换会话时调,新会话重新问。
|
|
25852
|
+
* 没有任何持久化端口——本版刻意不做跨会话记忆,那是比用户预期更宽的授权。
|
|
25853
|
+
* @experimental
|
|
25854
|
+
*/
|
|
25855
|
+
var ImageSourceAuthorizer = class {
|
|
25856
|
+
#ui;
|
|
25857
|
+
#approved = /* @__PURE__ */ new Set();
|
|
25858
|
+
/** 同一类来源的并发请求搭同一次询问,不各弹各的(同视觉委派 FR-26.8) */
|
|
25859
|
+
#asking = /* @__PURE__ */ new Map();
|
|
25860
|
+
#seq = 0;
|
|
25861
|
+
constructor(options) {
|
|
25862
|
+
this.#ui = options.ui;
|
|
25863
|
+
}
|
|
25864
|
+
forgetSessionDecisions() {
|
|
25865
|
+
this.#approved.clear();
|
|
25866
|
+
}
|
|
25867
|
+
/**
|
|
25868
|
+
* 记忆粒度是**来源类型**,不是域名(FR-13.7)。因此同一类的并发请求搭同一次询问,
|
|
25869
|
+
* 卡上也只会出现其中一个 URL——这正是为什么卡文必须把
|
|
25870
|
+
* 「本次对话内所有外链图片」说出来,而不是只展示那一个域名。
|
|
25871
|
+
*/
|
|
25872
|
+
async authorize(kind, details = {}) {
|
|
25873
|
+
if (this.#approved.has(kind)) return true;
|
|
25874
|
+
const pending = this.#asking.get(kind);
|
|
25875
|
+
if (pending !== void 0) return await pending;
|
|
25876
|
+
const ask = this.#ask(kind, details);
|
|
25877
|
+
this.#asking.set(kind, ask);
|
|
25878
|
+
try {
|
|
25879
|
+
return await ask;
|
|
25880
|
+
} finally {
|
|
25881
|
+
this.#asking.delete(kind);
|
|
25882
|
+
}
|
|
25883
|
+
}
|
|
25884
|
+
async #ask(kind, details) {
|
|
25885
|
+
this.#seq += 1;
|
|
25886
|
+
const scope = describeImageSourceScope(kind);
|
|
25887
|
+
const { note, url } = details;
|
|
25888
|
+
const response = await this.#ui.request({
|
|
25889
|
+
type: "authorize",
|
|
25890
|
+
id: `image-source-${kind}-${String(this.#seq)}`,
|
|
25891
|
+
capability: "readImageSource",
|
|
25892
|
+
message: note === void 0 ? MESSAGES[kind] : `${MESSAGES[kind]} The assistant says: ${note}`,
|
|
25893
|
+
details: {
|
|
25894
|
+
kind: scope.key,
|
|
25895
|
+
...note === void 0 ? {} : { note },
|
|
25896
|
+
...url === void 0 ? {} : { url },
|
|
25897
|
+
rememberable: true,
|
|
25898
|
+
rememberScope: "session",
|
|
25899
|
+
rememberLabelKey: scope.labelKey
|
|
25900
|
+
}
|
|
25901
|
+
});
|
|
25902
|
+
if (response.cancelled === true || response.value === false) return false;
|
|
25903
|
+
if (response.remembered === true) this.#approved.add(scope.key);
|
|
25904
|
+
return true;
|
|
25905
|
+
}
|
|
25906
|
+
};
|
|
25907
|
+
|
|
25519
25908
|
//#endregion
|
|
25520
25909
|
//#region src/core/markdownBoundary.ts
|
|
25521
25910
|
/**
|
|
@@ -26088,6 +26477,8 @@ const chatbotDictionary = defineDictionary({
|
|
|
26088
26477
|
"interaction.pageAction.attach": "Allow the assistant to attach a file to “{target}”?",
|
|
26089
26478
|
"interaction.pageAction.scroll.down": "Allow the assistant to scroll down the area around “{target}”?",
|
|
26090
26479
|
"interaction.pageAction.scroll.up": "Allow the assistant to scroll up the area around “{target}”?",
|
|
26480
|
+
"interaction.pageAction.hover": "Allow the assistant to hover over “{target}”?",
|
|
26481
|
+
"interaction.pageAction.drag": "Allow the assistant to drag “{target}” onto another element?",
|
|
26091
26482
|
"interaction.pageAction.back": "Allow the assistant to go back to the previous page on “{target}”?",
|
|
26092
26483
|
"interaction.pageAction.role.click": "this control",
|
|
26093
26484
|
"interaction.pageAction.role.fill": "this field",
|
|
@@ -26096,6 +26487,8 @@ const chatbotDictionary = defineDictionary({
|
|
|
26096
26487
|
"interaction.pageAction.role.set": "this switch",
|
|
26097
26488
|
"interaction.pageAction.role.attach": "this upload field",
|
|
26098
26489
|
"interaction.pageAction.role.scroll": "this area",
|
|
26490
|
+
"interaction.pageAction.role.hover": "this element",
|
|
26491
|
+
"interaction.pageAction.role.drag": "this item",
|
|
26099
26492
|
"interaction.pageAction.role.back": "this page",
|
|
26100
26493
|
"interaction.pageAction.remember": "Don’t ask again for this kind of action",
|
|
26101
26494
|
"interaction.pageAction.frame": "The target is in the embedded frame \"{frame}\".",
|
|
@@ -26119,6 +26512,15 @@ const chatbotDictionary = defineDictionary({
|
|
|
26119
26512
|
"interaction.visionDelegation.rememberFallback": "Don’t ask again for this endpoint",
|
|
26120
26513
|
"interaction.visionDelegation.rememberSession": "Don’t ask again in this conversation",
|
|
26121
26514
|
"interaction.visionDelegation.body": "The model you selected cannot read images. Send the {images} image(s) produced by “{source}” to “{label}” at {host} to be described? If you decline, nothing is sent and the turn continues without them.",
|
|
26515
|
+
"interaction.imageSource.title": "Put a picture into the document?",
|
|
26516
|
+
"interaction.imageSource.pageImageBody": "Allow the assistant to copy a picture from the page into the document it is building? The picture is saved with this conversation until you delete it.",
|
|
26517
|
+
"interaction.imageSource.remoteImageBody": "Allow the assistant to download an image from {url} and put it into the document it is building? Fetching it tells that server your network address and when you asked for it.",
|
|
26518
|
+
"interaction.imageSource.remoteImageAnyBody": "Allow the assistant to download images from the web and put them into the document it is building? Fetching one tells that server your network address and when you asked for it.",
|
|
26519
|
+
"interaction.imageSource.insecure": "This address is plain http://, so the image can be read or swapped by anyone on the network in between.",
|
|
26520
|
+
"interaction.imageSource.note": "The assistant says it is for: {note}",
|
|
26521
|
+
"interaction.imageSource.rememberFallback": "Don’t ask again in this conversation",
|
|
26522
|
+
"interaction.imageSource.rememberPageImage": "Don’t ask again in this conversation before taking pictures from the page",
|
|
26523
|
+
"interaction.imageSource.rememberRemoteImage": "Don’t ask again in this conversation before downloading images from the web",
|
|
26122
26524
|
"interaction.uploadFile.read": "Allow the skill “{skill}” to read the file you uploaded?",
|
|
26123
26525
|
"interaction.uploadFile.file": "{name} · {type} · {size}",
|
|
26124
26526
|
"interaction.sandbox.readReference": "Allow the skill “{skill}” to read the reference file “{path}”?",
|
|
@@ -26184,6 +26586,7 @@ const chatbotDictionary = defineDictionary({
|
|
|
26184
26586
|
"composer.attachment.kind.image": "Image",
|
|
26185
26587
|
"composer.attachment.kind.file": "File",
|
|
26186
26588
|
"composer.attachment.kind.document-text": "Doc text",
|
|
26589
|
+
"composer.attachment.kind.binary": "Drawing",
|
|
26187
26590
|
"composer.attachment.compressed": "Compressed {from} → {to}",
|
|
26188
26591
|
"composer.model": "Model",
|
|
26189
26592
|
"composer.model.unconfigured": "Not usable: this entry has no API key or base URL. Configure it in the console.",
|
|
@@ -26198,6 +26601,7 @@ const chatbotDictionary = defineDictionary({
|
|
|
26198
26601
|
"attachment.storeFailed.title": "Not enough space on this device",
|
|
26199
26602
|
"attachment.storeFailed": "“{name}” could not be saved to this device’s storage, so it was not attached. Free up space and try again.",
|
|
26200
26603
|
"attachment.pdfAsText": "“{name}” is {size}, over the {limit} a single request can carry, so its extracted text was sent instead of the original file. Layout, figures and scanned pages are not included.",
|
|
26604
|
+
"attachment.pdfAsPagedRead": "“{name}” is {size}, over the {limit} a single request can carry, so the file was not attached. The assistant will read it page by page instead, which takes several passes over a long document.",
|
|
26201
26605
|
"attachment.pdfRefused": "“{name}” is {size}, over the {limit} a single request can carry, and no text could be extracted from it — it is most likely a scan. It was not sent. Try a smaller file, a page range, or a text version.",
|
|
26202
26606
|
"attachment.pdfNoExtractor": "“{name}” is {size}, over the {limit} a single request can carry, and this environment cannot extract PDF text. It was not sent. Switch to a model that accepts large documents, or attach a Word or plain-text version.",
|
|
26203
26607
|
"attachment.textTooLong": "The text of “{name}” is {size}, over the {limit} one request may put into the model’s context. It was not sent, and it was not truncated either, because half a document is worse than none. Raise the extracted text limit in Settings → Agent runtime, or attach a shorter excerpt.",
|
|
@@ -26502,6 +26906,8 @@ const chatbotDictionary = defineDictionary({
|
|
|
26502
26906
|
"interaction.pageAction.attach": "允许助手向「{target}」附加文件吗?",
|
|
26503
26907
|
"interaction.pageAction.scroll.down": "允许助手在「{target}」所在区域向下滚动吗?",
|
|
26504
26908
|
"interaction.pageAction.scroll.up": "允许助手在「{target}」所在区域向上滚动吗?",
|
|
26909
|
+
"interaction.pageAction.hover": "允许助手将鼠标悬停在「{target}」上吗?",
|
|
26910
|
+
"interaction.pageAction.drag": "允许助手将「{target}」拖到另一个位置吗?",
|
|
26505
26911
|
"interaction.pageAction.back": "允许助手在「{target}」上返回上一页吗?",
|
|
26506
26912
|
"interaction.pageAction.role.click": "这个控件",
|
|
26507
26913
|
"interaction.pageAction.role.fill": "这个输入框",
|
|
@@ -26510,6 +26916,8 @@ const chatbotDictionary = defineDictionary({
|
|
|
26510
26916
|
"interaction.pageAction.role.set": "这个开关",
|
|
26511
26917
|
"interaction.pageAction.role.attach": "这个上传控件",
|
|
26512
26918
|
"interaction.pageAction.role.scroll": "这一块",
|
|
26919
|
+
"interaction.pageAction.role.hover": "这个元素",
|
|
26920
|
+
"interaction.pageAction.role.drag": "这一项",
|
|
26513
26921
|
"interaction.pageAction.role.back": "这个页面",
|
|
26514
26922
|
"interaction.pageAction.remember": "以后不再询问此类操作",
|
|
26515
26923
|
"interaction.pageAction.frame": "目标位于嵌入帧「{frame}」中。",
|
|
@@ -26533,6 +26941,15 @@ const chatbotDictionary = defineDictionary({
|
|
|
26533
26941
|
"interaction.visionDelegation.rememberFallback": "以后不再为该端点询问",
|
|
26534
26942
|
"interaction.visionDelegation.rememberSession": "本次对话不再询问,默认同意",
|
|
26535
26943
|
"interaction.visionDelegation.body": "你选中的模型看不了图片。是否把「{source}」产出的 {images} 张图发往「{label}」({host})请它描述?不同意则什么都不会发出,这一轮就当没看到图。",
|
|
26944
|
+
"interaction.imageSource.title": "把图片放进文档?",
|
|
26945
|
+
"interaction.imageSource.pageImageBody": "允许助手把当前页面上的一张图放进它正在生成的文档吗?该图会随本次会话保存,直到你删除这次会话。",
|
|
26946
|
+
"interaction.imageSource.remoteImageBody": "允许助手从 {url} 下载一张图片放进它正在生成的文档吗?下载会把你的网络地址与访问时间告知该服务器。",
|
|
26947
|
+
"interaction.imageSource.remoteImageAnyBody": "允许助手从互联网下载图片放进它正在生成的文档吗?下载会把你的网络地址与访问时间告知对方服务器。",
|
|
26948
|
+
"interaction.imageSource.insecure": "这个地址是明文 http://,图片内容可被链路上的任何人看到或替换。",
|
|
26949
|
+
"interaction.imageSource.note": "助手声称的用途:{note}",
|
|
26950
|
+
"interaction.imageSource.rememberFallback": "本次对话不再询问",
|
|
26951
|
+
"interaction.imageSource.rememberPageImage": "本次对话不再询问取用页面上的图",
|
|
26952
|
+
"interaction.imageSource.rememberRemoteImage": "本次对话不再询问从互联网下载图片",
|
|
26536
26953
|
"interaction.uploadFile.read": "允许技能「{skill}」读取你上传的这份文件吗?",
|
|
26537
26954
|
"interaction.uploadFile.file": "{name} · {type} · {size}",
|
|
26538
26955
|
"interaction.sandbox.readReference": "允许技能「{skill}」读取参考资料「{path}」吗?",
|
|
@@ -26598,6 +27015,7 @@ const chatbotDictionary = defineDictionary({
|
|
|
26598
27015
|
"composer.attachment.kind.image": "图片",
|
|
26599
27016
|
"composer.attachment.kind.file": "文件",
|
|
26600
27017
|
"composer.attachment.kind.document-text": "文档正文",
|
|
27018
|
+
"composer.attachment.kind.binary": "图纸",
|
|
26601
27019
|
"composer.attachment.compressed": "已压缩 {from} → {to}",
|
|
26602
27020
|
"composer.model": "模型",
|
|
26603
27021
|
"composer.model.unconfigured": "不可用:该条目既没有 API 密钥也没有基础地址,请在控制台补全。",
|
|
@@ -26612,6 +27030,7 @@ const chatbotDictionary = defineDictionary({
|
|
|
26612
27030
|
"attachment.storeFailed.title": "本机存储不够了",
|
|
26613
27031
|
"attachment.storeFailed": "「{name}」没能存入本机存储,因此未作为附件添加。请腾出空间后重试。",
|
|
26614
27032
|
"attachment.pdfAsText": "「{name}」有 {size},超过单次请求能带的 {limit},已改为发送从中抽取的文本,而不是原件。版面、图表与扫描页不在其中。",
|
|
27033
|
+
"attachment.pdfAsPagedRead": "「{name}」有 {size},超过单次请求能带的 {limit},因此未作为附件发送。助手改为逐页读取,长文档会读好几轮。",
|
|
26615
27034
|
"attachment.pdfRefused": "「{name}」有 {size},超过单次请求能带的 {limit},而且从中抽不出文本——它多半是扫描件。该文件未发送。请改用更小的文件、指定页码范围,或换文本版本。",
|
|
26616
27035
|
"attachment.pdfNoExtractor": "「{name}」有 {size},超过单次请求能带的 {limit},而本环境无法抽取 PDF 文本。该文件未发送。请换一个能收大文档的模型,或改传 Word / 纯文本版本。",
|
|
26617
27036
|
"attachment.textTooLong": "「{name}」的文本有 {size},超过单次请求能放进模型上下文的 {limit}。该文件未发送,也没有截断——半份文档比没有更糟。请在「设置 → 智能体运行」里调高抽取文本上限,或改附一段节选。",
|
|
@@ -26785,6 +27204,24 @@ function translateText(locale, key, params) {
|
|
|
26785
27204
|
return interpolate$1(chatbotDictionary[locale]?.[key] ?? chatbotDictionary.en[key] ?? key, params);
|
|
26786
27205
|
}
|
|
26787
27206
|
|
|
27207
|
+
//#endregion
|
|
27208
|
+
//#region src/core/attachmentRef.ts
|
|
27209
|
+
/**
|
|
27210
|
+
* `upload:` 引用落到哪条附件上。
|
|
27211
|
+
*
|
|
27212
|
+
* 引擎侧(投放文档里的图片块)与组件侧(正文 markdown 里的 ``)读的是
|
|
27213
|
+
* 两份不同的数据——一份从会话存储读回,一份是 React 手上的消息数组——但**判定必须是同一条**,
|
|
27214
|
+
* 否则会出现「文档里贴得出、对话里贴不出」这种没法向用户解释的差异。
|
|
27215
|
+
*
|
|
27216
|
+
* id 先行:它唯一。文件名兜底是必需的而不是宽容——附件 id 是 `att-…` 随机串,
|
|
27217
|
+
* 模型上下文里最显眼的始终是文件名,只认 id 的结果就是整篇文档一张图都没有。
|
|
27218
|
+
* 重名时取最先那条:这一路的候选全部来自用户自己的消息,没有越权面,
|
|
27219
|
+
* 取错一张也远好过整批丢图。
|
|
27220
|
+
*/
|
|
27221
|
+
function findAttachmentByRef(attachments, ref) {
|
|
27222
|
+
return attachments.find((a) => a.id === ref) ?? attachments.find((a) => a.name === ref);
|
|
27223
|
+
}
|
|
27224
|
+
|
|
26788
27225
|
//#endregion
|
|
26789
27226
|
//#region src/core/runPhases.ts
|
|
26790
27227
|
/** 展示用相位:discover 并进 route,prepare/observe 并进 execute(07 号文档 §4) */
|
|
@@ -26987,6 +27424,22 @@ function fromBase64(data) {
|
|
|
26987
27424
|
function formatSize(bytes) {
|
|
26988
27425
|
return bytes >= 1024 * 1024 ? `${(bytes / 1024 / 1024).toFixed(1)} MB` : `${Math.max(1, Math.round(bytes / 1024))} KB`;
|
|
26989
27426
|
}
|
|
27427
|
+
/** 技能写在图片块上的引用,由 publish 脚本渲染成这个属性;字节在这里填回去 */
|
|
27428
|
+
const IMAGE_REF_ATTR = "data-webskill-image";
|
|
27429
|
+
/**
|
|
27430
|
+
* 单张图进文档的字节上限。超了先压(`compressImageToBudget`),压不下去按 FR-11.8 归因丢弃,
|
|
27431
|
+
* 不许原样塞进载荷去撞分册 10 的硬兜底——那会让一张图带走整份文档。
|
|
27432
|
+
*/
|
|
27433
|
+
const MAX_DOCUMENT_IMAGE_BYTES = 8e6;
|
|
27434
|
+
/**
|
|
27435
|
+
* 在线 WebOffice 文档作模板时的取件号前缀(0.22.0 分册 42 · FR-42.12)。
|
|
27436
|
+
*
|
|
27437
|
+
* 前缀是显式的,不靠猜:`inspect_template` 的取件号绝大多数是附件 id,
|
|
27438
|
+
* 拿一个普通 id 去在线文档那边碰运气,猜中一次的收益远小于猜错一次的代价——
|
|
27439
|
+
* 那是把另一份文档当成用户指的那一份填了出去。
|
|
27440
|
+
* @experimental
|
|
27441
|
+
*/
|
|
27442
|
+
const WEB_OFFICE_TEMPLATE_PREFIX = "weboffice:";
|
|
26990
27443
|
/**
|
|
26991
27444
|
* 图片附件的指认说明(0.21.0 分册 22 · FR-22.4b)。
|
|
26992
27445
|
*
|
|
@@ -26998,7 +27451,8 @@ function formatSize(bytes) {
|
|
|
26998
27451
|
* 与图分开成片:端点拒收图时 `#dropRejectedImages` 换掉的只有图那一片,说明留在原地。
|
|
26999
27452
|
*/
|
|
27000
27453
|
function imageAttachmentLabel(attachment) {
|
|
27001
|
-
return `--- Attachment: ${attachment.name} (${attachment.contentType}) ---\nThe user attached this image to this message and it is already visible to you. Do not look for it on the page, in the downloads list or anywhere else, and do not call a tool to obtain it
|
|
27454
|
+
return `--- Attachment: ${attachment.name} (${attachment.contentType}, id: ${attachment.id}) ---\nThe user attached this image to this message and it is already visible to you. Do not look for it on the page, in the downloads list or anywhere else, and do not call a tool to obtain it.
|
|
27455
|
+
To show this picture rather than describe it, reference it as \`upload:${attachment.id}\`: write \`\` in your reply to display it inline, or pass the same string as the image \`ref\` when a skill or an export tool builds a document. Never invent base64 data for it.`;
|
|
27002
27456
|
}
|
|
27003
27457
|
/** 内置演示 LLM 的应答序列(用尽后从头循环) */
|
|
27004
27458
|
const DEMO_REPLIES = [
|
|
@@ -27153,6 +27607,11 @@ var ChatEngine = class {
|
|
|
27153
27607
|
#listeners = /* @__PURE__ */ new Set();
|
|
27154
27608
|
/** runId → 已激活技能累积(skill-activated 事件用;终态清理) */
|
|
27155
27609
|
#runSkills = /* @__PURE__ */ new Map();
|
|
27610
|
+
/**
|
|
27611
|
+
* runId → sessionId。trace 要等 run 结束才落盘,而导出工具在 run **进行中**
|
|
27612
|
+
* 就要按会话找附件与历史产物,只读 trace 时它们一律扑空(分册 21 §2.6)。
|
|
27613
|
+
*/
|
|
27614
|
+
#runSessions = /* @__PURE__ */ new Map();
|
|
27156
27615
|
/** 文档投放端口只构造一次:渲染侧把它放进 memo 依赖,每次取新对象会触发无谓重渲染 */
|
|
27157
27616
|
#documentSurfacePort;
|
|
27158
27617
|
/** 当前渲染框架档(复合 uiBridge 分发;setRenderer 热切换) */
|
|
@@ -27181,6 +27640,15 @@ var ChatEngine = class {
|
|
|
27181
27640
|
* 拒绝的记忆挂在它身上,因此 `createSession()` 要能找到它把那一位清掉。
|
|
27182
27641
|
*/
|
|
27183
27642
|
#visionDelegate;
|
|
27643
|
+
/**
|
|
27644
|
+
* 图片来源授权(0.22.0 分册 11 §3.3)。记忆随会话生灭,因此与视觉委派一样,
|
|
27645
|
+
* 切换 / 新建会话时要把那一份集合清掉。
|
|
27646
|
+
*/
|
|
27647
|
+
#imageSources;
|
|
27648
|
+
/** run → 已落下的页面图张数;产物名 `page-image-N.png` 的 N 从这里来 */
|
|
27649
|
+
#pageImageSeq = /* @__PURE__ */ new Map();
|
|
27650
|
+
/** run → 已落下的外链图张数;与页面图分开编号,产物名一眼看得出来源 */
|
|
27651
|
+
#remoteImageSeq = /* @__PURE__ */ new Map();
|
|
27184
27652
|
/** 当前 run('route' 事件捕获,终态清理);清单与页面感知的事件归属共用 */
|
|
27185
27653
|
#activeRunId;
|
|
27186
27654
|
/** dispose() 之后拒绝再发起 run:卸载后的组件不该还能悄悄跑一轮 */
|
|
@@ -27386,17 +27854,177 @@ var ChatEngine = class {
|
|
|
27386
27854
|
const dir = `${this.#chatRoot}/artifacts/${runId}`;
|
|
27387
27855
|
const html = await this.#adapter.storage.readText(this.#artifactPath(dir, artifact));
|
|
27388
27856
|
const css = style === void 0 ? void 0 : await this.#adapter.storage.readText(this.#artifactPath(dir, style));
|
|
27857
|
+
const resolved = await this.#resolveDocumentImages(html, runId);
|
|
27389
27858
|
await host.open({
|
|
27390
27859
|
skillName: await this.#skillNamesOfRun(runId),
|
|
27391
27860
|
dataSource: sanitizeUntrustedLine(dataSource),
|
|
27392
27861
|
document: {
|
|
27393
|
-
html,
|
|
27862
|
+
html: resolved.html,
|
|
27394
27863
|
...css === void 0 ? {} : { css }
|
|
27395
|
-
}
|
|
27864
|
+
},
|
|
27865
|
+
...resolved.images.length > 0 ? { images: resolved.images } : {},
|
|
27866
|
+
...resolved.omissions.length > 0 ? { omissions: resolved.omissions } : {}
|
|
27396
27867
|
});
|
|
27397
27868
|
} };
|
|
27398
27869
|
}
|
|
27399
27870
|
/**
|
|
27871
|
+
* 把产物 HTML 里的图片占位换成可直接显示的 `src`(0.22.0 FR-11.6)。
|
|
27872
|
+
*
|
|
27873
|
+
* 落在引擎而不是宿主或投放面,理由与 `documentSurface` 端口本身相同:要读产物就得知道
|
|
27874
|
+
* 产物目录布局,那是引擎独有的知识。`startViewerShell` 因此一行不改。
|
|
27875
|
+
*
|
|
27876
|
+
* 解析发生在宿主的确认框之前,用户取消时白做一次——接受这个代价,因为**体积与像素的
|
|
27877
|
+
* 度量必须看到已填字节的文档**,否则量到的是一份全是占位的空壳。
|
|
27878
|
+
*/
|
|
27879
|
+
async #resolveDocumentImages(html, runId) {
|
|
27880
|
+
if (!html.includes(IMAGE_REF_ATTR)) return {
|
|
27881
|
+
html,
|
|
27882
|
+
images: [],
|
|
27883
|
+
omissions: []
|
|
27884
|
+
};
|
|
27885
|
+
const doc = new DOMParser().parseFromString(html, "text/html");
|
|
27886
|
+
const images = [];
|
|
27887
|
+
const omissions = [];
|
|
27888
|
+
for (const element of [...doc.querySelectorAll(`img[${IMAGE_REF_ATTR}]`)]) {
|
|
27889
|
+
const ref = element.getAttribute(IMAGE_REF_ATTR) ?? "";
|
|
27890
|
+
const alt = element.getAttribute("alt") ?? "";
|
|
27891
|
+
element.removeAttribute(IMAGE_REF_ATTR);
|
|
27892
|
+
try {
|
|
27893
|
+
const src = await this.#documentImageSrc(ref, runId, images);
|
|
27894
|
+
element.setAttribute("src", src);
|
|
27895
|
+
} catch (e) {
|
|
27896
|
+
const reason = messageOf(e);
|
|
27897
|
+
omissions.push({
|
|
27898
|
+
ref,
|
|
27899
|
+
alt,
|
|
27900
|
+
stage: "resolve",
|
|
27901
|
+
reason
|
|
27902
|
+
});
|
|
27903
|
+
const note = doc.createElement("p");
|
|
27904
|
+
note.className = "wsdoc__image-missing";
|
|
27905
|
+
note.textContent = `[image unavailable] ${alt} — ${reason}`;
|
|
27906
|
+
element.replaceWith(note);
|
|
27907
|
+
}
|
|
27908
|
+
}
|
|
27909
|
+
return {
|
|
27910
|
+
html: doc.body.innerHTML,
|
|
27911
|
+
images,
|
|
27912
|
+
omissions
|
|
27913
|
+
};
|
|
27914
|
+
}
|
|
27915
|
+
/** 解析一条引用并把实测事实记进 `images`;失败一律抛错,由调用方转成归因 */
|
|
27916
|
+
async #documentImageSrc(ref, runId, images) {
|
|
27917
|
+
const separator = ref.indexOf(":");
|
|
27918
|
+
const kind = separator === -1 ? "" : ref.slice(0, separator);
|
|
27919
|
+
const rest = ref.slice(separator + 1);
|
|
27920
|
+
if (kind === "remote") {
|
|
27921
|
+
assertRemoteUrlAllowed(rest, { allowHttp: true });
|
|
27922
|
+
return rest;
|
|
27923
|
+
}
|
|
27924
|
+
const image = await toDocumentImage(await this.#documentImageBytes(kind, rest, runId), { maxBytes: MAX_DOCUMENT_IMAGE_BYTES });
|
|
27925
|
+
const src = `data:${image.mimeType};base64,${image.data}`;
|
|
27926
|
+
images.push({
|
|
27927
|
+
ref,
|
|
27928
|
+
width: image.width,
|
|
27929
|
+
height: image.height,
|
|
27930
|
+
units: src.length
|
|
27931
|
+
});
|
|
27932
|
+
return src;
|
|
27933
|
+
}
|
|
27934
|
+
async #documentImageBytes(kind, rest, runId) {
|
|
27935
|
+
if (kind === "artifact") {
|
|
27936
|
+
assertSafePathSegment(rest, "artifact");
|
|
27937
|
+
const read = (owner) => this.#adapter.storage.readBinary(resolveInsideRoot(`${this.#chatRoot}/artifacts/${owner}`, rest));
|
|
27938
|
+
try {
|
|
27939
|
+
return await read(runId);
|
|
27940
|
+
} catch {}
|
|
27941
|
+
for (const prior of await this.#priorRunIds(runId)) try {
|
|
27942
|
+
return await read(prior);
|
|
27943
|
+
} catch {}
|
|
27944
|
+
throw new WebSkillError("FS_NOT_FOUND", `artifact "${rest}" was not produced anywhere in this conversation`);
|
|
27945
|
+
}
|
|
27946
|
+
if (kind === "upload") {
|
|
27947
|
+
const attachment = await this.#uploadOfRun(runId, rest);
|
|
27948
|
+
if (attachment === void 0) throw new WebSkillError("UPLOAD_FILE_NOT_FOUND", `no attachment with id or file name "${rest}" was uploaded anywhere in this conversation`);
|
|
27949
|
+
return this.#adapter.storage.readBinary(resolveInsideRoot(this.attachmentsRoot, attachment.path));
|
|
27950
|
+
}
|
|
27951
|
+
throw new WebSkillError("VALIDATION_FAILED", `image reference "${kind}:${rest}" has no known prefix; expected artifact:, upload: or remote:`);
|
|
27952
|
+
}
|
|
27953
|
+
/**
|
|
27954
|
+
* 这一次 run 属于哪个会话。
|
|
27955
|
+
*
|
|
27956
|
+
* 进行中读内存,已结束读 trace——与 `#skillNamesOfRun` 同一范式。
|
|
27957
|
+
* 只读 trace 会让**工具调用期**的每一次查找扑空:trace 要等 run 收尾才 `put`,
|
|
27958
|
+
* 于是 `export_spreadsheet` 当场拿不到会话,而文档投放(用户事后点按钮)却正常,
|
|
27959
|
+
* 表现成「同一张图 Word 放得进、Excel 放不进」。
|
|
27960
|
+
*/
|
|
27961
|
+
async #sessionOfRun(runId) {
|
|
27962
|
+
return this.#runSessions.get(runId) ?? (await this.#traceStore.get(runId).catch(() => void 0))?.sessionId;
|
|
27963
|
+
}
|
|
27964
|
+
/**
|
|
27965
|
+
* 本会话里 `runId` 之前的那些 run,由近及远。
|
|
27966
|
+
*
|
|
27967
|
+
* 「先出 Word,再出一份 PPT」是两轮 run,图片产物却落在第一轮的目录里;
|
|
27968
|
+
* 只认当前 run 会让第二份文档必然缺图,而且缺得毫无道理——用户看来那张图刚刚还在。
|
|
27969
|
+
*
|
|
27970
|
+
* 放宽止于**本会话**:目录仍由引擎按 runId 拼,产物名仍过 `assertSafePathSegment`,
|
|
27971
|
+
* 模型既给不出别的会话,也给不出路径(FR-11.7 的守卫面一寸没让)。
|
|
27972
|
+
*/
|
|
27973
|
+
async #priorRunIds(runId) {
|
|
27974
|
+
const sessionId = await this.#sessionOfRun(runId);
|
|
27975
|
+
if (sessionId === void 0) return [];
|
|
27976
|
+
const messages = (await this.#sessions.get(sessionId).catch(() => void 0))?.messages ?? [];
|
|
27977
|
+
const ids = [];
|
|
27978
|
+
for (const message of messages) {
|
|
27979
|
+
const id = message.runId;
|
|
27980
|
+
if (message.role !== "assistant" || id === void 0 || id === runId || ids.includes(id)) continue;
|
|
27981
|
+
ids.push(id);
|
|
27982
|
+
}
|
|
27983
|
+
return ids.reverse();
|
|
27984
|
+
}
|
|
27985
|
+
/**
|
|
27986
|
+
* 表格里那张图的字节(0.22.0 分册 20 FR-20.8)。
|
|
27987
|
+
*
|
|
27988
|
+
* 与投放文档共用 `#documentImageBytes`:AC-G1 要的不是几条通道行为一致,而是只有一条通道。
|
|
27989
|
+
* 「同一张图 Word 放得进、Excel 放不进」正是分叉出第二条通道的代价。
|
|
27990
|
+
*
|
|
27991
|
+
* 外链在这里是硬拒:xlsx 里没有「链着一张图」这回事,工作簿必须自带字节。
|
|
27992
|
+
*/
|
|
27993
|
+
async #spreadsheetImage(ref, runId) {
|
|
27994
|
+
if (runId === void 0) throw new WebSkillError("TOOL_EXECUTION_FAILED", "Putting a picture in a workbook requires an active run.");
|
|
27995
|
+
const separator = ref.indexOf(":");
|
|
27996
|
+
const kind = separator === -1 ? "" : ref.slice(0, separator);
|
|
27997
|
+
const rest = ref.slice(separator + 1);
|
|
27998
|
+
if (kind === "remote") throw new WebSkillError("VALIDATION_FAILED", `image reference "${ref}" cannot go into a workbook: a spreadsheet carries the bytes, not a link. Download it with fetch_remote_image first, then reference the artifact it returns.`);
|
|
27999
|
+
const image = await toDocumentImage(await this.#documentImageBytes(kind, rest, runId), { maxBytes: SPREADSHEET_LIMITS.imageBytes });
|
|
28000
|
+
return {
|
|
28001
|
+
bytes: Uint8Array.from(atob(image.data), (char) => char.charCodeAt(0)),
|
|
28002
|
+
contentType: image.mimeType
|
|
28003
|
+
};
|
|
28004
|
+
}
|
|
28005
|
+
/**
|
|
28006
|
+
* 会话里的上传件,从发起本 run 的那条用户消息起往前找。
|
|
28007
|
+
*
|
|
28008
|
+
* 与 `#priorRunIds` 同一个理由:「上传照片 → 出 Word → 再出 PPT」是两轮,
|
|
28009
|
+
* 第二轮那条用户消息上没有附件,只认本轮就必然缺图。附件不随轮次过期。
|
|
28010
|
+
*
|
|
28011
|
+
* 仍然**在消息里查**而不是拿引用当路径拼:候选全部是用户自己在这个会话里上传的东西,
|
|
28012
|
+
* 路径一寸都不来自模型,跨会话与路径穿越都无从谈起(FR-11.7)。
|
|
28013
|
+
* 落到哪条附件上由 `findAttachmentByRef` 判,与正文 markdown 那一侧共用一份规则。
|
|
28014
|
+
*/
|
|
28015
|
+
async #uploadOfRun(runId, ref) {
|
|
28016
|
+
const sessionId = await this.#sessionOfRun(runId);
|
|
28017
|
+
if (sessionId === void 0) return void 0;
|
|
28018
|
+
const messages = (await this.#sessions.get(sessionId).catch(() => void 0))?.messages ?? [];
|
|
28019
|
+
const started = messages.findIndex((message) => message.role === "assistant" && message.runId === runId);
|
|
28020
|
+
for (let i = (started === -1 ? messages.length : started) - 1; i >= 0; i -= 1) {
|
|
28021
|
+
const message = messages[i];
|
|
28022
|
+
if (message?.role !== "user") continue;
|
|
28023
|
+
const hit = findAttachmentByRef(message.attachments ?? [], ref);
|
|
28024
|
+
if (hit !== void 0) return hit;
|
|
28025
|
+
}
|
|
28026
|
+
}
|
|
28027
|
+
/**
|
|
27400
28028
|
* 产物名 → 绝对路径。`assertSafePathSegment` + `resolveInsideRoot` 两道都不是可选写法:
|
|
27401
28029
|
* 少了它们,「组件只收产物名」就退化成「组件收完整路径」,凭空开出一个读取面。
|
|
27402
28030
|
*/
|
|
@@ -27405,6 +28033,84 @@ var ChatEngine = class {
|
|
|
27405
28033
|
return resolveInsideRoot(dir, artifact);
|
|
27406
28034
|
}
|
|
27407
28035
|
/**
|
|
28036
|
+
* 把宿主页面上的一张图落成本 run 的产物(0.22.0 FR-12.1 / FR-12.7)。
|
|
28037
|
+
*
|
|
28038
|
+
* 三件事按顺序发生且都在引擎侧:授权 → 取字节 → 落产物。宿主只做中间那一步,
|
|
28039
|
+
* 因为「run 目录在哪、会话什么时候换」只有引擎知道。
|
|
28040
|
+
*/
|
|
28041
|
+
async #capturePageImage(host, artifacts, input, runId) {
|
|
28042
|
+
if (runId === void 0) throw new WebSkillError("TOOL_EXECUTION_FAILED", "Capturing a picture requires an active run.");
|
|
28043
|
+
const note = input.note === void 0 ? void 0 : sanitizeUntrustedLine(input.note);
|
|
28044
|
+
if (!await (this.#imageSources ??= new ImageSourceAuthorizer({ ui: this.#uiBridge })).authorize("page-image", note === void 0 ? {} : { note })) return {
|
|
28045
|
+
reason: "the user declined to let the assistant copy pictures from the page",
|
|
28046
|
+
triedLevels: []
|
|
28047
|
+
};
|
|
28048
|
+
const captured = await host.capture(input.ref, MAX_DOCUMENT_IMAGE_BYTES);
|
|
28049
|
+
if ("reason" in captured) return {
|
|
28050
|
+
reason: captured.reason,
|
|
28051
|
+
triedLevels: captured.triedLevels
|
|
28052
|
+
};
|
|
28053
|
+
const seq = (this.#pageImageSeq.get(runId) ?? 0) + 1;
|
|
28054
|
+
this.#pageImageSeq.set(runId, seq);
|
|
28055
|
+
const path = `page-image-${String(seq)}.${captured.mimeType === "image/jpeg" ? "jpg" : "png"}`;
|
|
28056
|
+
await artifacts.createBinaryArtifact({
|
|
28057
|
+
runId,
|
|
28058
|
+
path,
|
|
28059
|
+
content: Uint8Array.from(atob(captured.data), (char) => char.charCodeAt(0)),
|
|
28060
|
+
mimeType: captured.mimeType,
|
|
28061
|
+
metadata: { resultCard: false }
|
|
28062
|
+
});
|
|
28063
|
+
return {
|
|
28064
|
+
artifact: `artifact:${path}`,
|
|
28065
|
+
width: captured.width,
|
|
28066
|
+
height: captured.height,
|
|
28067
|
+
bytes: captured.bytes
|
|
28068
|
+
};
|
|
28069
|
+
}
|
|
28070
|
+
/**
|
|
28071
|
+
* 把一张外链图落成本 run 的产物(0.22.0 FR-13.1 ~ FR-13.4)。
|
|
28072
|
+
*
|
|
28073
|
+
* 与 `#capturePageImage` 同一条顺序:授权 → 取字节 → 落产物。
|
|
28074
|
+
* **授权在 `fetch` 之前**不是风格问题:拒绝之后网络层必须一个请求都没发出去(AC-13.5),
|
|
28075
|
+
* 「先抓回来再丢掉」时用户的 IP 已经交出去了。
|
|
28076
|
+
*/
|
|
28077
|
+
async #fetchRemoteImage(host, artifacts, input, runId) {
|
|
28078
|
+
if (runId === void 0) throw new WebSkillError("TOOL_EXECUTION_FAILED", "Downloading an image requires an active run.");
|
|
28079
|
+
const note = input.note === void 0 ? void 0 : sanitizeUntrustedLine(input.note);
|
|
28080
|
+
if (!await (this.#imageSources ??= new ImageSourceAuthorizer({ ui: this.#uiBridge })).authorize("remote-image", {
|
|
28081
|
+
...note === void 0 ? {} : { note },
|
|
28082
|
+
url: sanitizeUntrustedLine(input.url)
|
|
28083
|
+
})) return {
|
|
28084
|
+
reason: "the user declined to let the assistant download images from the web",
|
|
28085
|
+
kind: "declined"
|
|
28086
|
+
};
|
|
28087
|
+
const fetched = await host.fetch(input.url, MAX_DOCUMENT_IMAGE_BYTES);
|
|
28088
|
+
if ("kind" in fetched) return {
|
|
28089
|
+
reason: fetched.reason,
|
|
28090
|
+
kind: fetched.kind
|
|
28091
|
+
};
|
|
28092
|
+
const seq = (this.#remoteImageSeq.get(runId) ?? 0) + 1;
|
|
28093
|
+
this.#remoteImageSeq.set(runId, seq);
|
|
28094
|
+
const path = `remote-image-${String(seq)}.${fetched.mimeType === "image/jpeg" ? "jpg" : "png"}`;
|
|
28095
|
+
await artifacts.createBinaryArtifact({
|
|
28096
|
+
runId,
|
|
28097
|
+
path,
|
|
28098
|
+
content: Uint8Array.from(atob(fetched.data), (char) => char.charCodeAt(0)),
|
|
28099
|
+
mimeType: fetched.mimeType,
|
|
28100
|
+
metadata: { resultCard: false }
|
|
28101
|
+
});
|
|
28102
|
+
return {
|
|
28103
|
+
artifact: `artifact:${path}`,
|
|
28104
|
+
width: fetched.width,
|
|
28105
|
+
height: fetched.height,
|
|
28106
|
+
bytes: fetched.bytes,
|
|
28107
|
+
...input.describe === true ? {
|
|
28108
|
+
data: fetched.data,
|
|
28109
|
+
mimeType: fetched.mimeType
|
|
28110
|
+
} : {}
|
|
28111
|
+
};
|
|
28112
|
+
}
|
|
28113
|
+
/**
|
|
27408
28114
|
* 这次 run 激活过的技能名。它是授权告知的主语,因此**不能**由发出 surface 的技能自报——
|
|
27409
28115
|
* 那等于让被授权方自己写授权提示。
|
|
27410
28116
|
*
|
|
@@ -27511,6 +28217,7 @@ var ChatEngine = class {
|
|
|
27511
28217
|
});
|
|
27512
28218
|
this.#currentSessionId = session.id;
|
|
27513
28219
|
this.#visionDelegate?.forgetSessionDecision();
|
|
28220
|
+
this.#imageSources?.forgetSessionDecisions();
|
|
27514
28221
|
if (previous !== void 0 && previous !== session.id) {
|
|
27515
28222
|
this.#endSession(previous, "switched");
|
|
27516
28223
|
this.#applySelectedModel(void 0);
|
|
@@ -27529,6 +28236,7 @@ var ChatEngine = class {
|
|
|
27529
28236
|
const previous = this.#currentSessionId;
|
|
27530
28237
|
this.#currentSessionId = id;
|
|
27531
28238
|
if (previous !== void 0 && previous !== id) this.#endSession(previous, "switched");
|
|
28239
|
+
if (previous !== id) this.#imageSources?.forgetSessionDecisions();
|
|
27532
28240
|
if (options.cursor === void 0) {
|
|
27533
28241
|
const stored = (await this.#sessions.getMeta(id))?.modelId;
|
|
27534
28242
|
this.#applySelectedModel(stored ?? [...page.items].reverse().find((message) => message.model !== void 0)?.model);
|
|
@@ -27746,12 +28454,26 @@ var ChatEngine = class {
|
|
|
27746
28454
|
text: handle
|
|
27747
28455
|
});
|
|
27748
28456
|
if (attachment.kind === "text" || attachment.kind === "document-text") {
|
|
27749
|
-
const body = await this.#
|
|
28457
|
+
const body = await this.#attachmentText(attachment, path);
|
|
28458
|
+
if (body === void 0) {
|
|
28459
|
+
parts.push({
|
|
28460
|
+
type: "text",
|
|
28461
|
+
text: `Attachment "${attachment.name}" (${attachment.contentType}) is attached, but no text extractor for this format is available in this environment, so its body is not inlined below.` + (this.#hasDocumentReader(attachment) ? ` Use read_document with id ${attachment.id} to read it unit by unit.` : "")
|
|
28462
|
+
});
|
|
28463
|
+
continue;
|
|
28464
|
+
}
|
|
27750
28465
|
const routed = this.#routeText(attachment, body, attachment.kind);
|
|
27751
28466
|
parts.push(routed.part);
|
|
27752
28467
|
if (routed.notice !== void 0) notices.push(routed.notice);
|
|
27753
28468
|
continue;
|
|
27754
28469
|
}
|
|
28470
|
+
if (attachment.kind === "binary") {
|
|
28471
|
+
parts.push({
|
|
28472
|
+
type: "text",
|
|
28473
|
+
text: `Attachment "${attachment.name}" (${attachment.size} bytes) is available to tools as id ${attachment.id}. Its contents are not included here — pass the id to a tool that reads this format.`
|
|
28474
|
+
});
|
|
28475
|
+
continue;
|
|
28476
|
+
}
|
|
27755
28477
|
const data = toBase64(await this.#adapter.storage.readBinary(path));
|
|
27756
28478
|
if (attachment.kind === "image") {
|
|
27757
28479
|
parts.push({
|
|
@@ -27775,7 +28497,7 @@ var ChatEngine = class {
|
|
|
27775
28497
|
};
|
|
27776
28498
|
}
|
|
27777
28499
|
/**
|
|
27778
|
-
* 文本类附件进上下文的预算(`loop.maxDocumentTextBytes`,默认
|
|
28500
|
+
* 文本类附件进上下文的预算(`loop.maxDocumentTextBytes`,默认 5000 KB)。
|
|
27779
28501
|
* 与 agentLoop 的 `#documentTooLarge` 同一个值、同一条口径。
|
|
27780
28502
|
*/
|
|
27781
28503
|
#textBudget() {
|
|
@@ -27801,11 +28523,38 @@ var ChatEngine = class {
|
|
|
27801
28523
|
* 给了 id 就是教模型去调一个不存在的工具。
|
|
27802
28524
|
*/
|
|
27803
28525
|
#documentHandleLabel(attachment) {
|
|
27804
|
-
|
|
27805
|
-
if ((mime === SUPPORTED_DOCUMENT_MIME.pdf ? this.#adapter.pdfDocumentReader : mime === SUPPORTED_DOCUMENT_MIME.docx ? this.#adapter.docxDocumentReader : mime === SUPPORTED_DOCUMENT_MIME.xlsx ? this.#adapter.xlsxDocumentReader : void 0) === void 0) return void 0;
|
|
28526
|
+
if (!this.#hasDocumentReader(attachment)) return void 0;
|
|
27806
28527
|
return `--- Attachment: ${attachment.name} (${attachment.contentType}, id: ${attachment.id}) ---\nread_document accepts this id while this turn lasts. Use it whenever you need the pictures, charts, diagrams or scanned pages inside this document: extracted text never contains them, and an endpoint that refuses attached files leaves you with the text alone. Do not answer "the document has no images" without reading it this way first.`;
|
|
27807
28528
|
}
|
|
27808
28529
|
/**
|
|
28530
|
+
* 这份附件的格式有没有注册文档读取器,即 `read_document` 能不能读它(0.22.0 分册 16 FR-16.1)。
|
|
28531
|
+
*
|
|
28532
|
+
* 取件号与「超限改走分批」共用这一个判据:判反了不是少一句说明,
|
|
28533
|
+
* 而是给出一个调不到的工具(没 reader 却给 id),或者白白丢掉全文(有 reader 却当没有)。
|
|
28534
|
+
*/
|
|
28535
|
+
#hasDocumentReader(attachment) {
|
|
28536
|
+
const mime = attachment.contentType.split(";")[0]?.trim().toLowerCase();
|
|
28537
|
+
return (mime === SUPPORTED_DOCUMENT_MIME.pdf ? this.#adapter.pdfDocumentReader : mime === SUPPORTED_DOCUMENT_MIME.docx ? this.#adapter.docxDocumentReader : mime === SUPPORTED_DOCUMENT_MIME.xlsx ? this.#adapter.xlsxDocumentReader : mime === SUPPORTED_DOCUMENT_MIME.pptx ? this.#adapter.pptxDocumentReader : void 0) !== void 0;
|
|
28538
|
+
}
|
|
28539
|
+
/**
|
|
28540
|
+
* 文本类附件的正文。`text` 存的就是文本;`document-text` 存的是**原件**,文本在这里现抽。
|
|
28541
|
+
*
|
|
28542
|
+
* 抽取推迟到发送,是为了让 `read_document` 还能拿到原件字节去读图表与扫描页——
|
|
28543
|
+
* 上传时就抽掉的话,那些东西在磁盘上根本不存在了(0.22.0 分册 18)。
|
|
28544
|
+
*
|
|
28545
|
+
* 返回 `undefined` = 本环境抽不出文本。不抛错:附件是上传时被收下的,
|
|
28546
|
+
* 此刻让整条消息发不出去等于拿用户刚打的字陪绑,而原件还在盘上,模型仍可按 id 逐块读。
|
|
28547
|
+
*/
|
|
28548
|
+
async #attachmentText(attachment, path) {
|
|
28549
|
+
if (attachment.kind === "text") return await this.#adapter.storage.readText(path);
|
|
28550
|
+
const bytes = await this.#adapter.storage.readBinary(path);
|
|
28551
|
+
if (!(bytes[0] === 80 && bytes[1] === 75)) return new TextDecoder().decode(bytes);
|
|
28552
|
+
const format = extractedDocumentFormat(attachment.contentType, attachment.name);
|
|
28553
|
+
const extract = format === "xlsx" ? this.#adapter.xlsxExtractor : format === "pptx" ? this.#adapter.pptxExtractor : this.#adapter.docxExtractor;
|
|
28554
|
+
if (extract === void 0) return void 0;
|
|
28555
|
+
return await extract(bytes);
|
|
28556
|
+
}
|
|
28557
|
+
/**
|
|
27809
28558
|
* 文本类附件(纯文本 / docx / xlsx / 抽取出来的 PDF 文本)的发送形态。
|
|
27810
28559
|
*
|
|
27811
28560
|
* 预算走 `maxDocumentTextBytes` 而不是 `formatAttachmentText` 的 32 KiB 兜底:
|
|
@@ -27817,16 +28566,24 @@ var ChatEngine = class {
|
|
|
27817
28566
|
const size = new TextEncoder().encode(body).length;
|
|
27818
28567
|
if (size > max) {
|
|
27819
28568
|
const locale = this.#liveConfig?.appearance.locale ?? "en";
|
|
28569
|
+
const params = {
|
|
28570
|
+
name: attachment.name,
|
|
28571
|
+
size: formatSize(size),
|
|
28572
|
+
limit: formatSize(max)
|
|
28573
|
+
};
|
|
28574
|
+
if (this.#hasDocumentReader(attachment)) return {
|
|
28575
|
+
part: {
|
|
28576
|
+
type: "text",
|
|
28577
|
+
text: `--- Attachment: ${attachment.name} (${attachment.contentType}, not attached, read it in parts) ---\nIts text alone is ${size} bytes, over the ${max} byte context limit, so it is not in this message. It was not truncated, because a partial document would be unusable. Call read_document with id ${attachment.id} to read it a block at a time — each result tells you how much is left and which cursor continues from there. Read as many times as the task needs before answering.`
|
|
28578
|
+
},
|
|
28579
|
+
notice: translateText(locale, "attachment.pdfAsPagedRead", params)
|
|
28580
|
+
};
|
|
27820
28581
|
return {
|
|
27821
28582
|
part: {
|
|
27822
28583
|
type: "text",
|
|
27823
28584
|
text: `--- Attachment: ${attachment.name} (${attachment.contentType}, not attached) ---\nIts text is ${size} bytes, over the ${max} byte context limit for attached text. It was not truncated, because a partial document would be unusable. Tell the user to raise the extracted-text limit in settings, or to attach a shorter excerpt or a specific section.`
|
|
27824
28585
|
},
|
|
27825
|
-
notice: translateText(locale, "attachment.textTooLong",
|
|
27826
|
-
name: attachment.name,
|
|
27827
|
-
size: formatSize(size),
|
|
27828
|
-
limit: formatSize(max)
|
|
27829
|
-
})
|
|
28586
|
+
notice: translateText(locale, "attachment.textTooLong", params)
|
|
27830
28587
|
};
|
|
27831
28588
|
}
|
|
27832
28589
|
return { part: {
|
|
@@ -27841,9 +28598,11 @@ var ChatEngine = class {
|
|
|
27841
28598
|
} };
|
|
27842
28599
|
}
|
|
27843
28600
|
/**
|
|
27844
|
-
* 单个 PDF 附件的发送形态(FR-18.5
|
|
27845
|
-
*
|
|
27846
|
-
*
|
|
28601
|
+
* 单个 PDF 附件的发送形态(FR-18.5;0.22.0 分册 16 改写超阈值那一支)。
|
|
28602
|
+
*
|
|
28603
|
+
* 阈值内直传原件——保住扫描件、版面与图表。超阈值分两种走法:
|
|
28604
|
+
* 该格式**注册了读取器**时交给 `read_document` 分批读(FR-16.1),正文一个字都不进消息;
|
|
28605
|
+
* 没有读取器时才退到抽取文本,抽不出就**拒绝并说明**,绝不截断(FR-18.5a)。
|
|
27847
28606
|
*/
|
|
27848
28607
|
async #routeDocument(attachment, data) {
|
|
27849
28608
|
const max = this.#documentBudget();
|
|
@@ -27867,6 +28626,13 @@ var ChatEngine = class {
|
|
|
27867
28626
|
},
|
|
27868
28627
|
notice: translateText(locale, key, params)
|
|
27869
28628
|
});
|
|
28629
|
+
if (this.#hasDocumentReader(attachment)) return {
|
|
28630
|
+
part: {
|
|
28631
|
+
type: "text",
|
|
28632
|
+
text: `${label("not attached, read it in pages")}The document is ${data.length} bytes once encoded, over the ${max} byte request limit, so the file itself is not in this message. Its text is not here either: a document this size does not fit the context window in one piece, and a partial copy would be unusable. Call read_document with id ${attachment.id} to read it page by page — each result tells you how much is left and which cursor continues from there. Read as many times as the task needs before answering.`
|
|
28633
|
+
},
|
|
28634
|
+
notice: translateText(locale, "attachment.pdfAsPagedRead", params)
|
|
28635
|
+
};
|
|
27870
28636
|
const extract = this.#adapter.pdfExtractor;
|
|
27871
28637
|
if (extract === void 0) return refuse("This environment has no PDF text extractor, so no fallback is available. Tell the user to switch to a model that accepts large documents, or to attach the content as a Word or plain-text file instead.", "attachment.pdfNoExtractor");
|
|
27872
28638
|
let body;
|
|
@@ -28227,6 +28993,7 @@ var ChatEngine = class {
|
|
|
28227
28993
|
...this.#adapter.downloads.consent ? { consent: this.#adapter.downloads.consent } : {},
|
|
28228
28994
|
...this.#adapter.docxExtractor ? { docxExtractor: this.#adapter.docxExtractor } : {},
|
|
28229
28995
|
...this.#adapter.xlsxExtractor ? { xlsxExtractor: this.#adapter.xlsxExtractor } : {},
|
|
28996
|
+
...this.#adapter.pptxExtractor ? { pptxExtractor: this.#adapter.pptxExtractor } : {},
|
|
28230
28997
|
...this.#adapter.pdfExtractor ? { pdfExtractor: this.#adapter.pdfExtractor } : {},
|
|
28231
28998
|
maxDocumentBytes: () => this.#documentBudget(),
|
|
28232
28999
|
...this.#adapter.documentAudit ? { audit: this.#adapter.documentAudit } : {}
|
|
@@ -28235,17 +29002,55 @@ var ChatEngine = class {
|
|
|
28235
29002
|
root: `${this.#chatRoot}/artifacts`,
|
|
28236
29003
|
fs
|
|
28237
29004
|
});
|
|
29005
|
+
const readDocumentBytes = async (id, context) => {
|
|
29006
|
+
const attachment = this.#runAttachments.find((a) => a.id === id);
|
|
29007
|
+
if (attachment) {
|
|
29008
|
+
const bytes = await this.#adapter.storage.readBinary(`${this.attachmentsRoot}/${attachment.path}`);
|
|
29009
|
+
return {
|
|
29010
|
+
name: attachment.name,
|
|
29011
|
+
bytes
|
|
29012
|
+
};
|
|
29013
|
+
}
|
|
29014
|
+
const stored = context?.binaries?.get(id);
|
|
29015
|
+
if (stored) return {
|
|
29016
|
+
name: stored.name,
|
|
29017
|
+
bytes: stored.bytes
|
|
29018
|
+
};
|
|
29019
|
+
if (!downloadPolicy) return void 0;
|
|
29020
|
+
const downloaded = await downloadPolicy.readBytes(id);
|
|
29021
|
+
return {
|
|
29022
|
+
name: downloaded.entry.name,
|
|
29023
|
+
bytes: downloaded.bytes
|
|
29024
|
+
};
|
|
29025
|
+
};
|
|
29026
|
+
/**
|
|
29027
|
+
* 模板字节比 `read_document` 多认一条取件号:`weboffice:<handle>`(0.22.0 分册 42 FR-42.12)。
|
|
29028
|
+
*
|
|
29029
|
+
* 多的只是**取件**这一段——拿到字节之后走的与本地模板是同一条路(FR-42.12 后半句)。
|
|
29030
|
+
* 前缀是显式的:不去猜某个 id 会不会恰好是个 weboffice handle,猜错的代价是
|
|
29031
|
+
* 拿了另一份文档冒充用户指的那一份。前缀不匹配时连问都不问,直接当作没有。
|
|
29032
|
+
*/
|
|
29033
|
+
const readTemplateBytes = async (id, context) => {
|
|
29034
|
+
const local = await readDocumentBytes(id, context);
|
|
29035
|
+
if (local) return local;
|
|
29036
|
+
const online = this.#adapter.webOfficeTemplateSource;
|
|
29037
|
+
if (!online || !id.startsWith("weboffice:")) return void 0;
|
|
29038
|
+
return await online.read(id.slice(10), context);
|
|
29039
|
+
};
|
|
28238
29040
|
const baseTools = [
|
|
28239
29041
|
...pageSkillSource ? [pageSkillSource] : [],
|
|
28240
|
-
createSpreadsheetToolSource({
|
|
28241
|
-
|
|
28242
|
-
|
|
28243
|
-
|
|
28244
|
-
|
|
28245
|
-
|
|
28246
|
-
|
|
28247
|
-
|
|
28248
|
-
|
|
29042
|
+
createSpreadsheetToolSource({
|
|
29043
|
+
writeArtifact: (path, content, mimeType, context) => {
|
|
29044
|
+
if (!context?.runId) throw new WebSkillError("TOOL_EXECUTION_FAILED", "Writing a workbook requires an active run.");
|
|
29045
|
+
return artifactStore.createBinaryArtifact({
|
|
29046
|
+
runId: context.runId,
|
|
29047
|
+
path,
|
|
29048
|
+
content,
|
|
29049
|
+
mimeType
|
|
29050
|
+
});
|
|
29051
|
+
},
|
|
29052
|
+
resolveImage: (ref, context) => this.#spreadsheetImage(ref, context?.runId)
|
|
29053
|
+
}),
|
|
28249
29054
|
...(rc === void 0 ? this.#options.generativeUi === true : rc.agentCapabilities.generativeUi) ? [createUiCatalogToolSource(rc ? { presets: rc.agentCapabilities.uiPresets } : {})] : [],
|
|
28250
29055
|
...rc?.agentCapabilities.todo === false ? [] : [createTodoToolSource({ store: this.#todoStore })],
|
|
28251
29056
|
...rc?.agentCapabilities.skillGeneration === true ? [createSkillGenerationToolSource({
|
|
@@ -28272,29 +29077,51 @@ var ChatEngine = class {
|
|
|
28272
29077
|
}
|
|
28273
29078
|
})] : [],
|
|
28274
29079
|
...this.#adapter.pageActions ? [createPageActionToolSource({ policy: this.#adapter.pageActions })] : [],
|
|
29080
|
+
...this.#adapter.pageImage ? [createPageImageToolSource({ capture: (input, context) => this.#capturePageImage(this.#adapter.pageImage, artifactStore, input, context?.runId) })] : [],
|
|
29081
|
+
...this.#adapter.remoteImage ? [createRemoteImageToolSource({ fetchImage: (input, context) => this.#fetchRemoteImage(this.#adapter.remoteImage, artifactStore, input, context?.runId) })] : [],
|
|
28275
29082
|
...this.#adapter.pageTabs ? [createTabWorksetToolSource({ policy: this.#adapter.pageTabs })] : [],
|
|
28276
29083
|
...downloadPolicy ? [createDownloadedFileToolSource({ policy: downloadPolicy })] : [],
|
|
28277
29084
|
createDocumentToolSource({
|
|
28278
|
-
files: { read:
|
|
28279
|
-
const attachment = this.#runAttachments.find((a) => a.id === id);
|
|
28280
|
-
if (attachment) {
|
|
28281
|
-
const bytes = await this.#adapter.storage.readBinary(`${this.attachmentsRoot}/${attachment.path}`);
|
|
28282
|
-
return {
|
|
28283
|
-
name: attachment.name,
|
|
28284
|
-
bytes
|
|
28285
|
-
};
|
|
28286
|
-
}
|
|
28287
|
-
if (!downloadPolicy) return void 0;
|
|
28288
|
-
const downloaded = await downloadPolicy.readBytes(id);
|
|
28289
|
-
return {
|
|
28290
|
-
name: downloaded.entry.name,
|
|
28291
|
-
bytes: downloaded.bytes
|
|
28292
|
-
};
|
|
28293
|
-
} },
|
|
29085
|
+
files: { read: readDocumentBytes },
|
|
28294
29086
|
...this.#adapter.pdfDocumentReader ? { pdf: this.#adapter.pdfDocumentReader } : {},
|
|
28295
29087
|
...this.#adapter.docxDocumentReader ? { docx: this.#adapter.docxDocumentReader } : {},
|
|
28296
|
-
...this.#adapter.xlsxDocumentReader ? { xlsx: this.#adapter.xlsxDocumentReader } : {}
|
|
29088
|
+
...this.#adapter.xlsxDocumentReader ? { xlsx: this.#adapter.xlsxDocumentReader } : {},
|
|
29089
|
+
...this.#adapter.pptxDocumentReader ? { pptx: this.#adapter.pptxDocumentReader } : {}
|
|
28297
29090
|
}),
|
|
29091
|
+
...this.#adapter.xlsxTemplateEngine || this.#adapter.docxTemplateEngine ? [createTemplateToolSource({
|
|
29092
|
+
files: { read: readTemplateBytes },
|
|
29093
|
+
...this.#adapter.webOfficeTemplateSource ? { idHint: `For a document open in an online WPS/WebOffice viewer, use "${WEB_OFFICE_TEMPLATE_PREFIX}<handle>" with the handle from list_weboffice_documents.` } : {},
|
|
29094
|
+
...this.#adapter.xlsxTemplateEngine ? { xlsx: this.#adapter.xlsxTemplateEngine } : {},
|
|
29095
|
+
...this.#adapter.docxTemplateEngine ? { docx: this.#adapter.docxTemplateEngine } : {},
|
|
29096
|
+
writeArtifact: (path, content, mimeType, context) => {
|
|
29097
|
+
if (!context?.runId) throw new WebSkillError("TOOL_EXECUTION_FAILED", "Filling a template requires an active run.");
|
|
29098
|
+
return artifactStore.createBinaryArtifact({
|
|
29099
|
+
runId: context.runId,
|
|
29100
|
+
path,
|
|
29101
|
+
content,
|
|
29102
|
+
mimeType
|
|
29103
|
+
});
|
|
29104
|
+
},
|
|
29105
|
+
resolveImage: async (ref, context) => {
|
|
29106
|
+
const found = await this.#spreadsheetImage(ref, context?.runId);
|
|
29107
|
+
return {
|
|
29108
|
+
bytes: found.bytes,
|
|
29109
|
+
extension: found.contentType.replace(/^image\//, "") || "png",
|
|
29110
|
+
contentType: found.contentType
|
|
29111
|
+
};
|
|
29112
|
+
}
|
|
29113
|
+
})] : [],
|
|
29114
|
+
...this.#adapter.dwg ? [createDwgToolSource({
|
|
29115
|
+
load: async (file, context) => {
|
|
29116
|
+
const found = await readDocumentBytes(file, context);
|
|
29117
|
+
if (!found) throw new WebSkillError("TOOL_EXECUTION_FAILED", `No file named "${file}" is available. Use one from the download list, a page link, or an attachment.`);
|
|
29118
|
+
return {
|
|
29119
|
+
document: await this.#adapter.dwg.read(found.bytes, found.name),
|
|
29120
|
+
name: found.name
|
|
29121
|
+
};
|
|
29122
|
+
},
|
|
29123
|
+
project: (input) => this.#adapter.dwg.project(input)
|
|
29124
|
+
})] : [],
|
|
28298
29125
|
...this.#adapter.webOffice ? [createWebOfficeToolSource({ policy: new WebOfficePolicy({
|
|
28299
29126
|
host: this.#adapter.webOffice.host,
|
|
28300
29127
|
ui: this.#uiBridge,
|
|
@@ -28345,6 +29172,7 @@ var ChatEngine = class {
|
|
|
28345
29172
|
...this.#adapter.linkedDocuments ? { linkedDocuments: this.#adapter.linkedDocuments } : {},
|
|
28346
29173
|
...this.#adapter.docxExtractor ? { docxExtractor: this.#adapter.docxExtractor } : {},
|
|
28347
29174
|
...this.#adapter.xlsxExtractor ? { xlsxExtractor: this.#adapter.xlsxExtractor } : {},
|
|
29175
|
+
...this.#adapter.pptxExtractor ? { pptxExtractor: this.#adapter.pptxExtractor } : {},
|
|
28348
29176
|
...this.#adapter.pdfExtractor ? { pdfExtractor: this.#adapter.pdfExtractor } : {},
|
|
28349
29177
|
visionDelegate: this.#visionDelegate,
|
|
28350
29178
|
...this.#adapter.documentAudit ? { documentAudit: this.#adapter.documentAudit } : {},
|
|
@@ -28366,6 +29194,7 @@ var ChatEngine = class {
|
|
|
28366
29194
|
})] : []]
|
|
28367
29195
|
});
|
|
28368
29196
|
runtime.events.on("*", (event) => {
|
|
29197
|
+
this.#runSessions.set(event.runId, event.sessionId);
|
|
28369
29198
|
if (event.phase === "route") {
|
|
28370
29199
|
this.#runSkills.set(event.runId, /* @__PURE__ */ new Set());
|
|
28371
29200
|
this.#runPhases.delete(event.runId);
|
|
@@ -28541,6 +29370,7 @@ var ChatEngine = class {
|
|
|
28541
29370
|
message: assistantMessage
|
|
28542
29371
|
});
|
|
28543
29372
|
this.#runSkills.delete(run.id);
|
|
29373
|
+
this.#runSessions.delete(run.id);
|
|
28544
29374
|
this.#runTodos.delete(run.id);
|
|
28545
29375
|
this.#runPerceptions.delete(run.id);
|
|
28546
29376
|
this.#runCandidates.delete(run.id);
|
|
@@ -29821,6 +30651,44 @@ function visionDelegationDetails(details) {
|
|
|
29821
30651
|
...typeof rememberLabel === "string" ? { rememberLabel } : {}
|
|
29822
30652
|
};
|
|
29823
30653
|
}
|
|
30654
|
+
/**
|
|
30655
|
+
* 图片来源授权卡的载荷(0.22.0 分册 11 §3.3 / 分册 12)。
|
|
30656
|
+
*
|
|
30657
|
+
* `kind` 决定风险说的是哪一件事:取页面上的图、还是去互联网上下载一张。
|
|
30658
|
+
* 两者共用一个能力名,所以**没有 `kind` 就不能渲染**——缺省回退成另一句话
|
|
30659
|
+
* 等于让用户在看不见来源的情况下点同意。
|
|
30660
|
+
*/
|
|
30661
|
+
function imageSourceDetails(details) {
|
|
30662
|
+
if (typeof details !== "object" || details === null) return void 0;
|
|
30663
|
+
const record = details;
|
|
30664
|
+
const kind = record["kind"];
|
|
30665
|
+
if (kind !== "page-image" && kind !== "remote-image") return void 0;
|
|
30666
|
+
const note = record["note"];
|
|
30667
|
+
const url = record["url"];
|
|
30668
|
+
const rememberLabelKey = record["rememberLabelKey"];
|
|
30669
|
+
return {
|
|
30670
|
+
kind,
|
|
30671
|
+
...typeof note === "string" && note !== "" ? { note } : {},
|
|
30672
|
+
...typeof url === "string" && url !== "" ? { url } : {},
|
|
30673
|
+
...record["rememberable"] === true ? { rememberable: true } : {},
|
|
30674
|
+
...record["rememberScope"] === "session" ? { rememberScope: "session" } : {},
|
|
30675
|
+
...typeof rememberLabelKey === "string" ? { rememberLabelKey } : {}
|
|
30676
|
+
};
|
|
30677
|
+
}
|
|
30678
|
+
/**
|
|
30679
|
+
* 这个地址是不是明文 http。
|
|
30680
|
+
*
|
|
30681
|
+
* 只认真正的 `http:` 协议,不做子串匹配——`https://a/?next=http://b` 里的那截
|
|
30682
|
+
* 不是本次要发的请求,据此吓唬用户会让这条提示很快被当成噪音忽略掉。
|
|
30683
|
+
*/
|
|
30684
|
+
function isInsecureUrl(url) {
|
|
30685
|
+
if (url === void 0) return false;
|
|
30686
|
+
try {
|
|
30687
|
+
return new URL(url).protocol === "http:";
|
|
30688
|
+
} catch {
|
|
30689
|
+
return false;
|
|
30690
|
+
}
|
|
30691
|
+
}
|
|
29824
30692
|
/** 五个沙箱能力闸门的共用载荷(`gate()` 统一注入 `skill`,其余按能力各带一项) */ function sandboxDetails(details) {
|
|
29825
30693
|
if (typeof details !== "object" || details === null) return void 0;
|
|
29826
30694
|
const record = details;
|
|
@@ -29910,6 +30778,14 @@ function localizedAuthorizeMessage(request, t) {
|
|
|
29910
30778
|
host: details.endpointHost
|
|
29911
30779
|
});
|
|
29912
30780
|
}
|
|
30781
|
+
if (request.capability === "readImageSource") {
|
|
30782
|
+
const details = imageSourceDetails(request.details);
|
|
30783
|
+
if (details === void 0) return void 0;
|
|
30784
|
+
const head = details.kind === "remote-image" && details.url !== void 0 ? t("interaction.imageSource.remoteImageBody", { url: details.url }) : t(`interaction.imageSource.${details.kind === "remote-image" ? "remoteImageAnyBody" : "pageImageBody"}`);
|
|
30785
|
+
const insecure = isInsecureUrl(details.url) ? ` ${t("interaction.imageSource.insecure")}` : "";
|
|
30786
|
+
if (details.note === void 0) return `${head}${insecure}`;
|
|
30787
|
+
return `${head}${insecure} ${t("interaction.imageSource.note", { note: details.note })}`;
|
|
30788
|
+
}
|
|
29913
30789
|
}
|
|
29914
30790
|
|
|
29915
30791
|
//#endregion
|
|
@@ -29976,7 +30852,7 @@ function InteractionWaitBar({ bridge, label, elsewhere = false, panel, onLocate
|
|
|
29976
30852
|
id: INTERACTION_WAIT_ID,
|
|
29977
30853
|
"data-testid": "interaction-wait",
|
|
29978
30854
|
role: "status",
|
|
29979
|
-
className: "webskill-chat-prose mx-auto flex w-full items-center gap-2 px-4 pt-2 text-xs text-muted",
|
|
30855
|
+
className: "webskill-chat-prose mx-auto flex w-full shrink-0 items-center gap-2 px-4 pt-2 text-xs text-muted",
|
|
29980
30856
|
children: [
|
|
29981
30857
|
/* @__PURE__ */ jsx(Hourglass, {
|
|
29982
30858
|
className: "size-3.5 shrink-0",
|
|
@@ -42189,6 +43065,8 @@ function attachmentAccept(options) {
|
|
|
42189
43065
|
if (options.files) parts.push(...FILE_MIME_TYPES);
|
|
42190
43066
|
if (options.documents) parts.push(DOCX_MIME, ".docx");
|
|
42191
43067
|
if (options.spreadsheets) parts.push(XLSX_MIME, ".xlsx");
|
|
43068
|
+
if (options.presentations) parts.push(PPTX_MIME, ".pptx");
|
|
43069
|
+
if (options.drawings) parts.push(...DWG_MIME_TYPES, ...BINARY_EXTENSIONS.map((ext) => `.${ext}`));
|
|
42192
43070
|
return parts.join(",");
|
|
42193
43071
|
}
|
|
42194
43072
|
|
|
@@ -42207,11 +43085,14 @@ function safeName(name) {
|
|
|
42207
43085
|
const cleaned = (name.split(/[\\/]/).pop() ?? "attachment").replace(/[^\w.\- ]+/g, "_").replace(/^\.+/, "");
|
|
42208
43086
|
return cleaned === "" ? "attachment" : cleaned.slice(0, 80);
|
|
42209
43087
|
}
|
|
42210
|
-
/**
|
|
42211
|
-
|
|
42212
|
-
|
|
42213
|
-
|
|
42214
|
-
|
|
43088
|
+
/** 三种待抽取格式共用 `document-text` 这一种 kind,选抽取器时要按格式再分一次 */
|
|
43089
|
+
const EXTRACTED_LABEL = {
|
|
43090
|
+
docx: "Word documents",
|
|
43091
|
+
xlsx: "Excel workbooks",
|
|
43092
|
+
pptx: "PowerPoint presentations"
|
|
43093
|
+
};
|
|
43094
|
+
function formatOf(file) {
|
|
43095
|
+
return extractedDocumentFormat(file.type, file.name);
|
|
42215
43096
|
}
|
|
42216
43097
|
/**
|
|
42217
43098
|
* 附件落盘到 `<attachmentsRoot>/<sessionId>/`,`ChatEngine.send` 再按路径读回构造 `LlmContentPart[]`。
|
|
@@ -42230,7 +43111,9 @@ var WebSkillAttachmentAdapter = class {
|
|
|
42230
43111
|
images: this.#imagesAllowed(),
|
|
42231
43112
|
files: true,
|
|
42232
43113
|
documents: this.#options.docxExtractor !== void 0,
|
|
42233
|
-
spreadsheets: this.#options.xlsxExtractor !== void 0
|
|
43114
|
+
spreadsheets: this.#options.xlsxExtractor !== void 0,
|
|
43115
|
+
presentations: this.#options.pptxExtractor !== void 0,
|
|
43116
|
+
drawings: this.#options.drawingsSupported?.() ?? false
|
|
42234
43117
|
});
|
|
42235
43118
|
}
|
|
42236
43119
|
#multimodal() {
|
|
@@ -42240,7 +43123,10 @@ var WebSkillAttachmentAdapter = class {
|
|
|
42240
43123
|
return this.#multimodal().imageAttachments && (this.#options.imageCapable?.() ?? false);
|
|
42241
43124
|
}
|
|
42242
43125
|
#extractorFor(file) {
|
|
42243
|
-
|
|
43126
|
+
const format = formatOf(file);
|
|
43127
|
+
if (format === "xlsx") return this.#options.xlsxExtractor;
|
|
43128
|
+
if (format === "pptx") return this.#options.pptxExtractor;
|
|
43129
|
+
return this.#options.docxExtractor;
|
|
42244
43130
|
}
|
|
42245
43131
|
async add({ file }) {
|
|
42246
43132
|
const kind = classifyAttachment(file.type, file.name);
|
|
@@ -42248,11 +43134,12 @@ var WebSkillAttachmentAdapter = class {
|
|
|
42248
43134
|
if (kind === "image") {
|
|
42249
43135
|
if (!this.#multimodal().imageAttachments) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", "Image attachments are turned off. Enable them in settings before sending images.");
|
|
42250
43136
|
if (!(this.#options.imageCapable?.() ?? false)) throw new WebSkillError("MODEL_IMAGE_UNSUPPORTED", "Current model does not support image input. Switch model or remove the image.");
|
|
42251
|
-
} else if (kind === "document-text" && this.#extractorFor(file) === void 0) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", `${
|
|
43137
|
+
} else if (kind === "document-text" && this.#extractorFor(file) === void 0) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", `${EXTRACTED_LABEL[formatOf(file)]} are not supported in this environment: no ${formatOf(file)} extractor is wired up.`);
|
|
43138
|
+
else if (kind === "binary" && !(this.#options.drawingsSupported?.() ?? false)) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", `Drawings are not supported in this environment: no CAD reader is wired up.`);
|
|
42252
43139
|
const id = `att-${Date.now().toString(36)}-${Math.random().toString(36).slice(2, 8)}`;
|
|
42253
43140
|
const relative = `${this.#options.sessionId() ?? "pending"}/${id}-${safeName(file.name)}`;
|
|
42254
43141
|
const path = `${this.#options.root}/${relative}`;
|
|
42255
|
-
const contentType = file.type === "" ? "
|
|
43142
|
+
const contentType = file.type !== "" ? file.type : kind === "binary" ? "application/octet-stream" : "text/plain";
|
|
42256
43143
|
try {
|
|
42257
43144
|
this.#stored.set(id, await this.#store({
|
|
42258
43145
|
id,
|
|
@@ -42296,9 +43183,8 @@ var WebSkillAttachmentAdapter = class {
|
|
|
42296
43183
|
};
|
|
42297
43184
|
}
|
|
42298
43185
|
if (kind === "document-text") {
|
|
42299
|
-
|
|
42300
|
-
|
|
42301
|
-
await this.#options.storage.writeText(path, await extract(new Uint8Array(await file.arrayBuffer())));
|
|
43186
|
+
if (this.#extractorFor(file) === void 0) throw new WebSkillError("ATTACHMENT_TYPE_REJECTED", "No document text extractor is wired up in this environment.");
|
|
43187
|
+
await this.#writeStreaming(path, file);
|
|
42302
43188
|
return {
|
|
42303
43189
|
...base,
|
|
42304
43190
|
size: file.size
|
|
@@ -42311,6 +43197,13 @@ var WebSkillAttachmentAdapter = class {
|
|
|
42311
43197
|
size: file.size
|
|
42312
43198
|
};
|
|
42313
43199
|
}
|
|
43200
|
+
if (kind === "binary") {
|
|
43201
|
+
await this.#writeStreaming(path, file);
|
|
43202
|
+
return {
|
|
43203
|
+
...base,
|
|
43204
|
+
size: file.size
|
|
43205
|
+
};
|
|
43206
|
+
}
|
|
42314
43207
|
const compressed = await compressImageToBudget(file, this.#multimodal().maxImageBytes);
|
|
42315
43208
|
await this.#options.storage.writeBinary(path, new Uint8Array(await compressed.blob.arrayBuffer()));
|
|
42316
43209
|
const recoded = compressed.compressedBytes !== compressed.originalBytes;
|
|
@@ -42832,8 +43725,9 @@ function ActiveInteractionCard({ request, bridge }) {
|
|
|
42832
43725
|
const downloadedFile = isAuthorize && request.capability === "readDownloadedFile" ? downloadedFileDetails(request.details) : void 0;
|
|
42833
43726
|
const webOffice = isAuthorize && request.capability === "readWebOfficeDocument" ? webOfficeDetails(request.details) : void 0;
|
|
42834
43727
|
const visionDelegation = isAuthorize && request.capability === "delegateVision" ? visionDelegationDetails(request.details) : void 0;
|
|
43728
|
+
const imageSource = isAuthorize && request.capability === "readImageSource" ? imageSourceDetails(request.details) : void 0;
|
|
42835
43729
|
const traceEvidence = isAuthorize ? traceEvidenceDetails(request.details) : void 0;
|
|
42836
|
-
const localized = pageAction !== void 0 || downloadedFile !== void 0 || webOffice !== void 0 || visionDelegation !== void 0;
|
|
43730
|
+
const localized = pageAction !== void 0 || downloadedFile !== void 0 || webOffice !== void 0 || visionDelegation !== void 0 || imageSource !== void 0;
|
|
42837
43731
|
const spec = interactionToUiSpec(request.type === "authorize" && localized ? {
|
|
42838
43732
|
...request,
|
|
42839
43733
|
message: ""
|
|
@@ -43036,6 +43930,37 @@ function ActiveInteractionCard({ request, bridge }) {
|
|
|
43036
43930
|
}), visionDelegation.rememberLabel ?? t(visionDelegation.rememberScope === "endpoint" ? "interaction.visionDelegation.rememberFallback" : "interaction.visionDelegation.rememberSession")]
|
|
43037
43931
|
}) : null
|
|
43038
43932
|
] }) : null,
|
|
43933
|
+
imageSource ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
43934
|
+
/* @__PURE__ */ jsx("p", {
|
|
43935
|
+
"data-testid": "interaction-image-source-title",
|
|
43936
|
+
className: "text-sm font-medium text-ink",
|
|
43937
|
+
children: t("interaction.imageSource.title")
|
|
43938
|
+
}),
|
|
43939
|
+
/* @__PURE__ */ jsx("p", {
|
|
43940
|
+
"data-testid": "interaction-image-source-body",
|
|
43941
|
+
className: "text-sm text-ink",
|
|
43942
|
+
children: imageSource.kind === "remote-image" ? imageSource.url !== void 0 ? t("interaction.imageSource.remoteImageBody", { url: imageSource.url }) : t("interaction.imageSource.remoteImageAnyBody") : t("interaction.imageSource.pageImageBody")
|
|
43943
|
+
}),
|
|
43944
|
+
isInsecureUrl(imageSource.url) ? /* @__PURE__ */ jsx("p", {
|
|
43945
|
+
"data-testid": "interaction-image-source-insecure",
|
|
43946
|
+
className: "text-sm text-destructive",
|
|
43947
|
+
children: t("interaction.imageSource.insecure")
|
|
43948
|
+
}) : null,
|
|
43949
|
+
imageSource.note !== void 0 ? /* @__PURE__ */ jsx("p", {
|
|
43950
|
+
"data-testid": "interaction-image-source-note",
|
|
43951
|
+
className: "text-sm text-muted",
|
|
43952
|
+
children: t("interaction.imageSource.note", { note: imageSource.note })
|
|
43953
|
+
}) : null,
|
|
43954
|
+
imageSource.rememberable === true ? /* @__PURE__ */ jsxs("label", {
|
|
43955
|
+
className: "flex items-center gap-2 text-sm text-ink",
|
|
43956
|
+
children: [/* @__PURE__ */ jsx("input", {
|
|
43957
|
+
type: "checkbox",
|
|
43958
|
+
"data-testid": "interaction-image-source-remember",
|
|
43959
|
+
checked: remember,
|
|
43960
|
+
onChange: (event) => setRemember(event.currentTarget.checked)
|
|
43961
|
+
}), t(imageSource.rememberLabelKey ?? "interaction.imageSource.rememberFallback")]
|
|
43962
|
+
}) : null
|
|
43963
|
+
] }) : null,
|
|
43039
43964
|
traceEvidence ? /* @__PURE__ */ jsxs("div", {
|
|
43040
43965
|
"data-testid": "interaction-trace-evidence",
|
|
43041
43966
|
children: [/* @__PURE__ */ jsx("div", {
|
|
@@ -43694,6 +44619,7 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
|
|
|
43694
44619
|
multimodal: () => multimodalRef.current,
|
|
43695
44620
|
imageCapable: () => imageCapableRef.current,
|
|
43696
44621
|
notifyStorageError: (fileName) => storageToastRef.current?.(fileName),
|
|
44622
|
+
drawingsSupported: () => adapter.dwg !== void 0,
|
|
43697
44623
|
...adapter.docxExtractor ? { docxExtractor: adapter.docxExtractor } : {},
|
|
43698
44624
|
...adapter.xlsxExtractor ? { xlsxExtractor: adapter.xlsxExtractor } : {}
|
|
43699
44625
|
}), [adapter, engine]);
|
|
@@ -43957,6 +44883,19 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
|
|
|
43957
44883
|
const bytes = await adapter.storage.readBinary(`${engine.attachmentsRoot}/${attachment.path}`);
|
|
43958
44884
|
return URL.createObjectURL(new Blob([bytes], { type: attachment.contentType }));
|
|
43959
44885
|
}, [adapter, engine]);
|
|
44886
|
+
const messagesRef = useRef(messages);
|
|
44887
|
+
messagesRef.current = messages;
|
|
44888
|
+
/** `` → 附件字节的 blob URL。模型输出不了 base64,正文贴附件图只能靠引用 */
|
|
44889
|
+
const resolveImageRef = useCallback(async (ref) => {
|
|
44890
|
+
const separator = ref.indexOf(":");
|
|
44891
|
+
if (separator === -1) return void 0;
|
|
44892
|
+
const kind = ref.slice(0, separator);
|
|
44893
|
+
const rest = ref.slice(separator + 1);
|
|
44894
|
+
if (kind !== "upload") return void 0;
|
|
44895
|
+
const attachment = findAttachmentByRef([...messagesRef.current].reverse().flatMap((message) => message.attachments ?? []), rest);
|
|
44896
|
+
if (attachment === void 0) return void 0;
|
|
44897
|
+
return resolveAttachmentUrl(attachment);
|
|
44898
|
+
}, [resolveAttachmentUrl]);
|
|
43960
44899
|
const handleRetry = useCallback((messageId) => {
|
|
43961
44900
|
flushPendingDeletes().then(() => engine.retry(messageId)).catch((e) => setError({ message: toErrorMessage(e) }));
|
|
43962
44901
|
}, [engine, flushPendingDeletes]);
|
|
@@ -44123,187 +45062,190 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
|
|
|
44123
45062
|
onArchiveSession: handleArchiveSession,
|
|
44124
45063
|
onUnarchiveSession: handleUnarchiveSession,
|
|
44125
45064
|
onDeleteSession: handleDeleteSession,
|
|
44126
|
-
children: /* @__PURE__ */ jsx(
|
|
44127
|
-
|
|
44128
|
-
|
|
44129
|
-
|
|
44130
|
-
|
|
44131
|
-
|
|
44132
|
-
|
|
44133
|
-
|
|
44134
|
-
|
|
44135
|
-
|
|
44136
|
-
|
|
44137
|
-
"
|
|
44138
|
-
|
|
44139
|
-
|
|
44140
|
-
|
|
44141
|
-
|
|
44142
|
-
|
|
44143
|
-
|
|
44144
|
-
|
|
44145
|
-
|
|
44146
|
-
|
|
44147
|
-
|
|
44148
|
-
|
|
44149
|
-
|
|
44150
|
-
|
|
44151
|
-
|
|
44152
|
-
|
|
44153
|
-
|
|
44154
|
-
|
|
44155
|
-
|
|
44156
|
-
|
|
44157
|
-
|
|
44158
|
-
|
|
44159
|
-
|
|
44160
|
-
|
|
44161
|
-
|
|
44162
|
-
|
|
44163
|
-
|
|
44164
|
-
|
|
44165
|
-
|
|
44166
|
-
|
|
44167
|
-
|
|
44168
|
-
|
|
44169
|
-
|
|
44170
|
-
|
|
44171
|
-
|
|
44172
|
-
|
|
44173
|
-
|
|
44174
|
-
children:
|
|
44175
|
-
|
|
44176
|
-
|
|
44177
|
-
|
|
44178
|
-
|
|
44179
|
-
|
|
44180
|
-
|
|
44181
|
-
|
|
44182
|
-
|
|
44183
|
-
|
|
44184
|
-
|
|
44185
|
-
|
|
44186
|
-
|
|
44187
|
-
onRemove: (messageId) => setMessages((prev) => prev.filter((m) => m.id !== messageId)),
|
|
44188
|
-
children: (handleDeleteMessage) => /* @__PURE__ */ jsx(AssistantUiMessageList, {
|
|
45065
|
+
children: /* @__PURE__ */ jsx(MarkdownImageResolverProvider, {
|
|
45066
|
+
resolve: resolveImageRef,
|
|
45067
|
+
children: /* @__PURE__ */ jsx(ThemeScope, {
|
|
45068
|
+
ref: shellRef,
|
|
45069
|
+
theme,
|
|
45070
|
+
"data-layout": resolvedLayout,
|
|
45071
|
+
"data-session-list": resolvedSessionList,
|
|
45072
|
+
"data-session-narrow": narrow ? "true" : "false",
|
|
45073
|
+
className: "webskill-aui-shell relative flex h-full min-h-0 flex-row overflow-hidden font-sans text-ink",
|
|
45074
|
+
children: /* @__PURE__ */ jsxs(LocalizedUndoToastProvider, { children: [/* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
45075
|
+
/* @__PURE__ */ jsx(AttachmentStorageToastBridge, { sink: storageToastRef }),
|
|
45076
|
+
narrow && sessionsExpanded ? /* @__PURE__ */ jsx("div", {
|
|
45077
|
+
"data-testid": "session-list-overlay",
|
|
45078
|
+
"aria-hidden": true,
|
|
45079
|
+
onClick: () => setSessionsOpen(false),
|
|
45080
|
+
className: "absolute inset-0 z-[var(--webskill-z-raised)] bg-scrim"
|
|
45081
|
+
}) : null,
|
|
45082
|
+
/* @__PURE__ */ jsx(AssistantUiSessionList, {
|
|
45083
|
+
className: "webskill-aui-session-list",
|
|
45084
|
+
onCollapse: () => setSessionsOpen(false),
|
|
45085
|
+
...narrow ? { onNavigate: () => setSessionsOpen(false) } : {},
|
|
45086
|
+
hasEarlier: sessionsCursor !== void 0,
|
|
45087
|
+
loadingEarlier: loadingEarlierSessions,
|
|
45088
|
+
onLoadEarlier: handleLoadEarlierSessions,
|
|
45089
|
+
source: sessionsSource,
|
|
45090
|
+
skipped: skippedSessions
|
|
45091
|
+
})
|
|
45092
|
+
] }), /* @__PURE__ */ jsxs("div", {
|
|
45093
|
+
className: "webskill-chat-column flex min-h-0 min-w-0 flex-1 flex-col",
|
|
45094
|
+
children: [
|
|
45095
|
+
/* @__PURE__ */ jsx(AssistantUiThreadToolbar, {
|
|
45096
|
+
...config?.title ? { title: config.title } : {},
|
|
45097
|
+
onOpenSessions: () => setSessionsOpen(true),
|
|
45098
|
+
onCreateSession: handleCreate,
|
|
45099
|
+
...onOpenSettings ? { onOpenSettings: () => onOpenSettings() } : {},
|
|
45100
|
+
...docked !== void 0 ? { docked } : {},
|
|
45101
|
+
...onDock ? { onDock } : {},
|
|
45102
|
+
...onClose ? { onClose } : {}
|
|
45103
|
+
}),
|
|
45104
|
+
/* @__PURE__ */ jsx(InterruptedBanner, {
|
|
45105
|
+
engine,
|
|
45106
|
+
...currentSessionId ? { sessionId: currentSessionId } : {}
|
|
45107
|
+
}),
|
|
45108
|
+
/* @__PURE__ */ jsxs("div", {
|
|
45109
|
+
className: `flex min-h-0 flex-1 flex-col ${composerCentered ? "justify-center" : ""}`,
|
|
45110
|
+
children: [
|
|
45111
|
+
/* @__PURE__ */ jsx(AssistantUiViewport, {
|
|
45112
|
+
grow: !composerCentered,
|
|
45113
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
45114
|
+
className: "webskill-chat-prose mx-auto flex w-full min-w-0 flex-col gap-4 px-4 py-6",
|
|
45115
|
+
children: [
|
|
45116
|
+
messages.length === 0 && !sending ? /* @__PURE__ */ jsx(AssistantUiEmptyState, {
|
|
45117
|
+
...config?.title ? { title: config.title } : {},
|
|
45118
|
+
...staticQuickPrompts ? { prompts: staticQuickPrompts } : {},
|
|
45119
|
+
...config?.dynamicQuickPrompts ? { dynamicPrompts: config.dynamicQuickPrompts } : {},
|
|
45120
|
+
limit: quickPromptLimit,
|
|
45121
|
+
disabled: sending,
|
|
45122
|
+
onPrompt: handleSend
|
|
45123
|
+
}) : null,
|
|
45124
|
+
/* @__PURE__ */ jsx(MessageDeleteOutlet, {
|
|
45125
|
+
queue: pendingDeletes,
|
|
44189
45126
|
messages,
|
|
44190
|
-
...
|
|
44191
|
-
|
|
44192
|
-
|
|
44193
|
-
|
|
44194
|
-
|
|
44195
|
-
|
|
44196
|
-
|
|
44197
|
-
|
|
44198
|
-
|
|
44199
|
-
|
|
44200
|
-
|
|
44201
|
-
|
|
44202
|
-
|
|
44203
|
-
|
|
44204
|
-
|
|
44205
|
-
|
|
44206
|
-
|
|
44207
|
-
|
|
44208
|
-
|
|
44209
|
-
|
|
44210
|
-
|
|
44211
|
-
|
|
44212
|
-
return /* @__PURE__ */ jsxs(Fragment$1, { children: [renderer !== "native" ? /* @__PURE__ */ jsx("div", {
|
|
44213
|
-
"data-testid": "spec-interaction-slot",
|
|
44214
|
-
...interactionVisible ? { "data-webskill-interaction-anchor": "" } : {},
|
|
44215
|
-
className: interactionVisible ? void 0 : "hidden",
|
|
44216
|
-
children: /* @__PURE__ */ jsx(SpecInteraction, {
|
|
44217
|
-
channel: engine.interactionChannel,
|
|
44218
|
-
renderer
|
|
45127
|
+
...currentSessionId ? { sessionId: currentSessionId } : {},
|
|
45128
|
+
onRemove: (messageId) => setMessages((prev) => prev.filter((m) => m.id !== messageId)),
|
|
45129
|
+
children: (handleDeleteMessage) => /* @__PURE__ */ jsx(AssistantUiMessageList, {
|
|
45130
|
+
messages,
|
|
45131
|
+
...messagesCursor !== void 0 ? { earlier: {
|
|
45132
|
+
loading: loadingEarlierMessages,
|
|
45133
|
+
onLoad: handleLoadEarlierMessages
|
|
45134
|
+
} } : {},
|
|
45135
|
+
renderMessage: (message) => /* @__PURE__ */ jsx(AssistantUiMessage, {
|
|
45136
|
+
message,
|
|
45137
|
+
onRetry: handleRetry,
|
|
45138
|
+
onDelete: handleDeleteMessage,
|
|
45139
|
+
onDownload: handleDownload,
|
|
45140
|
+
modelLabels,
|
|
45141
|
+
resolveAttachmentUrl,
|
|
45142
|
+
...surfaceRegistry ? { surfaceRegistry } : {},
|
|
45143
|
+
...engine.documentSurface ? { documentSurface: engine.documentSurface } : {},
|
|
45144
|
+
renderer,
|
|
45145
|
+
bridge: engine.bridge,
|
|
45146
|
+
...adapter.navigation.openConsoleTrace ? { onViewTrace: adapter.navigation.openConsoleTrace } : {},
|
|
45147
|
+
...adapter.navigation.openConsoleCandidate ? { onOpenCandidate: adapter.navigation.openConsoleCandidate } : {}
|
|
45148
|
+
})
|
|
44219
45149
|
})
|
|
44220
|
-
})
|
|
44221
|
-
|
|
44222
|
-
|
|
44223
|
-
|
|
44224
|
-
|
|
44225
|
-
|
|
44226
|
-
|
|
44227
|
-
|
|
44228
|
-
|
|
44229
|
-
|
|
44230
|
-
|
|
44231
|
-
|
|
44232
|
-
|
|
44233
|
-
|
|
44234
|
-
|
|
44235
|
-
|
|
44236
|
-
|
|
44237
|
-
|
|
44238
|
-
|
|
44239
|
-
|
|
44240
|
-
|
|
44241
|
-
|
|
44242
|
-
|
|
44243
|
-
|
|
44244
|
-
|
|
44245
|
-
|
|
44246
|
-
(() => {
|
|
44247
|
-
const items = visiblePinnedTodos(pinnedTodos);
|
|
44248
|
-
return items ? /* @__PURE__ */ jsx("div", {
|
|
44249
|
-
"data-testid": "pinned-todo-panel",
|
|
44250
|
-
className: "webskill-chat-prose mx-auto w-full shrink-0 px-3",
|
|
44251
|
-
children: /* @__PURE__ */ jsx(TodoPanel, {
|
|
44252
|
-
items,
|
|
44253
|
-
scrollable: true,
|
|
44254
|
-
onDismiss: () => setPinnedTodos(dismissPinnedTodos)
|
|
45150
|
+
}),
|
|
45151
|
+
(() => {
|
|
45152
|
+
const interactionVisible = interactionSessionId === void 0 || interactionSessionId === currentSessionId;
|
|
45153
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [renderer !== "native" ? /* @__PURE__ */ jsx("div", {
|
|
45154
|
+
"data-testid": "spec-interaction-slot",
|
|
45155
|
+
...interactionVisible ? { "data-webskill-interaction-anchor": "" } : {},
|
|
45156
|
+
className: interactionVisible ? void 0 : "hidden",
|
|
45157
|
+
children: /* @__PURE__ */ jsx(SpecInteraction, {
|
|
45158
|
+
channel: engine.interactionChannel,
|
|
45159
|
+
renderer
|
|
45160
|
+
})
|
|
45161
|
+
}) : null, interactionVisible ? /* @__PURE__ */ jsx(InteractionCard, { bridge: engine.bridge }) : null] });
|
|
45162
|
+
})(),
|
|
45163
|
+
/* @__PURE__ */ jsx(ResultBlocksPro, {
|
|
45164
|
+
bridge: engine.bridge,
|
|
45165
|
+
activeRunId: currentRunId,
|
|
45166
|
+
onDownload: handleDownload
|
|
45167
|
+
}),
|
|
45168
|
+
error ? /* @__PURE__ */ jsx(ErrorCard, {
|
|
45169
|
+
message: error.message,
|
|
45170
|
+
...error.code ? { code: error.code } : {},
|
|
45171
|
+
loopLimits,
|
|
45172
|
+
...onOpenSettings ? { onOpenSettings: () => onOpenSettings("settings.runtime") } : {},
|
|
45173
|
+
onDismiss: () => setError(void 0)
|
|
45174
|
+
}) : null
|
|
45175
|
+
]
|
|
44255
45176
|
})
|
|
44256
|
-
})
|
|
44257
|
-
|
|
44258
|
-
|
|
44259
|
-
|
|
44260
|
-
|
|
44261
|
-
|
|
44262
|
-
|
|
44263
|
-
|
|
44264
|
-
|
|
44265
|
-
|
|
44266
|
-
|
|
44267
|
-
|
|
44268
|
-
|
|
44269
|
-
|
|
44270
|
-
|
|
44271
|
-
|
|
44272
|
-
|
|
44273
|
-
|
|
44274
|
-
|
|
44275
|
-
|
|
44276
|
-
|
|
44277
|
-
}
|
|
44278
|
-
|
|
44279
|
-
|
|
44280
|
-
|
|
44281
|
-
|
|
44282
|
-
|
|
44283
|
-
|
|
44284
|
-
|
|
44285
|
-
|
|
44286
|
-
|
|
44287
|
-
|
|
44288
|
-
|
|
44289
|
-
|
|
44290
|
-
|
|
44291
|
-
|
|
44292
|
-
|
|
44293
|
-
|
|
44294
|
-
|
|
44295
|
-
|
|
44296
|
-
|
|
44297
|
-
|
|
44298
|
-
|
|
44299
|
-
|
|
44300
|
-
|
|
44301
|
-
|
|
44302
|
-
|
|
44303
|
-
|
|
44304
|
-
|
|
44305
|
-
|
|
44306
|
-
|
|
45177
|
+
}),
|
|
45178
|
+
/* @__PURE__ */ jsx(InteractionWaitBar, {
|
|
45179
|
+
bridge: engine.bridge,
|
|
45180
|
+
...interactionPending && interactionTarget !== void 0 ? { label: interactionTarget } : {},
|
|
45181
|
+
...interactionElsewhere ? {
|
|
45182
|
+
elsewhere: true,
|
|
45183
|
+
...interactionPanel !== void 0 ? { panel: interactionPanel } : {}
|
|
45184
|
+
} : {},
|
|
45185
|
+
onLocate: handleLocateInteraction
|
|
45186
|
+
}),
|
|
45187
|
+
(() => {
|
|
45188
|
+
const items = visiblePinnedTodos(pinnedTodos);
|
|
45189
|
+
return items ? /* @__PURE__ */ jsx("div", {
|
|
45190
|
+
"data-testid": "pinned-todo-panel",
|
|
45191
|
+
className: "webskill-chat-prose mx-auto w-full shrink-0 px-3",
|
|
45192
|
+
children: /* @__PURE__ */ jsx(TodoPanel, {
|
|
45193
|
+
items,
|
|
45194
|
+
scrollable: true,
|
|
45195
|
+
onDismiss: () => setPinnedTodos(dismissPinnedTodos)
|
|
45196
|
+
})
|
|
45197
|
+
}) : null;
|
|
45198
|
+
})(),
|
|
45199
|
+
(() => {
|
|
45200
|
+
const window = selectedEntry?.contextWindow;
|
|
45201
|
+
const usage = formatContextUsage(contextTokens, window);
|
|
45202
|
+
if (!queuedSend && usage === void 0) return null;
|
|
45203
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
45204
|
+
className: "webskill-chat-prose text-muted-foreground mx-auto flex w-full shrink-0 items-center justify-between gap-2 px-3 text-xs",
|
|
45205
|
+
children: [queuedSend ? /* @__PURE__ */ jsx("span", {
|
|
45206
|
+
"data-testid": "queued-send-hint",
|
|
45207
|
+
children: locale === "zh" ? "前一条还在处理,这条排在后面" : "Waiting for the previous message to finish"
|
|
45208
|
+
}) : /* @__PURE__ */ jsx("span", {}), usage !== void 0 ? /* @__PURE__ */ jsx("span", {
|
|
45209
|
+
"data-testid": "context-usage",
|
|
45210
|
+
children: locale === "zh" ? `上下文 ${usage}` : `Context ${usage}`
|
|
45211
|
+
}) : null]
|
|
45212
|
+
});
|
|
45213
|
+
})(),
|
|
45214
|
+
/* @__PURE__ */ jsx(AssistantUiComposer, {
|
|
45215
|
+
...modelCapabilities ? { capabilities: {
|
|
45216
|
+
tools: modelCapabilities.tools,
|
|
45217
|
+
image: modelCapabilities.image
|
|
45218
|
+
} } : {},
|
|
45219
|
+
running: busy,
|
|
45220
|
+
disabled: interactionPending,
|
|
45221
|
+
...interactionTarget !== void 0 ? { waitTarget: interactionTarget } : {},
|
|
45222
|
+
centered: composerCentered,
|
|
45223
|
+
attachments: attachmentsReady,
|
|
45224
|
+
attachmentInfo: (id) => attachmentAdapter.describe(id),
|
|
45225
|
+
attachmentImages: {
|
|
45226
|
+
enabled: multimodal.imageAttachments,
|
|
45227
|
+
imageCapable: modelTakesImages
|
|
45228
|
+
},
|
|
45229
|
+
...dictationLang !== "" ? { dictationLang } : {},
|
|
45230
|
+
...onDictationPermissionDenied ? { onDictationPermissionDenied } : {},
|
|
45231
|
+
...onCameraPermissionDenied ? { onCameraPermissionDenied } : {},
|
|
45232
|
+
...composerModels && !noUsableModel ? { models: composerModels } : {},
|
|
45233
|
+
...noUsableModel ? { noModel: { onConfigure: () => setNoModelGuideOpen(true) } } : {},
|
|
45234
|
+
...config?.placeholder ? { placeholder: config.placeholder } : {}
|
|
45235
|
+
}),
|
|
45236
|
+
/* @__PURE__ */ jsx(NoModelGuideDialog, {
|
|
45237
|
+
open: noModelGuideOpen,
|
|
45238
|
+
onClose: () => setNoModelGuideOpen(false),
|
|
45239
|
+
...onOpenSettings ? { onConfigure: () => {
|
|
45240
|
+
setNoModelGuideOpen(false);
|
|
45241
|
+
onOpenSettings("connections.models");
|
|
45242
|
+
} } : {}
|
|
45243
|
+
})
|
|
45244
|
+
]
|
|
45245
|
+
})
|
|
45246
|
+
]
|
|
45247
|
+
})] })
|
|
45248
|
+
})
|
|
44307
45249
|
})
|
|
44308
45250
|
}) })
|
|
44309
45251
|
});
|
|
@@ -44364,7 +45306,7 @@ function VercelPayloadPreview({ bridge }) {
|
|
|
44364
45306
|
* Version of the published `@webskill/chatbot` package, injected at build time.
|
|
44365
45307
|
* @stable
|
|
44366
45308
|
*/
|
|
44367
|
-
const CHATBOT_VERSION = "0.
|
|
45309
|
+
const CHATBOT_VERSION = "0.19.0";
|
|
44368
45310
|
|
|
44369
45311
|
//#endregion
|
|
44370
|
-
export { A2uiSurfaceHost, A2uiSurfaceSnapshotHost, CHATBOT_VERSION, ChatEngine, Chatbot, ChatbotVisionDelegate, 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 };
|
|
45312
|
+
export { A2uiSurfaceHost, A2uiSurfaceSnapshotHost, CHATBOT_VERSION, ChatEngine, Chatbot, ChatbotVisionDelegate, CompositeUiBridge, DEFAULT_RENDERER_CAPABILITIES, DEFAULT_RUNTIME_CONFIG_STORAGE_KEY, ErrorCard, InteractionCard, InterruptedBanner, OpenUiSurfaceHost, OpenUiSurfaceSnapshotHost, ResultBlockList, ResultBlocksPro, SkillBadges, SpecInteraction, VercelPayloadPreview, VercelSurfaceHost, VercelSurfaceSnapshotHost, WEB_OFFICE_TEMPLATE_PREFIX, chatbotDictionary, configureA2uiMarkdown, createLocalStorageRuntimeConfigStore, createMemoryRuntimeConfigStore, isLlmEntryUsable, pickUsableLlmEntry, probeA2uiAvailability, probeOpenUiAvailability, sandboxExecutorDeps, useT };
|