@webskill/chatbot 0.13.0 → 0.14.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 CHANGED
@@ -349,6 +349,12 @@
349
349
  max-width: 96rem;
350
350
  }
351
351
  }
352
+ .m-1 {
353
+ margin: var(--spacing);
354
+ }
355
+ .m-2 {
356
+ margin: calc(var(--spacing) * 2);
357
+ }
352
358
  .m-3 {
353
359
  margin: calc(var(--spacing) * 3);
354
360
  }
@@ -1068,6 +1074,9 @@
1068
1074
  .fill-ink {
1069
1075
  fill: var(--foreground);
1070
1076
  }
1077
+ .object-contain {
1078
+ object-fit: contain;
1079
+ }
1071
1080
  .object-cover {
1072
1081
  object-fit: cover;
1073
1082
  }
package/dist/index.d.ts CHANGED
@@ -1507,6 +1507,13 @@ declare const chatbotDictionary: {
1507
1507
  'interaction.downloadedFile.read': string;
1508
1508
  'interaction.downloadedFile.file': string;
1509
1509
  'interaction.downloadedFile.remember': string;
1510
+ 'interaction.uploadFile.read': string;
1511
+ 'interaction.uploadFile.file': string;
1512
+ 'interaction.sandbox.readReference': string;
1513
+ 'interaction.sandbox.readAsset': string;
1514
+ 'interaction.sandbox.fetchData': string;
1515
+ 'interaction.sandbox.writeArtifact': string;
1516
+ 'interaction.sandbox.confirm': string;
1510
1517
  'interaction.traceEvidence.title': string;
1511
1518
  'interaction.traceEvidence.columnDraft': string;
1512
1519
  'interaction.traceEvidence.columnTrace': string;
@@ -1580,6 +1587,20 @@ declare const chatbotDictionary: {
1580
1587
  'composer.dictate.recording': string;
1581
1588
  'composer.dictate.api-missing': string;
1582
1589
  'composer.dictate.insecure-context': string;
1590
+ 'composer.dictate.denied': string;
1591
+ 'composer.dictate.denied.handoff': string;
1592
+ 'composer.dictate.failed': string;
1593
+ 'composer.camera': string;
1594
+ 'composer.camera.title': string;
1595
+ 'composer.camera.description': string;
1596
+ 'composer.camera.preview': string;
1597
+ 'composer.camera.shutter': string;
1598
+ 'composer.camera.noStorage': string;
1599
+ 'composer.camera.api-missing': string;
1600
+ 'composer.camera.insecure-context': string;
1601
+ 'composer.camera.denied': string;
1602
+ 'composer.camera.denied.handoff': string;
1603
+ 'composer.camera.failed': string;
1583
1604
  'settings.title': string;
1584
1605
  'settings.group.general': string;
1585
1606
  'settings.group.models': string;
@@ -1873,6 +1894,13 @@ declare const chatbotDictionary: {
1873
1894
  'interaction.downloadedFile.read': string;
1874
1895
  'interaction.downloadedFile.file': string;
1875
1896
  'interaction.downloadedFile.remember': string;
1897
+ 'interaction.uploadFile.read': string;
1898
+ 'interaction.uploadFile.file': string;
1899
+ 'interaction.sandbox.readReference': string;
1900
+ 'interaction.sandbox.readAsset': string;
1901
+ 'interaction.sandbox.fetchData': string;
1902
+ 'interaction.sandbox.writeArtifact': string;
1903
+ 'interaction.sandbox.confirm': string;
1876
1904
  'interaction.traceEvidence.title': string;
1877
1905
  'interaction.traceEvidence.columnDraft': string;
1878
1906
  'interaction.traceEvidence.columnTrace': string;
@@ -1946,6 +1974,20 @@ declare const chatbotDictionary: {
1946
1974
  'composer.dictate.recording': string;
1947
1975
  'composer.dictate.api-missing': string;
1948
1976
  'composer.dictate.insecure-context': string;
1977
+ 'composer.dictate.denied': string;
1978
+ 'composer.dictate.denied.handoff': string;
1979
+ 'composer.dictate.failed': string;
1980
+ 'composer.camera': string;
1981
+ 'composer.camera.title': string;
1982
+ 'composer.camera.description': string;
1983
+ 'composer.camera.preview': string;
1984
+ 'composer.camera.shutter': string;
1985
+ 'composer.camera.noStorage': string;
1986
+ 'composer.camera.api-missing': string;
1987
+ 'composer.camera.insecure-context': string;
1988
+ 'composer.camera.denied': string;
1989
+ 'composer.camera.denied.handoff': string;
1990
+ 'composer.camera.failed': string;
1949
1991
  'settings.title': string;
1950
1992
  'settings.group.general': string;
1951
1993
  'settings.group.models': string;
@@ -2242,6 +2284,13 @@ declare const useT: () => TranslateFn<{
2242
2284
  'interaction.downloadedFile.read': string;
2243
2285
  'interaction.downloadedFile.file': string;
2244
2286
  'interaction.downloadedFile.remember': string;
2287
+ 'interaction.uploadFile.read': string;
2288
+ 'interaction.uploadFile.file': string;
2289
+ 'interaction.sandbox.readReference': string;
2290
+ 'interaction.sandbox.readAsset': string;
2291
+ 'interaction.sandbox.fetchData': string;
2292
+ 'interaction.sandbox.writeArtifact': string;
2293
+ 'interaction.sandbox.confirm': string;
2245
2294
  'interaction.traceEvidence.title': string;
2246
2295
  'interaction.traceEvidence.columnDraft': string;
2247
2296
  'interaction.traceEvidence.columnTrace': string;
@@ -2315,6 +2364,20 @@ declare const useT: () => TranslateFn<{
2315
2364
  'composer.dictate.recording': string;
2316
2365
  'composer.dictate.api-missing': string;
2317
2366
  'composer.dictate.insecure-context': string;
2367
+ 'composer.dictate.denied': string;
2368
+ 'composer.dictate.denied.handoff': string;
2369
+ 'composer.dictate.failed': string;
2370
+ 'composer.camera': string;
2371
+ 'composer.camera.title': string;
2372
+ 'composer.camera.description': string;
2373
+ 'composer.camera.preview': string;
2374
+ 'composer.camera.shutter': string;
2375
+ 'composer.camera.noStorage': string;
2376
+ 'composer.camera.api-missing': string;
2377
+ 'composer.camera.insecure-context': string;
2378
+ 'composer.camera.denied': string;
2379
+ 'composer.camera.denied.handoff': string;
2380
+ 'composer.camera.failed': string;
2318
2381
  'settings.title': string;
2319
2382
  'settings.group.general': string;
2320
2383
  'settings.group.models': string;
@@ -2608,6 +2671,13 @@ declare const useT: () => TranslateFn<{
2608
2671
  'interaction.downloadedFile.read': string;
2609
2672
  'interaction.downloadedFile.file': string;
2610
2673
  'interaction.downloadedFile.remember': string;
2674
+ 'interaction.uploadFile.read': string;
2675
+ 'interaction.uploadFile.file': string;
2676
+ 'interaction.sandbox.readReference': string;
2677
+ 'interaction.sandbox.readAsset': string;
2678
+ 'interaction.sandbox.fetchData': string;
2679
+ 'interaction.sandbox.writeArtifact': string;
2680
+ 'interaction.sandbox.confirm': string;
2611
2681
  'interaction.traceEvidence.title': string;
2612
2682
  'interaction.traceEvidence.columnDraft': string;
2613
2683
  'interaction.traceEvidence.columnTrace': string;
@@ -2681,6 +2751,20 @@ declare const useT: () => TranslateFn<{
2681
2751
  'composer.dictate.recording': string;
2682
2752
  'composer.dictate.api-missing': string;
2683
2753
  'composer.dictate.insecure-context': string;
2754
+ 'composer.dictate.denied': string;
2755
+ 'composer.dictate.denied.handoff': string;
2756
+ 'composer.dictate.failed': string;
2757
+ 'composer.camera': string;
2758
+ 'composer.camera.title': string;
2759
+ 'composer.camera.description': string;
2760
+ 'composer.camera.preview': string;
2761
+ 'composer.camera.shutter': string;
2762
+ 'composer.camera.noStorage': string;
2763
+ 'composer.camera.api-missing': string;
2764
+ 'composer.camera.insecure-context': string;
2765
+ 'composer.camera.denied': string;
2766
+ 'composer.camera.denied.handoff': string;
2767
+ 'composer.camera.failed': string;
2684
2768
  'settings.title': string;
2685
2769
  'settings.group.general': string;
2686
2770
  'settings.group.models': string;
@@ -2874,6 +2958,18 @@ interface ChatbotProps {
2874
2958
  renderer?: RendererKind;
2875
2959
  /** 语音识别语言(BCP-47);缺省跟随界面语言(FR-7.7) */
2876
2960
  dictationLang?: string;
2961
+ /**
2962
+ * 麦克风授权被拒时的宿主出口(0.17.0 FR-10.1)。
2963
+ * 侧栏 / popup 这类页面没地方错权限气泡,只有宿主知道该把用户带到哪儿去。
2964
+ * 不接就只给一句「去浏览器权限设置里允许」的提示,行为与以往一致。
2965
+ */
2966
+ onDictationPermissionDenied?(): void;
2967
+ /**
2968
+ * 摄像头授权被拒时的宿主出口(0.17.0 FR-14.7)。
2969
+ * 机制与 `onDictationPermissionDenied` 完全一致:侧栏 / popup 弹不出浏览器的授权气泡。
2970
+ * 不接就只给一句「去浏览器权限设置里允许」的提示。
2971
+ */
2972
+ onCameraPermissionDenied?(): void;
2877
2973
  /** Native custom surface 的显式 renderer/schema allowlist。 */
2878
2974
  surfaceRegistry?: SurfaceRegistry;
2879
2975
  /** 布局档(默认 'auto':按容器尺寸在 fullscreen / embedded / mobile 之间判定) */
@@ -2912,7 +3008,7 @@ interface ChatbotProps {
2912
3008
  * presentation shell and accessible thread primitives.
2913
3009
  * @stable
2914
3010
  */
2915
- declare function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, renderer: rendererProp, dictationLang: dictationLangProp, surfaceRegistry, layout, sessionList, onOpenSettings, docked, onDock, onClose, onEngineReady }: ChatbotProps): import("react").JSX.Element;
3011
+ declare function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, renderer: rendererProp, dictationLang: dictationLangProp, onDictationPermissionDenied, onCameraPermissionDenied, surfaceRegistry, layout, sessionList, onOpenSettings, docked, onDock, onClose, onEngineReady }: ChatbotProps): import("react").JSX.Element;
2916
3012
  //#endregion
2917
3013
  //#region src/react/A2uiSurfaceHost.d.ts
2918
3014
  /** @experimental */
@@ -3096,6 +3192,6 @@ declare function ErrorCard({ code, message, loopLimits, onOpenSettings, onDismis
3096
3192
  * Version of the published `@webskill/chatbot` package, injected at build time.
3097
3193
  * @stable
3098
3194
  */
3099
- declare const CHATBOT_VERSION = "0.13.0";
3195
+ declare const CHATBOT_VERSION = "0.14.0";
3100
3196
  //#endregion
3101
3197
  export { A2uiSurfaceHost, type A2uiSurfaceHostProps, A2uiSurfaceSnapshotHost, type A2uiSurfaceSnapshotHostProps, type AppearanceChange, CHATBOT_VERSION, type ChatAttachmentInput, type ChatAttachmentMeta, type ChatContentPart, ChatEngine, type ChatEngineOptions, type ChatEvent, type ChatInteractionRecord, type ChatLayout, type ChatMessage, type ChatRunPhase, type ChatSessionListResult, type ChatSessionMeta, type ChatTheme, type ChatThinking, type ChatTokenUsage, type ChatToolCall, Chatbot, type ChatbotConfig, type ChatbotGovernancePorts, type ChatbotHostAdapter, type ChatbotProps, CompositeUiBridge, type CompositeUiBridgeDeps, DEFAULT_RENDERER_CAPABILITIES, DEFAULT_RUNTIME_CONFIG_STORAGE_KEY, type DownloadableFile, ErrorCard, type ErrorCardProps, InteractionCard, type InteractionCardProps, InterruptedBanner, type InterruptedBannerProps, type Locale, type LocalizedText, OpenUiSurfaceHost, type OpenUiSurfaceHostProps, OpenUiSurfaceSnapshotHost, type OpenUiSurfaceSnapshotHostProps, type QuickPrompt, type QuickPromptIconName, type RendererCapability, type RendererKind, type ResolvedChatLayout, ResultBlockList, type ResultBlockListProps, ResultBlocksPro, type ResultBlocksProProps, type RunSnapshot, type RuntimeAppearanceConfig, type RuntimeConfig, type RuntimeConfigStore, type RuntimeQuickPrompt, type RuntimeRendererId, type SandboxExecutorDeps, type SettingsSectionId, SkillBadges, SpecInteraction, type SpecInteractionProps, VercelPayloadPreview, type VercelPayloadPreviewProps, VercelSurfaceHost, type VercelSurfaceHostProps, VercelSurfaceSnapshotHost, type VercelSurfaceSnapshotHostProps, chatbotDictionary, configureA2uiMarkdown, createLocalStorageRuntimeConfigStore, createMemoryRuntimeConfigStore, isLlmEntryUsable, pickUsableLlmEntry, probeA2uiAvailability, probeOpenUiAvailability, sandboxExecutorDeps, useT };
package/dist/index.js CHANGED
@@ -7,7 +7,7 @@ import { t as json } from "./json-BOcU6nei.js";
7
7
  import { t as typescript } from "./typescript-DAau_Qyl.js";
8
8
  import { t as jsx$1 } from "./jsx-D2Jv0wvt.js";
9
9
  import { BEHAVIOR_RECORDS_KEY, DEFAULT_LOOP_LIMITS, DEFAULT_USER_PROFILE_LIMITS, EventBus, FsArtifactStore, FsMemoryStore, FsRunSnapshotStore, FsRunTraceStore, FsSessionStore, FsToolStepStore, FullDisclosureRouter, SUPPORTED_DOCUMENT_MIME, SerializingMemoryStore, SkillDiscovery, USER_PROFILE_KEY, WebSkillError, WebSkillRuntime, applyUserProfileImport, assertRemoteUrlAllowed, assertSafePathSegment, atomicWriteText, diffUserProfile, exportUserProfile, formatAttachmentText, isUnsupportedRunSnapshot, messageOf, readBehaviorRecords, readUserProfile, refineUserProfile, resolveInsideRoot, sanitizeUntrustedLine, summarizeRunUsage, summarizeToolCalls, textParts } from "@webskill/sdk";
10
- import { BrowserWorkerScriptExecutor, checkDictationAvailability, compressImageToBudget, createEncryptedMemoryStore, createLlmClient, deleteMemoryEncryptionKey, openMemoryEncryptionKey, probeChromeBuiltinAvailability, startDictation } from "@webskill/sdk/browser";
10
+ import { BrowserWorkerScriptExecutor, capturePhoto, checkCameraAvailability, checkDictationAvailability, compressImageToBudget, createEncryptedMemoryStore, createLlmClient, deleteMemoryEncryptionKey, openCamera, openMemoryEncryptionKey, probeChromeBuiltinAvailability, startDictation } from "@webskill/sdk/browser";
11
11
  import { JsonRenderSpecSurface, NativeSpecSurface, OpenUiSpecSurface, ReactBridgeState, SpecInteractionChannel, SurfaceFormTextsProvider, UiSurfaceSnapshotList, probeJsonRenderAvailability } from "@webskill/sdk/ui-react";
12
12
  import { A2UI_SPEC_FORM_PATH, collectFormScopes, createUiCatalogToolSource, fromA2uiSpecAction, fromA2uiSurfaceAction, interactionToFormModel, interactionToUiSpec, loadOpenUiPeers, loadWebSkillLitCatalog, renderMiniChart, shapeInteractionValue, toA2uiSpecMessages, toA2uiSurfaceAction, toVercelToolInvocation, uiCatalog } from "@webskill/sdk/ui";
13
13
  import { DownloadedFilePolicy, PAGE_ACTION_KINDS, TodoStore, createDelegationToolSource, createDownloadedFileToolSource, createPageActionToolSource, createPagePerceptionToolSource, createSkillGenerationToolSource, createTabWorksetToolSource, createTodoToolSource, withDelegationOrigin } from "@webskill/sdk/agent";
@@ -254,7 +254,7 @@ const createLucideIcon = (iconName, iconNode) => {
254
254
  * This source code is licensed under the ISC license.
255
255
  * See the LICENSE file in the root directory of this source tree.
256
256
  */
257
- const __iconNode$49 = [
257
+ const __iconNode$50 = [
258
258
  ["rect", {
259
259
  x: "2",
260
260
  y: "4",
@@ -276,7 +276,7 @@ const __iconNode$49 = [
276
276
  key: "1svtjw"
277
277
  }]
278
278
  ];
279
- const AppWindow = createLucideIcon("app-window", __iconNode$49);
279
+ const AppWindow = createLucideIcon("app-window", __iconNode$50);
280
280
 
281
281
  //#endregion
282
282
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/archive-restore.mjs
@@ -286,7 +286,7 @@ const AppWindow = createLucideIcon("app-window", __iconNode$49);
286
286
  * This source code is licensed under the ISC license.
287
287
  * See the LICENSE file in the root directory of this source tree.
288
288
  */
289
- const __iconNode$48 = [
289
+ const __iconNode$49 = [
290
290
  ["rect", {
291
291
  width: "20",
292
292
  height: "5",
@@ -312,7 +312,7 @@ const __iconNode$48 = [
312
312
  key: "192myk"
313
313
  }]
314
314
  ];
315
- const ArchiveRestore = createLucideIcon("archive-restore", __iconNode$48);
315
+ const ArchiveRestore = createLucideIcon("archive-restore", __iconNode$49);
316
316
 
317
317
  //#endregion
318
318
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/archive.mjs
@@ -322,7 +322,7 @@ const ArchiveRestore = createLucideIcon("archive-restore", __iconNode$48);
322
322
  * This source code is licensed under the ISC license.
323
323
  * See the LICENSE file in the root directory of this source tree.
324
324
  */
325
- const __iconNode$47 = [
325
+ const __iconNode$48 = [
326
326
  ["rect", {
327
327
  width: "20",
328
328
  height: "5",
@@ -340,7 +340,7 @@ const __iconNode$47 = [
340
340
  key: "a56b0p"
341
341
  }]
342
342
  ];
343
- const Archive = createLucideIcon("archive", __iconNode$47);
343
+ const Archive = createLucideIcon("archive", __iconNode$48);
344
344
 
345
345
  //#endregion
346
346
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/arrow-up.mjs
@@ -350,14 +350,14 @@ const Archive = createLucideIcon("archive", __iconNode$47);
350
350
  * This source code is licensed under the ISC license.
351
351
  * See the LICENSE file in the root directory of this source tree.
352
352
  */
353
- const __iconNode$46 = [["path", {
353
+ const __iconNode$47 = [["path", {
354
354
  d: "m5 12 7-7 7 7",
355
355
  key: "hav0vg"
356
356
  }], ["path", {
357
357
  d: "M12 19V5",
358
358
  key: "x0mq9r"
359
359
  }]];
360
- const ArrowUp = createLucideIcon("arrow-up", __iconNode$46);
360
+ const ArrowUp = createLucideIcon("arrow-up", __iconNode$47);
361
361
 
362
362
  //#endregion
363
363
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/bug.mjs
@@ -367,7 +367,7 @@ const ArrowUp = createLucideIcon("arrow-up", __iconNode$46);
367
367
  * This source code is licensed under the ISC license.
368
368
  * See the LICENSE file in the root directory of this source tree.
369
369
  */
370
- const __iconNode$45 = [
370
+ const __iconNode$46 = [
371
371
  ["path", {
372
372
  d: "M12 20v-9",
373
373
  key: "1qisl0"
@@ -413,7 +413,26 @@ const __iconNode$45 = [
413
413
  key: "1vgav8"
414
414
  }]
415
415
  ];
416
- const Bug = createLucideIcon("bug", __iconNode$45);
416
+ const Bug = createLucideIcon("bug", __iconNode$46);
417
+
418
+ //#endregion
419
+ //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/camera.mjs
420
+ /**
421
+ * @license lucide-react v1.27.0 - ISC
422
+ *
423
+ * This source code is licensed under the ISC license.
424
+ * See the LICENSE file in the root directory of this source tree.
425
+ */
426
+ const __iconNode$45 = [["path", {
427
+ d: "M13.997 4a2 2 0 0 1 1.76 1.05l.486.9A2 2 0 0 0 18.003 7H20a2 2 0 0 1 2 2v9a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V9a2 2 0 0 1 2-2h1.997a2 2 0 0 0 1.759-1.048l.489-.904A2 2 0 0 1 10.004 4z",
428
+ key: "18u6gg"
429
+ }], ["circle", {
430
+ cx: "12",
431
+ cy: "13",
432
+ r: "3",
433
+ key: "1vg3eu"
434
+ }]];
435
+ const Camera = createLucideIcon("camera", __iconNode$45);
417
436
 
418
437
  //#endregion
419
438
  //#region ../../node_modules/.pnpm/lucide-react@1.27.0_react@19.2.8/node_modules/lucide-react/dist/esm/icons/chart-column.mjs
@@ -30291,6 +30310,12 @@ const markdownComponents = {
30291
30310
  /**
30292
30311
  * 流式 Markdown:消费当前 text part 的增量文本,边流边成形(标题/列表/表格/代码块),
30293
30312
  * 终态与流式期是同一个组件、同一份数据,因此 run 结束时不重排。
30313
+ *
30314
+ * `smooth={false}` 是硬性要求,不是口味问题:assistant-ui 以 partIndex 作 React key
30315
+ * (`MessagePartsGrouped`),而运行细节的 part 排在正文之前,每来一次工具调用正文就整体后移一位、
30316
+ * 被卸载重挂。平滑揭示在重挂时从空串重新开始,于是同一段正文会被完整地重打一遍——
30317
+ * 表现为「正文下半段删掉又补回来、高度忽高忽低」。模型本身已按 token 粒度流式返回,
30318
+ * 再叠一层揭示动画只会把结构性重挂放大成可见抖动。守卫见 `e2e-extension/liveBodyMonotonic.spec.ts`。
30294
30319
  */
30295
30320
  const MarkdownText = memo(function MarkdownText({ className }) {
30296
30321
  const [dark, themeRef] = useDarkAncestor();
@@ -30302,6 +30327,7 @@ const MarkdownText = memo(function MarkdownText({ className }) {
30302
30327
  remarkPlugins: [remarkGfm],
30303
30328
  className: cn("aui-md", className),
30304
30329
  components,
30330
+ smooth: false,
30305
30331
  defer: true
30306
30332
  })
30307
30333
  });
@@ -31698,6 +31724,13 @@ const chatbotDictionary = defineDictionary({
31698
31724
  "interaction.downloadedFile.read": "Allow the assistant to read this file from your downloads?",
31699
31725
  "interaction.downloadedFile.file": "{name} · {size}",
31700
31726
  "interaction.downloadedFile.remember": "Don’t ask again for this kind of access",
31727
+ "interaction.uploadFile.read": "Allow the skill “{skill}” to read the file you uploaded?",
31728
+ "interaction.uploadFile.file": "{name} · {type} · {size}",
31729
+ "interaction.sandbox.readReference": "Allow the skill “{skill}” to read the reference file “{path}”?",
31730
+ "interaction.sandbox.readAsset": "Allow the skill “{skill}” to read the bundled asset “{path}”?",
31731
+ "interaction.sandbox.fetchData": "Allow the skill “{skill}” to fetch the data source “{source}”?",
31732
+ "interaction.sandbox.writeArtifact": "Allow the skill “{skill}” to write the artifact “{path}”?",
31733
+ "interaction.sandbox.confirm": "The skill “{skill}” asks for confirmation:",
31701
31734
  "interaction.traceEvidence.title": "Steps this skill claims",
31702
31735
  "interaction.traceEvidence.columnDraft": "In the skill",
31703
31736
  "interaction.traceEvidence.columnTrace": "Actually run in this session",
@@ -31771,6 +31804,20 @@ const chatbotDictionary = defineDictionary({
31771
31804
  "composer.dictate.recording": "Recording",
31772
31805
  "composer.dictate.api-missing": "This browser does not support speech recognition.",
31773
31806
  "composer.dictate.insecure-context": "Voice input needs a secure connection (https or localhost).",
31807
+ "composer.dictate.denied": "The browser denied microphone access. Allow it in the site permission settings, then try again.",
31808
+ "composer.dictate.denied.handoff": "Microphone access needs to be granted once in a regular tab. Finish it there, then come back and try again.",
31809
+ "composer.dictate.failed": "Voice input stopped unexpectedly. Try again, or type your message.",
31810
+ "composer.camera": "Take a photo",
31811
+ "composer.camera.title": "Take a photo",
31812
+ "composer.camera.description": "The photo is added to the composer as an attachment. It is not sent until you send the message.",
31813
+ "composer.camera.preview": "Camera preview",
31814
+ "composer.camera.shutter": "Capture",
31815
+ "composer.camera.noStorage": "attachments are unavailable in this host",
31816
+ "composer.camera.api-missing": "This browser does not support camera capture.",
31817
+ "composer.camera.insecure-context": "The camera needs a secure connection (https or localhost).",
31818
+ "composer.camera.denied": "The browser denied camera access. Allow it in the site permission settings, then try again.",
31819
+ "composer.camera.denied.handoff": "Camera access needs to be granted once in a regular tab. Finish it there, then come back and try again.",
31820
+ "composer.camera.failed": "The photo could not be taken. Try again, or attach an image file instead.",
31774
31821
  "settings.title": "Settings",
31775
31822
  "settings.group.general": "General",
31776
31823
  "settings.group.models": "Models",
@@ -32064,6 +32111,13 @@ const chatbotDictionary = defineDictionary({
32064
32111
  "interaction.downloadedFile.read": "允许助手读取你下载目录里的这份文件吗?",
32065
32112
  "interaction.downloadedFile.file": "{name} · {size}",
32066
32113
  "interaction.downloadedFile.remember": "以后不再询问此类访问",
32114
+ "interaction.uploadFile.read": "允许技能「{skill}」读取你上传的这份文件吗?",
32115
+ "interaction.uploadFile.file": "{name} · {type} · {size}",
32116
+ "interaction.sandbox.readReference": "允许技能「{skill}」读取参考资料「{path}」吗?",
32117
+ "interaction.sandbox.readAsset": "允许技能「{skill}」读取随附文件「{path}」吗?",
32118
+ "interaction.sandbox.fetchData": "允许技能「{skill}」访问数据源「{source}」吗?",
32119
+ "interaction.sandbox.writeArtifact": "允许技能「{skill}」写出产物「{path}」吗?",
32120
+ "interaction.sandbox.confirm": "技能「{skill}」请求确认:",
32067
32121
  "interaction.traceEvidence.title": "该技能声称的步骤",
32068
32122
  "interaction.traceEvidence.columnDraft": "技能里写的",
32069
32123
  "interaction.traceEvidence.columnTrace": "本会话实际执行的",
@@ -32137,6 +32191,20 @@ const chatbotDictionary = defineDictionary({
32137
32191
  "composer.dictate.recording": "录音中",
32138
32192
  "composer.dictate.api-missing": "当前浏览器不支持语音识别。",
32139
32193
  "composer.dictate.insecure-context": "语音输入需要安全连接(https 或 localhost)。",
32194
+ "composer.dictate.denied": "浏览器拒绝了麦克风。请在站点权限设置里允许麦克风后重试。",
32195
+ "composer.dictate.denied.handoff": "麦克风需要在普通标签页里授权一次。请在那里完成授权,再回到这里重试。",
32196
+ "composer.dictate.failed": "语音输入意外中断。可以再试一次,或直接打字。",
32197
+ "composer.camera": "拍照",
32198
+ "composer.camera.title": "拍照",
32199
+ "composer.camera.description": "拍好的照片会作为附件放进输入框,你点发送才会发出去。",
32200
+ "composer.camera.preview": "摄像头取景",
32201
+ "composer.camera.shutter": "拍照",
32202
+ "composer.camera.noStorage": "当前宿主不支持附件",
32203
+ "composer.camera.api-missing": "当前浏览器不支持调用摄像头。",
32204
+ "composer.camera.insecure-context": "使用摄像头需要安全连接(https 或 localhost)。",
32205
+ "composer.camera.denied": "浏览器拒绝了摄像头。请在站点权限设置里允许摄像头后重试。",
32206
+ "composer.camera.denied.handoff": "摄像头需要在普通标签页里授权一次。请在那里完成授权,再回到这里重试。",
32207
+ "composer.camera.failed": "没能拍下这张照片。可以再试一次,或改为直接添加图片文件。",
32140
32208
  "settings.title": "设置",
32141
32209
  "settings.group.general": "通用",
32142
32210
  "settings.group.models": "模型",
@@ -34273,6 +34341,7 @@ function assembleAssistantParts(source) {
34273
34341
  ...source.skills.map(skillPart),
34274
34342
  ...source.perceptions.map(perceptionPart),
34275
34343
  ...source.runDetails.map((item) => item.part),
34344
+ ...source.precedingBody ?? [],
34276
34345
  ...bodyParts(source.contentParts, source.text),
34277
34346
  ...source.interactions.map(interactionPart),
34278
34347
  ...source.candidates.map(skillCandidatePart),
@@ -34295,15 +34364,24 @@ function terminationParts(phases) {
34295
34364
  }
34296
34365
  }];
34297
34366
  }
34367
+ /** 引用在「已落盘」集合里的键;blocks 一个 run 至多一个,无需再分 */
34368
+ const refKey = (part) => part.type === "surface" ? `surface:${part.surfaceId}` : "blocks";
34298
34369
  /**
34299
34370
  * 同 run 的运行细节合并计划(UI-UX5 #7):FR-17.8 的交互切片会让一个 run 产生多条
34300
34371
  * assistant 消息,每条各自组装「运行细节」就是用户看到的两个框(run id 相同)。
34301
34372
  * 这里把相位/技能/工具并入**第一条片段**(表单上方),后续片段与续跑 live 不再出框。
34302
34373
  * 相位本来就是无重叠的增量切片(`#flushed` 水位),直接拼接即全量;
34303
34374
  * live 的相位是全量累积,增量 = 全量减去已落盘数。
34375
+ *
34376
+ * 分册 16:run 未终止时还要把已切片载体本身并进 live 气泡(`hidden`)。
34377
+ * 切片会把写到一半的正文从 live 气泡搬到一个新 id 的消息上,
34378
+ * live 气泡随即被清空再由下一轮填满——每次工具调用都卸载/重建一次 DOM,
34379
+ * 就是用户看到的「正文下半段删掉又补新的进去、高度一会变小一会变大」。
34380
+ * 合并后一个进行中的 run 始终只有一条气泡,内容只增不减;终态消息到达后才恢复切片形态(历史不变)。
34304
34381
  */
34305
34382
  function planRunTrace(messages, live) {
34306
34383
  const byMessage = /* @__PURE__ */ new Map();
34384
+ const hidden = /* @__PURE__ */ new Set();
34307
34385
  let livePlan;
34308
34386
  const byRun = /* @__PURE__ */ new Map();
34309
34387
  for (const message of messages) {
@@ -34344,16 +34422,27 @@ function planRunTrace(messages, live) {
34344
34422
  terminationPhases: message.phases ?? []
34345
34423
  });
34346
34424
  });
34347
- if (liveHere) livePlan = {
34348
- phases: [],
34349
- skills: [],
34350
- runDetails: [],
34351
- settled: false,
34352
- terminationPhases: liveHere.phases
34353
- };
34425
+ if (liveHere) {
34426
+ const refs = /* @__PURE__ */ new Set();
34427
+ for (const message of carriers) for (const part of message.contentParts ?? []) if (part.type !== "text") refs.add(refKey(part));
34428
+ for (const message of carriers) hidden.add(message.id);
34429
+ livePlan = {
34430
+ phases: mergedPhases,
34431
+ skills: mergedSkills,
34432
+ runDetails: mergedRunDetails,
34433
+ settled: false,
34434
+ terminationPhases: liveHere.phases,
34435
+ merged: {
34436
+ carriers,
34437
+ text: carriers.reduce((total, m) => total + (m.text?.length ?? 0), 0),
34438
+ refs
34439
+ }
34440
+ };
34441
+ }
34354
34442
  }
34355
34443
  return {
34356
34444
  byMessage,
34445
+ hidden,
34357
34446
  ...livePlan ? { live: livePlan } : {}
34358
34447
  };
34359
34448
  }
@@ -34406,6 +34495,9 @@ function liveTiming(live) {
34406
34495
  };
34407
34496
  }
34408
34497
  function liveMessage(live, trace) {
34498
+ const from = trace?.merged?.text ?? 0;
34499
+ const flushedRefs = trace?.merged?.refs;
34500
+ const precedingBody = (trace?.merged?.carriers ?? []).flatMap((message) => [...bodyParts(message.contentParts, message.text), ...(message.interactions ?? []).map(interactionPart)]);
34409
34501
  const content = assembleAssistantParts({
34410
34502
  phases: trace?.phases ?? live.phases,
34411
34503
  skills: trace?.skills ?? live.skills,
@@ -34414,8 +34506,9 @@ function liveMessage(live, trace) {
34414
34506
  part: liveToolPart(call),
34415
34507
  at: call.startedAt
34416
34508
  })), live.thinkings),
34417
- text: live.text,
34418
- contentParts: materialize(live.ledger, live.text, 0, { includePending: true }),
34509
+ precedingBody,
34510
+ text: live.text.slice(from),
34511
+ contentParts: materialize(live.ledger, live.text, from, { includePending: true }).filter((part) => part.type === "text" || flushedRefs === void 0 || !flushedRefs.has(refKey(part))),
34419
34512
  interactions: [],
34420
34513
  candidates: [],
34421
34514
  settled: trace?.settled ?? false,
@@ -34450,13 +34543,14 @@ function liveMessage(live, trace) {
34450
34543
  */
34451
34544
  function toExternalMessages(messages, live, cache) {
34452
34545
  const trace = planRunTrace(messages, live);
34453
- const result = messages.map((message) => {
34454
- if (message.role === "user") return userMessage(message);
34546
+ const result = messages.flatMap((message) => {
34547
+ if (message.role === "user") return [userMessage(message)];
34548
+ if (trace.hidden.has(message.id)) return [];
34455
34549
  const override = trace.byMessage.get(message.id);
34456
34550
  const cached = cache?.get(message.id);
34457
34551
  const external = cached !== void 0 && override === void 0 ? cached : assistantMessage(message, override);
34458
34552
  if (override === void 0) cache?.set(message.id, external);
34459
- return external;
34553
+ return [external];
34460
34554
  });
34461
34555
  const settled = messages.some((message) => message.role === "assistant" && message.runId === live?.runId && message.interactions === void 0);
34462
34556
  if (live && !settled) result.push(liveMessage(live, trace.live));
@@ -34978,6 +35072,124 @@ function InteractionWaitBar({ bridge, label, elsewhere = false, panel, onLocate
34978
35072
  });
34979
35073
  }
34980
35074
 
35075
+ //#endregion
35076
+ //#region src/react/CameraCaptureDialog.tsx
35077
+ /**
35078
+ * 取景与快门(分册 14 FR-14.5)。
35079
+ *
35080
+ * 点遮罩不关闭:摄像头开着时误点一下就得重新授权取流,代价与「丢掉表单草稿」同级。
35081
+ * 预览框按 4:3 留位,避免流还没出画时对话框高度跳一下。
35082
+ */
35083
+ function CameraCaptureDialog({ open, busy, videoRef, onCapture, onCancel }) {
35084
+ const t = useT();
35085
+ return /* @__PURE__ */ jsx(Dialog, {
35086
+ open,
35087
+ onClose: onCancel,
35088
+ dismissOnOutsideClick: false,
35089
+ title: t("composer.camera.title"),
35090
+ description: t("composer.camera.description"),
35091
+ size: "lg",
35092
+ footer: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Button, {
35093
+ variant: "secondary",
35094
+ onClick: onCancel,
35095
+ children: t("common.cancel")
35096
+ }), /* @__PURE__ */ jsx(Button, {
35097
+ "data-testid": "composer-camera-shutter",
35098
+ icon: /* @__PURE__ */ jsx(Camera, { className: "size-4" }),
35099
+ disabled: busy,
35100
+ onClick: onCapture,
35101
+ children: t("composer.camera.shutter")
35102
+ })] }),
35103
+ children: /* @__PURE__ */ jsx("video", {
35104
+ ref: videoRef,
35105
+ "data-testid": "composer-camera-preview",
35106
+ "aria-label": t("composer.camera.preview"),
35107
+ autoPlay: true,
35108
+ muted: true,
35109
+ playsInline: true,
35110
+ className: "aspect-[4/3] w-full rounded-card bg-subtle object-contain"
35111
+ })
35112
+ });
35113
+ }
35114
+
35115
+ //#endregion
35116
+ //#region src/react/useComposerCamera.ts
35117
+ /**
35118
+ * composer 的拍照接线(分册 14)。照片只作为附件落进输入框,**不发送**——
35119
+ * 与语音同一条口径:拍歪了要能先删掉再重拍,自动发送会把误拍变成不可撤销的操作。
35120
+ */
35121
+ function useComposerCamera(onPermissionDenied) {
35122
+ const aui = useAui();
35123
+ const [availability] = useState(() => checkCameraAvailability());
35124
+ const [previewing, setPreviewing] = useState(false);
35125
+ const [busy, setBusy] = useState(false);
35126
+ const [failure, setFailure] = useState();
35127
+ const sessionRef = useRef(void 0);
35128
+ const videoElementRef = useRef(null);
35129
+ const close = useCallback(() => {
35130
+ sessionRef.current?.close();
35131
+ sessionRef.current = void 0;
35132
+ if (videoElementRef.current !== null) videoElementRef.current.srcObject = null;
35133
+ setPreviewing(false);
35134
+ setBusy(false);
35135
+ }, []);
35136
+ useEffect(() => () => sessionRef.current?.close(), []);
35137
+ /** 侧栏 / popup 这类页面弹不出授权框,只有宿主知道该把用户带到哪儿去(FR-14.7) */
35138
+ const report = useCallback((e) => {
35139
+ const denied = e instanceof WebSkillError && e.code === "CAMERA_PERMISSION_DENIED";
35140
+ setFailure(denied ? "denied" : "failed");
35141
+ if (denied) onPermissionDenied?.();
35142
+ }, [onPermissionDenied]);
35143
+ const videoRef = useCallback((element) => {
35144
+ videoElementRef.current = element;
35145
+ if (element === null) return;
35146
+ const stream = sessionRef.current?.stream;
35147
+ if (stream === void 0) return;
35148
+ element.srcObject = stream;
35149
+ element.play().catch(() => void 0);
35150
+ }, []);
35151
+ return {
35152
+ availability,
35153
+ previewing,
35154
+ busy,
35155
+ start: useCallback(() => {
35156
+ if (!availability.available || busy || previewing) return;
35157
+ setFailure(void 0);
35158
+ setBusy(true);
35159
+ openCamera().then((session) => {
35160
+ sessionRef.current = session;
35161
+ setPreviewing(true);
35162
+ setBusy(false);
35163
+ }).catch((e) => {
35164
+ setBusy(false);
35165
+ report(e);
35166
+ });
35167
+ }, [
35168
+ availability,
35169
+ busy,
35170
+ previewing,
35171
+ report
35172
+ ]),
35173
+ cancel: close,
35174
+ capture: useCallback(() => {
35175
+ const video = videoElementRef.current;
35176
+ if (video === null || busy) return;
35177
+ setBusy(true);
35178
+ capturePhoto(video).then((file) => aui.thread.composer().addAttachment(file)).then(() => close()).catch((e) => {
35179
+ close();
35180
+ report(e);
35181
+ });
35182
+ }, [
35183
+ aui,
35184
+ busy,
35185
+ close,
35186
+ report
35187
+ ]),
35188
+ videoRef,
35189
+ ...failure !== void 0 ? { failure } : {}
35190
+ };
35191
+ }
35192
+
34981
35193
  //#endregion
34982
35194
  //#region src/react/useComposerDictation.ts
34983
35195
  /** 转写片段拼到已有草稿后面:语音只做追加,不覆盖用户已经打好的字 */
@@ -34990,11 +35202,11 @@ function joinText(base, addition) {
34990
35202
  * composer 的语音输入接线(需求 7)。转写结果只写进输入框,**不发送**(FR-7.8)——
34991
35203
  * 语音识别不准是常态,自动发送会把识别错误变成不可撤销的操作。
34992
35204
  */
34993
- function useComposerDictation(lang) {
35205
+ function useComposerDictation(lang, onPermissionDenied) {
34994
35206
  const aui = useAui();
34995
35207
  const [availability] = useState(() => checkDictationAvailability());
34996
35208
  const [recording, setRecording] = useState(false);
34997
- const [error, setError] = useState();
35209
+ const [failure, setFailure] = useState();
34998
35210
  const sessionRef = useRef(void 0);
34999
35211
  /** 本次录音开始时输入框里的内容;中间结果每次都在它后面重画,否则会自我叠加 */
35000
35212
  const baseRef = useRef("");
@@ -35004,6 +35216,12 @@ function useComposerDictation(lang) {
35004
35216
  setRecording(false);
35005
35217
  }, []);
35006
35218
  useEffect(() => () => sessionRef.current?.stop(), []);
35219
+ /** 侧栏 / popup 这类页面弹不出授权框,只有宿主知道该把用户带到哪儿去(FR-10.1) */
35220
+ const report = useCallback((e) => {
35221
+ const denied = e instanceof WebSkillError && e.code === "DICTATION_PERMISSION_DENIED";
35222
+ setFailure(denied ? "denied" : "failed");
35223
+ if (denied) onPermissionDenied?.();
35224
+ }, [onPermissionDenied]);
35007
35225
  return {
35008
35226
  availability,
35009
35227
  recording,
@@ -35015,7 +35233,7 @@ function useComposerDictation(lang) {
35015
35233
  if (!availability.available) return;
35016
35234
  const composer = aui.thread.composer();
35017
35235
  baseRef.current = composer.getState().text;
35018
- setError(void 0);
35236
+ setFailure(void 0);
35019
35237
  try {
35020
35238
  sessionRef.current = startDictation({
35021
35239
  lang,
@@ -35025,21 +35243,22 @@ function useComposerDictation(lang) {
35025
35243
  aui.thread.composer().setText(baseRef.current);
35026
35244
  },
35027
35245
  onError: (e) => {
35028
- setError(messageOf(e));
35246
+ report(e);
35029
35247
  stop();
35030
35248
  }
35031
35249
  });
35032
35250
  setRecording(true);
35033
35251
  } catch (e) {
35034
- setError(messageOf(e));
35252
+ report(e);
35035
35253
  }
35036
35254
  }, [
35037
35255
  aui,
35038
35256
  availability,
35039
35257
  lang,
35258
+ report,
35040
35259
  stop
35041
35260
  ]),
35042
- ...error !== void 0 ? { error } : {}
35261
+ ...failure !== void 0 ? { failure } : {}
35043
35262
  };
35044
35263
  }
35045
35264
 
@@ -35114,13 +35333,22 @@ function ModelMenuButton({ models }) {
35114
35333
  }, option.id))
35115
35334
  })] });
35116
35335
  }
35117
- function AssistantUiComposer({ running, disabled = false, waitTarget, placeholder, attachments = false, attachmentInfo, attachmentImages, centered = false, models, noModel, dictationLang, capabilities }) {
35336
+ function AssistantUiComposer({ running, disabled = false, waitTarget, placeholder, attachments = false, attachmentInfo, attachmentImages, centered = false, models, noModel, dictationLang, onDictationPermissionDenied, onCameraPermissionDenied, capabilities }) {
35118
35337
  const t = useT();
35119
35338
  const locale = useLocale();
35120
- const dictation = useComposerDictation(dictationLang ?? DEFAULT_DICTATION_LANG[locale]);
35339
+ const dictation = useComposerDictation(dictationLang ?? DEFAULT_DICTATION_LANG[locale], onDictationPermissionDenied);
35340
+ const camera = useComposerCamera(onCameraPermissionDenied);
35121
35341
  const dictationReason = dictation.availability.available ? void 0 : t(`composer.dictate.${dictation.availability.reason}`);
35342
+ const dictationFailure = dictation.failure === void 0 ? void 0 : dictation.failure === "denied" && onDictationPermissionDenied !== void 0 ? t("composer.dictate.denied.handoff") : t(`composer.dictate.${dictation.failure}`);
35122
35343
  const waitReason = disabled ? t("interaction.wait", { target: waitTarget ?? t("interaction.wait.fallback") }) : void 0;
35123
35344
  const attachmentReason = attachmentImages === void 0 || attachmentImages.enabled && attachmentImages.imageCapable ? void 0 : attachmentImages.enabled ? t("composer.addAttachment.modelNoImages") : t("composer.addAttachment.imagesOff");
35345
+ /**
35346
+ * 相机的禁用原因(FR-14.2)。三道门缺一不可,且顺序即优先级:
35347
+ * 存不下 → 图片被关 / 模型不收图 → 浏览器不支持。先说最外层那道,
35348
+ * 否则用户会照着「换 https」折腾半天,其实是模型压根不收图。
35349
+ */
35350
+ const cameraReason = !attachments ? t("composer.camera.noStorage") : attachmentReason ?? (camera.availability.available ? void 0 : t(`composer.camera.${camera.availability.reason}`));
35351
+ const cameraFailure = camera.failure === void 0 ? void 0 : camera.failure === "denied" && onCameraPermissionDenied !== void 0 ? t("composer.camera.denied.handoff") : t(`composer.camera.${camera.failure}`);
35124
35352
  const plainChatHintId = useId();
35125
35353
  return /* @__PURE__ */ jsx(ComposerPrimitive.Root, {
35126
35354
  "data-testid": "assistant-ui-composer",
@@ -35254,70 +35482,102 @@ function AssistantUiComposer({ running, disabled = false, waitTarget, placeholde
35254
35482
  ]
35255
35483
  }), /* @__PURE__ */ jsxs("div", {
35256
35484
  className: "flex shrink-0 items-center gap-1",
35257
- children: [/* @__PURE__ */ jsxs("button", {
35258
- type: "button",
35259
- "data-testid": "composer-dictate",
35260
- "data-recording": dictation.recording ? "true" : void 0,
35261
- "aria-pressed": dictation.recording,
35262
- "aria-label": dictation.recording ? t("composer.dictate.stop") : t("composer.dictate"),
35263
- title: dictationReason ?? (dictation.recording ? t("composer.dictate.stop") : t("composer.dictate")),
35264
- disabled: dictationReason !== void 0,
35265
- onClick: dictation.toggle,
35266
- className: `inline-flex size-8 shrink-0 items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${dictation.recording ? "animate-pulse bg-danger/10 text-danger" : "text-muted hover:bg-accent hover:text-ink"}`,
35267
- children: [dictation.recording ? /* @__PURE__ */ jsx(Square, {
35268
- className: "size-3 fill-current",
35269
- "aria-hidden": true
35270
- }) : /* @__PURE__ */ jsx(Mic, {
35271
- className: "size-4",
35272
- "aria-hidden": true
35273
- }), /* @__PURE__ */ jsx("span", {
35274
- className: "sr-only",
35275
- children: dictation.recording ? t("composer.dictate.recording") : t("composer.dictate")
35276
- })]
35277
- }), running ? /* @__PURE__ */ jsxs(ComposerPrimitive.Cancel, {
35278
- "data-testid": "composer-send",
35279
- "aria-label": t("composer.stop"),
35280
- className: "inline-flex size-8 shrink-0 items-center justify-center rounded-full border border-border text-ink transition-colors hover:bg-accent",
35281
- children: [/* @__PURE__ */ jsx(Square, {
35282
- className: "size-3 fill-current",
35283
- "aria-hidden": true
35284
- }), /* @__PURE__ */ jsx("span", {
35285
- className: "sr-only",
35286
- children: t("composer.stop")
35287
- })]
35288
- }) : noModel ? /* @__PURE__ */ jsxs("button", {
35289
- type: "button",
35290
- "data-testid": "composer-send",
35291
- "aria-label": t("composer.send"),
35292
- onClick: noModel.onConfigure,
35293
- className: "webskill-aui-send inline-flex size-8 shrink-0 items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50",
35294
- children: [/* @__PURE__ */ jsx(ArrowUp, {
35295
- className: "size-4",
35296
- "aria-hidden": true
35297
- }), /* @__PURE__ */ jsx("span", {
35298
- className: "sr-only",
35299
- children: t("composer.send")
35300
- })]
35301
- }) : /* @__PURE__ */ jsxs(ComposerPrimitive.Send, {
35302
- "data-testid": "composer-send",
35303
- "aria-label": t("composer.send"),
35304
- className: "webskill-aui-send inline-flex size-8 shrink-0 items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50",
35305
- children: [/* @__PURE__ */ jsx(ArrowUp, {
35306
- className: "size-4",
35307
- "aria-hidden": true
35308
- }), /* @__PURE__ */ jsx("span", {
35309
- className: "sr-only",
35310
- children: t("composer.send")
35311
- })]
35312
- })]
35485
+ children: [
35486
+ /* @__PURE__ */ jsxs("button", {
35487
+ type: "button",
35488
+ "data-testid": "composer-camera",
35489
+ "aria-label": t("composer.camera"),
35490
+ title: cameraReason === void 0 ? t("composer.camera") : `${t("composer.camera")} — ${cameraReason}`,
35491
+ disabled: cameraReason !== void 0 || camera.busy,
35492
+ onClick: camera.start,
35493
+ className: "inline-flex size-8 shrink-0 items-center justify-center rounded-full text-muted transition-colors hover:bg-accent hover:text-ink disabled:cursor-not-allowed disabled:opacity-50",
35494
+ children: [/* @__PURE__ */ jsx(Camera, {
35495
+ className: "size-4",
35496
+ "aria-hidden": true
35497
+ }), /* @__PURE__ */ jsx("span", {
35498
+ className: "sr-only",
35499
+ children: t("composer.camera")
35500
+ })]
35501
+ }),
35502
+ /* @__PURE__ */ jsxs("button", {
35503
+ type: "button",
35504
+ "data-testid": "composer-dictate",
35505
+ "data-recording": dictation.recording ? "true" : void 0,
35506
+ "aria-pressed": dictation.recording,
35507
+ "aria-label": dictation.recording ? t("composer.dictate.stop") : t("composer.dictate"),
35508
+ title: dictationReason ?? (dictation.recording ? t("composer.dictate.stop") : t("composer.dictate")),
35509
+ disabled: dictationReason !== void 0,
35510
+ onClick: dictation.toggle,
35511
+ className: `inline-flex size-8 shrink-0 items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50 ${dictation.recording ? "animate-pulse bg-danger/10 text-danger" : "text-muted hover:bg-accent hover:text-ink"}`,
35512
+ children: [dictation.recording ? /* @__PURE__ */ jsx(Square, {
35513
+ className: "size-3 fill-current",
35514
+ "aria-hidden": true
35515
+ }) : /* @__PURE__ */ jsx(Mic, {
35516
+ className: "size-4",
35517
+ "aria-hidden": true
35518
+ }), /* @__PURE__ */ jsx("span", {
35519
+ className: "sr-only",
35520
+ children: dictation.recording ? t("composer.dictate.recording") : t("composer.dictate")
35521
+ })]
35522
+ }),
35523
+ running ? /* @__PURE__ */ jsxs(ComposerPrimitive.Cancel, {
35524
+ "data-testid": "composer-send",
35525
+ "aria-label": t("composer.stop"),
35526
+ className: "inline-flex size-8 shrink-0 items-center justify-center rounded-full border border-border text-ink transition-colors hover:bg-accent",
35527
+ children: [/* @__PURE__ */ jsx(Square, {
35528
+ className: "size-3 fill-current",
35529
+ "aria-hidden": true
35530
+ }), /* @__PURE__ */ jsx("span", {
35531
+ className: "sr-only",
35532
+ children: t("composer.stop")
35533
+ })]
35534
+ }) : noModel ? /* @__PURE__ */ jsxs("button", {
35535
+ type: "button",
35536
+ "data-testid": "composer-send",
35537
+ "aria-label": t("composer.send"),
35538
+ onClick: noModel.onConfigure,
35539
+ className: "webskill-aui-send inline-flex size-8 shrink-0 items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50",
35540
+ children: [/* @__PURE__ */ jsx(ArrowUp, {
35541
+ className: "size-4",
35542
+ "aria-hidden": true
35543
+ }), /* @__PURE__ */ jsx("span", {
35544
+ className: "sr-only",
35545
+ children: t("composer.send")
35546
+ })]
35547
+ }) : /* @__PURE__ */ jsxs(ComposerPrimitive.Send, {
35548
+ "data-testid": "composer-send",
35549
+ "aria-label": t("composer.send"),
35550
+ className: "webskill-aui-send inline-flex size-8 shrink-0 items-center justify-center rounded-full transition-colors disabled:cursor-not-allowed disabled:opacity-50",
35551
+ children: [/* @__PURE__ */ jsx(ArrowUp, {
35552
+ className: "size-4",
35553
+ "aria-hidden": true
35554
+ }), /* @__PURE__ */ jsx("span", {
35555
+ className: "sr-only",
35556
+ children: t("composer.send")
35557
+ })]
35558
+ })
35559
+ ]
35313
35560
  })]
35314
35561
  }),
35315
- dictation.error !== void 0 ? /* @__PURE__ */ jsx("p", {
35562
+ dictationFailure !== void 0 ? /* @__PURE__ */ jsx("p", {
35316
35563
  "data-testid": "composer-dictate-error",
35317
35564
  role: "alert",
35318
35565
  className: "text-xs text-danger",
35319
- children: dictation.error
35320
- }) : null
35566
+ children: dictationFailure
35567
+ }) : null,
35568
+ cameraFailure !== void 0 ? /* @__PURE__ */ jsx("p", {
35569
+ "data-testid": "composer-camera-error",
35570
+ role: "alert",
35571
+ className: "text-xs text-danger",
35572
+ children: cameraFailure
35573
+ }) : null,
35574
+ /* @__PURE__ */ jsx(CameraCaptureDialog, {
35575
+ open: camera.previewing,
35576
+ busy: camera.busy,
35577
+ videoRef: camera.videoRef,
35578
+ onCapture: camera.capture,
35579
+ onCancel: camera.cancel
35580
+ })
35321
35581
  ]
35322
35582
  })
35323
35583
  });
@@ -45881,6 +46141,145 @@ function InlineBlocks() {
45881
46141
  });
45882
46142
  }
45883
46143
 
46144
+ //#endregion
46145
+ //#region src/react/authorizeDetails.ts
46146
+ /** 页面操作确认卡的载荷;形状不对就整段不渲染,退回通用授权展示 */
46147
+ function pageActionDetails(details) {
46148
+ if (typeof details !== "object" || details === null) return void 0;
46149
+ const record = details;
46150
+ const action = record["action"];
46151
+ const target = record["target"];
46152
+ if (typeof action !== "string" || !PAGE_ACTION_KINDS.includes(action)) return void 0;
46153
+ if (typeof target !== "object" || target === null) return void 0;
46154
+ const { role, name, frame } = target;
46155
+ if (typeof role !== "string") return void 0;
46156
+ const value = record["value"];
46157
+ const rememberLabel = record["rememberLabel"];
46158
+ return {
46159
+ action,
46160
+ role,
46161
+ ...typeof name === "string" ? { name } : {},
46162
+ ...typeof value === "string" ? { value } : {},
46163
+ ...typeof frame === "string" ? { frame } : {},
46164
+ ...record["elevated"] === true ? { elevated: true } : {},
46165
+ ...record["rememberable"] === true ? { rememberable: true } : {},
46166
+ ...typeof rememberLabel === "string" ? { rememberLabel } : {}
46167
+ };
46168
+ }
46169
+ /**
46170
+ * 下载文件确认卡的载荷(0.14.0 分册 20)。与 `pageActionDetails` 同一条规矩:
46171
+ * 形状不对就整段不渲染,退回通用授权展示,不猜。
46172
+ */
46173
+ function downloadedFileDetails(details) {
46174
+ if (typeof details !== "object" || details === null) return void 0;
46175
+ const record = details;
46176
+ const action = record["action"];
46177
+ if (action !== "list" && action !== "read") return void 0;
46178
+ const file = record["file"];
46179
+ const rememberLabel = record["rememberLabel"];
46180
+ const named = typeof file === "object" && file !== null ? file : void 0;
46181
+ return {
46182
+ action,
46183
+ ...typeof named?.["name"] === "string" && typeof named["size"] === "number" ? { file: {
46184
+ name: named["name"],
46185
+ size: named["size"]
46186
+ } } : {},
46187
+ ...record["rememberable"] === true ? { rememberable: true } : {},
46188
+ ...typeof rememberLabel === "string" ? { rememberLabel } : {}
46189
+ };
46190
+ }
46191
+ /** 上传文件确认卡的载荷(0.15.0 分册 17,0.17.0 分册 19 起结构化) */
46192
+ function uploadFileDetails(details) {
46193
+ if (typeof details !== "object" || details === null) return void 0;
46194
+ const record = details;
46195
+ const skill = record["skill"];
46196
+ if (typeof skill !== "string") return void 0;
46197
+ const file = record["file"];
46198
+ const named = typeof file === "object" && file !== null ? file : void 0;
46199
+ return {
46200
+ skill,
46201
+ ...typeof named?.["name"] === "string" && typeof named["contentType"] === "string" && typeof named["size"] === "number" ? { file: {
46202
+ name: named["name"],
46203
+ contentType: named["contentType"],
46204
+ size: named["size"]
46205
+ } } : {}
46206
+ };
46207
+ }
46208
+ /** 五个沙箱能力闸门的共用载荷(`gate()` 统一注入 `skill`,其余按能力各带一项) */
46209
+ function sandboxDetails(details) {
46210
+ if (typeof details !== "object" || details === null) return void 0;
46211
+ const record = details;
46212
+ const skill = record["skill"];
46213
+ if (typeof skill !== "string") return void 0;
46214
+ const out = { skill };
46215
+ for (const key of [
46216
+ "path",
46217
+ "sourceId",
46218
+ "question"
46219
+ ]) {
46220
+ const value = record[key];
46221
+ if (typeof value === "string") out[key] = value;
46222
+ }
46223
+ return out;
46224
+ }
46225
+ /**
46226
+ * 授权卡正文的本地化文案(分册 19 FR-19.2 / FR-19.6)。
46227
+ *
46228
+ * 活卡、只读回看、非 native 三档的 spec 通道读的是同一个 `request`,因此这份映射
46229
+ * 只能有一份:写进任一视图都会让另外两个继续显示 runtime 的英文原句——
46230
+ * 0.12.0 分册 23 与 0.16.0 分册 15 各犯过一次。
46231
+ * 认不得的能力或形状不对的载荷返回 undefined,正文回落到 `request.message`(FR-19.5)。
46232
+ */
46233
+ function localizedAuthorizeMessage(request, t) {
46234
+ if (request.type !== "authorize") return void 0;
46235
+ if (request.capability === "readUploadFile") {
46236
+ const details = uploadFileDetails(request.details);
46237
+ if (details === void 0) return void 0;
46238
+ const head = t("interaction.uploadFile.read", { skill: details.skill });
46239
+ if (details.file === void 0) return head;
46240
+ return `${head} ${t("interaction.uploadFile.file", {
46241
+ name: details.file.name,
46242
+ type: details.file.contentType,
46243
+ size: formatBytes(details.file.size)
46244
+ })}`;
46245
+ }
46246
+ if (request.capability === "readReference" || request.capability === "readAsset" || request.capability === "writeArtifact" || request.capability === "fetchData" || request.capability === "confirm") {
46247
+ const details = sandboxDetails(request.details);
46248
+ if (details === void 0) return void 0;
46249
+ if (request.capability === "fetchData") {
46250
+ if (details["sourceId"] === void 0) return void 0;
46251
+ return t("interaction.sandbox.fetchData", {
46252
+ skill: details["skill"],
46253
+ source: details["sourceId"]
46254
+ });
46255
+ }
46256
+ if (request.capability === "confirm") {
46257
+ const head = t("interaction.sandbox.confirm", { skill: details["skill"] });
46258
+ return details["question"] === void 0 ? head : `${head} ${details["question"]}`;
46259
+ }
46260
+ if (details["path"] === void 0) return void 0;
46261
+ return t(`interaction.sandbox.${request.capability}`, {
46262
+ skill: details["skill"],
46263
+ path: details["path"]
46264
+ });
46265
+ }
46266
+ if (request.capability === "pageAction") {
46267
+ const details = pageActionDetails(request.details);
46268
+ if (details === void 0) return void 0;
46269
+ return t(`interaction.pageAction.${details.action}`, { target: details.name ?? t(`interaction.pageAction.role.${details.action}`) });
46270
+ }
46271
+ if (request.capability === "readDownloadedFile") {
46272
+ const details = downloadedFileDetails(request.details);
46273
+ if (details === void 0) return void 0;
46274
+ const head = t(`interaction.downloadedFile.${details.action}`);
46275
+ if (details.file === void 0) return head;
46276
+ return `${head} ${t("interaction.downloadedFile.file", {
46277
+ name: details.file.name,
46278
+ size: formatBytes(details.file.size)
46279
+ })}`;
46280
+ }
46281
+ }
46282
+
45884
46283
  //#endregion
45885
46284
  //#region src/react/interactionLabels.ts
45886
46285
  /**
@@ -45900,10 +46299,12 @@ function interactionTitleKey(request) {
45900
46299
  * 四档共用本函数,切换渲染档不会让同一个请求出现两套文案。
45901
46300
  */
45902
46301
  function interactionLabels(request, t) {
46302
+ const message = localizedAuthorizeMessage(request, t);
45903
46303
  return {
45904
46304
  title: request.type === "form" && request.title ? request.title : t(interactionTitleKey(request)),
45905
46305
  submitLabel: t(request.type === "authorize" ? "interaction.allow" : request.type === "confirm" ? "interaction.confirm" : "interaction.submit"),
45906
- cancelLabel: t(request.type === "authorize" ? "interaction.deny" : "interaction.cancel")
46306
+ cancelLabel: t(request.type === "authorize" ? "interaction.deny" : "interaction.cancel"),
46307
+ ...message !== void 0 ? { message } : {}
45907
46308
  };
45908
46309
  }
45909
46310
 
@@ -47441,51 +47842,6 @@ function fileToBase64(file) {
47441
47842
  reader.readAsDataURL(file);
47442
47843
  });
47443
47844
  }
47444
- /** 页面操作确认卡的载荷;形状不对就整段不渲染,退回通用授权展示 */
47445
- function pageActionDetails(details) {
47446
- if (typeof details !== "object" || details === null) return void 0;
47447
- const record = details;
47448
- const action = record["action"];
47449
- const target = record["target"];
47450
- if (typeof action !== "string" || !PAGE_ACTION_KINDS.includes(action)) return void 0;
47451
- if (typeof target !== "object" || target === null) return void 0;
47452
- const { role, name, frame } = target;
47453
- if (typeof role !== "string") return void 0;
47454
- const value = record["value"];
47455
- const rememberLabel = record["rememberLabel"];
47456
- return {
47457
- action,
47458
- role,
47459
- ...typeof name === "string" ? { name } : {},
47460
- ...typeof value === "string" ? { value } : {},
47461
- ...typeof frame === "string" ? { frame } : {},
47462
- ...record["elevated"] === true ? { elevated: true } : {},
47463
- ...record["rememberable"] === true ? { rememberable: true } : {},
47464
- ...typeof rememberLabel === "string" ? { rememberLabel } : {}
47465
- };
47466
- }
47467
- /**
47468
- * 下载文件确认卡的载荷(0.14.0 分册 20)。与 `pageActionDetails` 同一条规矩:
47469
- * 形状不对就整段不渲染,退回通用授权展示,不猜。
47470
- */
47471
- function downloadedFileDetails(details) {
47472
- if (typeof details !== "object" || details === null) return void 0;
47473
- const record = details;
47474
- const action = record["action"];
47475
- if (action !== "list" && action !== "read") return void 0;
47476
- const file = record["file"];
47477
- const rememberLabel = record["rememberLabel"];
47478
- const named = typeof file === "object" && file !== null ? file : void 0;
47479
- return {
47480
- action,
47481
- ...typeof named?.["name"] === "string" && typeof named["size"] === "number" ? { file: {
47482
- name: named["name"],
47483
- size: named["size"]
47484
- } } : {},
47485
- ...record["rememberable"] === true ? { rememberable: true } : {},
47486
- ...typeof rememberLabel === "string" ? { rememberLabel } : {}
47487
- };
47488
- }
47489
47845
  /**
47490
47846
  * 轨迹证据载荷(分册 30)。形状不对就整段不渲染,退回 `preview` 纯文本——
47491
47847
  * 与 `pageActionDetails` 同一个先例:认不得的结构不猜。
@@ -47527,6 +47883,7 @@ function ActiveInteractionCard({ request, bridge }) {
47527
47883
  message: ""
47528
47884
  } : request, {
47529
47885
  ...interactionLabels(request, t),
47886
+ ...localized ? { message: "" } : {},
47530
47887
  title: request.type === "form" && request.title ? request.title : ""
47531
47888
  });
47532
47889
  const preview = isAuthorize && typeof request.details === "object" && request.details !== null ? request.details["preview"] : void 0;
@@ -48132,7 +48489,7 @@ async function downloadArtifactFile(storage, chatRoot, file) {
48132
48489
  * presentation shell and accessible thread primitives.
48133
48490
  * @stable
48134
48491
  */
48135
- function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, renderer: rendererProp, dictationLang: dictationLangProp, surfaceRegistry, layout = "auto", sessionList = "auto", onOpenSettings, docked, onDock, onClose, onEngineReady }) {
48492
+ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, renderer: rendererProp, dictationLang: dictationLangProp, onDictationPermissionDenied, onCameraPermissionDenied, surfaceRegistry, layout = "auto", sessionList = "auto", onOpenSettings, docked, onDock, onClose, onEngineReady }) {
48136
48493
  const engine = useMemo(() => new ChatEngine(adapter, {
48137
48494
  ...config?.chatRoot ? { chatRoot: config.chatRoot } : {},
48138
48495
  ...config?.llm ? { llm: config.llm } : {},
@@ -48829,6 +49186,8 @@ function Chatbot({ adapter, config, locale: localeProp, theme: themeProp, render
48829
49186
  imageCapable: modelTakesImages
48830
49187
  },
48831
49188
  ...dictationLang !== "" ? { dictationLang } : {},
49189
+ ...onDictationPermissionDenied ? { onDictationPermissionDenied } : {},
49190
+ ...onCameraPermissionDenied ? { onCameraPermissionDenied } : {},
48832
49191
  ...composerModels && !noUsableModel ? { models: composerModels } : {},
48833
49192
  ...noUsableModel ? { noModel: { onConfigure: () => setNoModelGuideOpen(true) } } : {},
48834
49193
  ...config?.placeholder ? { placeholder: config.placeholder } : {}
@@ -48905,7 +49264,7 @@ function VercelPayloadPreview({ bridge }) {
48905
49264
  * Version of the published `@webskill/chatbot` package, injected at build time.
48906
49265
  * @stable
48907
49266
  */
48908
- const CHATBOT_VERSION = "0.13.0";
49267
+ const CHATBOT_VERSION = "0.14.0";
48909
49268
 
48910
49269
  //#endregion
48911
49270
  export { A2uiSurfaceHost, A2uiSurfaceSnapshotHost, CHATBOT_VERSION, ChatEngine, Chatbot, CompositeUiBridge, DEFAULT_RENDERER_CAPABILITIES, DEFAULT_RUNTIME_CONFIG_STORAGE_KEY, ErrorCard, InteractionCard, InterruptedBanner, OpenUiSurfaceHost, OpenUiSurfaceSnapshotHost, ResultBlockList, ResultBlocksPro, SkillBadges, SpecInteraction, VercelPayloadPreview, VercelSurfaceHost, VercelSurfaceSnapshotHost, chatbotDictionary, configureA2uiMarkdown, createLocalStorageRuntimeConfigStore, createMemoryRuntimeConfigStore, isLlmEntryUsable, pickUsableLlmEntry, probeA2uiAvailability, probeOpenUiAvailability, sandboxExecutorDeps, useT };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@webskill/chatbot",
3
- "version": "0.13.0",
3
+ "version": "0.14.0",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
6
  "exports": {
@@ -26,7 +26,7 @@
26
26
  "@a2ui/web_core": "^0.10.5",
27
27
  "@lit/context": "^1.1.0",
28
28
  "@openuidev/react-lang": "0.2.8",
29
- "@webskill/sdk": "^0.16.0",
29
+ "@webskill/sdk": "^0.17.0",
30
30
  "react": ">=19.0.0",
31
31
  "react-dom": ">=19.0.0",
32
32
  "zod": "^3.25.0 || ^4.0.0"