@ant-design/agentic-ui 2.29.26 → 2.29.28

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.
@@ -275,7 +275,7 @@ var shouldRenderBeforeContent = function shouldRenderBeforeContent(placement, ro
275
275
  var _React_useState = _sliced_to_array(React.useState(false), 2), hidePadding = _React_useState[0], setHidePadding = _React_useState[1];
276
276
  var getPrefixCls = useContext(ConfigProvider.ConfigContext).getPrefixCls;
277
277
  var context = useContext(BubbleConfigContext);
278
- var _ref = context || {}, compact = _ref.compact, standalone = _ref.standalone, locale = _ref.locale;
278
+ var _ref = context || {}, compact = _ref.compact, standalone = _ref.standalone;
279
279
  var prefixClass = getPrefixCls('agentic');
280
280
  var _useStyle = useStyle(prefixClass), wrapSSR = _useStyle.wrapSSR, hashId = _useStyle.hashId;
281
281
  var preMessageSameRole = isSameRoleAsPrevious(preMessage, originData);
@@ -356,7 +356,6 @@ var shouldRenderBeforeContent = function shouldRenderBeforeContent(placement, ro
356
356
  value: {
357
357
  compact: compact,
358
358
  standalone: !!standalone,
359
- locale: locale,
360
359
  bubble: props
361
360
  }
362
361
  }, /*#__PURE__*/ React.createElement(Flex, {
@@ -1,5 +1,4 @@
1
1
  import React from 'react';
2
- import type { LocalKeys } from '../I18n';
3
2
  import { ThoughtChainListProps } from '../ThoughtChainList/types';
4
3
  import { BubbleProps } from './type';
5
4
  export type ChatConfigType = {
@@ -18,8 +17,6 @@ export type ChatConfigType = {
18
17
  */
19
18
  enable: boolean;
20
19
  };
21
- /** 可选覆盖,与 I18nContext 合并时优先生效,国际化主数据源为 I18nContext */
22
- locale?: Partial<LocalKeys>;
23
20
  bubble?: BubbleProps<{
24
21
  /**
25
22
  * 聊天内容
@@ -1,5 +1,4 @@
1
1
  import React from "react";
2
2
  export var BubbleConfigContext = /*#__PURE__*/ React.createContext({
3
- standalone: false,
4
- locale: undefined
3
+ standalone: false
5
4
  });
@@ -269,7 +269,7 @@ import { motion } from "framer-motion";
269
269
  import React, { useContext, useEffect, useMemo, useState } from "react";
270
270
  import { ActionIconBox } from "../../Components/ActionIconBox";
271
271
  import { Loading } from "../../Components/Loading";
272
- import { useMergedLocale } from "../../I18n";
272
+ import { useLocale } from "../../I18n";
273
273
  import { BubbleConfigContext } from "../BubbleConfigProvide";
274
274
  import { CopyButton } from "./CopyButton";
275
275
  import { VoiceButton } from "./VoiceButton";
@@ -307,7 +307,7 @@ import { VoiceButton } from "./VoiceButton";
307
307
  var _originalData_extra, _originalData_extra1, _originalData_extra2, _originalData_extra3, _bubble_originData, _bubble_originData1, _props_rightRender;
308
308
  var getPrefixCls = useContext(ConfigProvider.ConfigContext).getPrefixCls;
309
309
  var context = useContext(BubbleConfigContext);
310
- var locale = useMergedLocale(context === null || context === void 0 ? void 0 : context.locale);
310
+ var locale = useLocale();
311
311
  var _useState = _sliced_to_array(useState(false), 2), feedbackLoading = _useState[0], setFeedbackLoading = _useState[1];
312
312
  // 获取聊天项的原始数据
313
313
  var originalData = bubble === null || bubble === void 0 ? void 0 : bubble.originData;
@@ -214,14 +214,13 @@ function _ts_generator(thisArg, body) {
214
214
  };
215
215
  }
216
216
  }
217
- import { memo, useContext } from "react";
217
+ import { memo } from "react";
218
218
  import { CheckCircleFilled } from "@ant-design/icons";
219
219
  import { isFunction } from "lodash-es";
220
220
  import React from "react";
221
221
  import { ActionIconBox } from "../../../Components/ActionIconBox";
222
222
  import { useCopied } from "../../../Hooks/useCopied";
223
- import { useMergedLocale } from "../../../I18n";
224
- import { BubbleConfigContext } from "../../BubbleConfigProvide";
223
+ import { useLocale } from "../../../I18n";
225
224
  /**
226
225
  * CopyIcon 组件 - 复制图标组件
227
226
  *
@@ -310,9 +309,8 @@ import { BubbleConfigContext } from "../../BubbleConfigProvide";
310
309
  "onClick",
311
310
  'data-testid'
312
311
  ]);
313
- var _useContext;
314
312
  var _useCopied = useCopied(), copied = _useCopied.copied, setCopied = _useCopied.setCopied;
315
- var locale = useMergedLocale((_useContext = useContext(BubbleConfigContext)) === null || _useContext === void 0 ? void 0 : _useContext.locale);
313
+ var locale = useLocale();
316
314
  var copySuccessText = (locale === null || locale === void 0 ? void 0 : locale['chat.message.copy.success']) || '复制成功';
317
315
  return /*#__PURE__*/ React.createElement(ActionIconBox, _object_spread_props(_object_spread({
318
316
  onClick: function onClick1(e) {
@@ -54,7 +54,7 @@ import { Popover, theme } from "antd";
54
54
  import React, { useContext, useEffect, useMemo } from "react";
55
55
  import { ErrorBoundary } from "react-error-boundary";
56
56
  import { MarkdownEditor, parserMdToSchema } from "../..";
57
- import { useMergedLocale } from "../../I18n";
57
+ import { useLocale } from "../../I18n";
58
58
  import { BubbleConfigContext } from "../BubbleConfigProvide";
59
59
  import { MessagesContext } from "./BubbleContext";
60
60
  /**
@@ -98,7 +98,7 @@ import { MessagesContext } from "./BubbleContext";
98
98
  var MarkdownEditorRef = React.useRef(undefined);
99
99
  var hidePadding = (useContext(MessagesContext) || {}).hidePadding;
100
100
  var config = useContext(BubbleConfigContext);
101
- var locale = useMergedLocale(config === null || config === void 0 ? void 0 : config.locale);
101
+ var locale = useLocale();
102
102
  var standalone = config === null || config === void 0 ? void 0 : config.standalone;
103
103
  var token = theme.useToken().token;
104
104
  var isPaddingHidden = useMemo(function() {
@@ -279,7 +279,7 @@ export var PureBubble = /*#__PURE__*/ memo(function(props) {
279
279
  var _React_useState = _sliced_to_array(React.useState(false), 2), hidePadding = _React_useState[0], setHidePadding = _React_useState[1];
280
280
  var getPrefixCls = useContext(ConfigProvider.ConfigContext).getPrefixCls;
281
281
  var context = useContext(BubbleConfigContext);
282
- var _ref1 = context || {}, compact = _ref1.compact, standalone = _ref1.standalone, locale = _ref1.locale;
282
+ var _ref1 = context || {}, compact = _ref1.compact, standalone = _ref1.standalone;
283
283
  var prefixClass = getPrefixCls('agentic');
284
284
  var _useStyle = useStyle(prefixClass), wrapSSR = _useStyle.wrapSSR, hashId = _useStyle.hashId;
285
285
  var placement = (_props_placement = props.placement) !== null && _props_placement !== void 0 ? _props_placement : 'left';
@@ -452,7 +452,6 @@ export var PureBubble = /*#__PURE__*/ memo(function(props) {
452
452
  value: {
453
453
  compact: compact,
454
454
  standalone: !!standalone,
455
- locale: locale,
456
455
  bubble: props
457
456
  }
458
457
  }, /*#__PURE__*/ React.createElement(Flex, {
@@ -128,7 +128,7 @@ var getContentStyle = function getContentStyle(standalone, customStyle) {
128
128
  var _React_useState = _sliced_to_array(React.useState(false), 2), hidePadding = _React_useState[0], setHidePadding = _React_useState[1];
129
129
  var getPrefixCls = useContext(ConfigProvider.ConfigContext).getPrefixCls;
130
130
  var context = useContext(BubbleConfigContext);
131
- var _ref = context || {}, compact = _ref.compact, standalone = _ref.standalone, locale = _ref.locale;
131
+ var _ref = context || {}, compact = _ref.compact, standalone = _ref.standalone;
132
132
  var prefixClass = getPrefixCls('agentic');
133
133
  var _useStyle = useStyle(prefixClass, classNames), wrapSSR = _useStyle.wrapSSR, hashId = _useStyle.hashId;
134
134
  var time = (originData === null || originData === void 0 ? void 0 : originData.createAt) || props.time;
@@ -180,7 +180,6 @@ var getContentStyle = function getContentStyle(standalone, customStyle) {
180
180
  value: {
181
181
  compact: compact,
182
182
  standalone: !!standalone,
183
- locale: locale,
184
183
  bubble: props
185
184
  }
186
185
  }, /*#__PURE__*/ React.createElement(Flex, {
@@ -203,7 +202,12 @@ var getContentStyle = function getContentStyle(standalone, customStyle) {
203
202
  style: styles === null || styles === void 0 ? void 0 : styles.bubbleListItemExtraStyle,
204
203
  className: cx("".concat(prefixClass, "-bubble-before"), "".concat(prefixClass, "-bubble-before-").concat(placement), "".concat(prefixClass, "-bubble-before-user"), hashId),
205
204
  "data-testid": "message-before"
206
- }, contentBeforeDom), hasFileMap && /*#__PURE__*/ React.createElement("div", {
205
+ }, contentBeforeDom), /*#__PURE__*/ React.createElement("div", {
206
+ style: contentStyle,
207
+ className: cx("".concat(prefixClass, "-bubble-content"), "".concat(prefixClass, "-bubble-content-").concat(placement), "".concat(prefixClass, "-bubble-content-user"), _define_property({}, "".concat(prefixClass, "-bubble-content-pure"), props.pure), classNames === null || classNames === void 0 ? void 0 : classNames.bubbleListItemContentClassName, hashId),
208
+ onDoubleClick: props.onDoubleClick,
209
+ "data-testid": "message-content"
210
+ }, childrenDom), hasFileMap && /*#__PURE__*/ React.createElement("div", {
207
211
  style: fileViewStyle,
208
212
  className: cx("".concat(prefixClass, "-bubble-after"), "".concat(prefixClass, "-bubble-after-").concat(placement), "".concat(prefixClass, "-bubble-after-ai"), hashId),
209
213
  "data-testid": "message-after"
@@ -211,12 +215,7 @@ var getContentStyle = function getContentStyle(standalone, customStyle) {
211
215
  bubbleListRef: props.bubbleListRef,
212
216
  bubble: props,
213
217
  placement: placement
214
- })), /*#__PURE__*/ React.createElement("div", {
215
- style: contentStyle,
216
- className: cx("".concat(prefixClass, "-bubble-content"), "".concat(prefixClass, "-bubble-content-").concat(placement), "".concat(prefixClass, "-bubble-content-user"), _define_property({}, "".concat(prefixClass, "-bubble-content-pure"), props.pure), classNames === null || classNames === void 0 ? void 0 : classNames.bubbleListItemContentClassName, hashId),
217
- onDoubleClick: props.onDoubleClick,
218
- "data-testid": "message-content"
219
- }, childrenDom), contentAfterDom)))));
218
+ })), contentAfterDom)))));
220
219
  if ((bubbleRenderConfig === null || bubbleRenderConfig === void 0 ? void 0 : bubbleRenderConfig.render) === false) return null;
221
220
  return /*#__PURE__*/ React.createElement(MessagesContext.Provider, {
222
221
  value: {
@@ -85,6 +85,10 @@ export declare class SchemaEditorBridgeManager {
85
85
  * 检查某个 ID 是否已注册
86
86
  */
87
87
  has(id: string): boolean;
88
+ /**
89
+ * 获取指定 id 的当前内容(用于测试或调试)
90
+ */
91
+ getContentById(id: string): string | undefined;
88
92
  /**
89
93
  * 启动 Bridge(幂等,已启动时直接返回)
90
94
  */
@@ -134,6 +134,15 @@ import { MarkdownEditor } from "../../MarkdownEditor";
134
134
  return this.registry.has(id);
135
135
  }
136
136
  },
137
+ {
138
+ /**
139
+ * 获取指定 id 的当前内容(用于测试或调试)
140
+ */ key: "getContentById",
141
+ value: function getContentById(id) {
142
+ var handler = this.registry.get(id);
143
+ return handler === null || handler === void 0 ? void 0 : handler.getContent();
144
+ }
145
+ },
137
146
  {
138
147
  key: "startBridge",
139
148
  value: /**
@@ -164,6 +164,10 @@ export var getRemoteMediaType = function getRemoteMediaType(url) {
164
164
  2,
165
165
  'other'
166
166
  ];
167
+ if (typeof url !== 'string') return [
168
+ 2,
169
+ 'other'
170
+ ];
167
171
  if (url.startsWith('data:')) {
168
172
  m = url.match(/data:image\/(\w+);base64,(.*)/);
169
173
  if (m) return [
@@ -175,10 +179,6 @@ export var getRemoteMediaType = function getRemoteMediaType(url) {
175
179
  'other'
176
180
  ];
177
181
  }
178
- if (typeof url !== 'string') return [
179
- 2,
180
- 'other'
181
- ];
182
182
  _state.label = 1;
183
183
  case 1:
184
184
  _state.trys.push([
@@ -1,8 +1,168 @@
1
- import { Eye, FileFailed, FileUploadingSpin } from "@sofa-design/icons";
1
+ function _array_like_to_array(arr, len) {
2
+ if (len == null || len > arr.length) len = arr.length;
3
+ for(var i = 0, arr2 = new Array(len); i < len; i++)arr2[i] = arr[i];
4
+ return arr2;
5
+ }
6
+ function _array_with_holes(arr) {
7
+ if (Array.isArray(arr)) return arr;
8
+ }
9
+ function _define_property(obj, key, value) {
10
+ if (key in obj) {
11
+ Object.defineProperty(obj, key, {
12
+ value: value,
13
+ enumerable: true,
14
+ configurable: true,
15
+ writable: true
16
+ });
17
+ } else {
18
+ obj[key] = value;
19
+ }
20
+ return obj;
21
+ }
22
+ function _iterable_to_array_limit(arr, i) {
23
+ var _i = arr == null ? null : typeof Symbol !== "undefined" && arr[Symbol.iterator] || arr["@@iterator"];
24
+ if (_i == null) return;
25
+ var _arr = [];
26
+ var _n = true;
27
+ var _d = false;
28
+ var _s, _e;
29
+ try {
30
+ for(_i = _i.call(arr); !(_n = (_s = _i.next()).done); _n = true){
31
+ _arr.push(_s.value);
32
+ if (i && _arr.length === i) break;
33
+ }
34
+ } catch (err) {
35
+ _d = true;
36
+ _e = err;
37
+ } finally{
38
+ try {
39
+ if (!_n && _i["return"] != null) _i["return"]();
40
+ } finally{
41
+ if (_d) throw _e;
42
+ }
43
+ }
44
+ return _arr;
45
+ }
46
+ function _non_iterable_rest() {
47
+ throw new TypeError("Invalid attempt to destructure non-iterable instance.\\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.");
48
+ }
49
+ function _object_spread(target) {
50
+ for(var i = 1; i < arguments.length; i++){
51
+ var source = arguments[i] != null ? arguments[i] : {};
52
+ var ownKeys = Object.keys(source);
53
+ if (typeof Object.getOwnPropertySymbols === "function") {
54
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
55
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
56
+ }));
57
+ }
58
+ ownKeys.forEach(function(key) {
59
+ _define_property(target, key, source[key]);
60
+ });
61
+ }
62
+ return target;
63
+ }
64
+ function ownKeys(object, enumerableOnly) {
65
+ var keys = Object.keys(object);
66
+ if (Object.getOwnPropertySymbols) {
67
+ var symbols = Object.getOwnPropertySymbols(object);
68
+ if (enumerableOnly) {
69
+ symbols = symbols.filter(function(sym) {
70
+ return Object.getOwnPropertyDescriptor(object, sym).enumerable;
71
+ });
72
+ }
73
+ keys.push.apply(keys, symbols);
74
+ }
75
+ return keys;
76
+ }
77
+ function _object_spread_props(target, source) {
78
+ source = source != null ? source : {};
79
+ if (Object.getOwnPropertyDescriptors) {
80
+ Object.defineProperties(target, Object.getOwnPropertyDescriptors(source));
81
+ } else {
82
+ ownKeys(Object(source)).forEach(function(key) {
83
+ Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key));
84
+ });
85
+ }
86
+ return target;
87
+ }
88
+ function _sliced_to_array(arr, i) {
89
+ return _array_with_holes(arr) || _iterable_to_array_limit(arr, i) || _unsupported_iterable_to_array(arr, i) || _non_iterable_rest();
90
+ }
91
+ function _unsupported_iterable_to_array(o, minLen) {
92
+ if (!o) return;
93
+ if (typeof o === "string") return _array_like_to_array(o, minLen);
94
+ var n = Object.prototype.toString.call(o).slice(8, -1);
95
+ if (n === "Object" && o.constructor) n = o.constructor.name;
96
+ if (n === "Map" || n === "Set") return Array.from(n);
97
+ if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _array_like_to_array(o, minLen);
98
+ }
99
+ import { Eye, FileFailed, FileUploadingSpin, Play } from "@sofa-design/icons";
2
100
  import { Image } from "antd";
3
- import React from "react";
101
+ import React, { useEffect, useState } from "react";
4
102
  import { getFileTypeIcon } from "../../../Workspace/File/utils";
5
- import { isImageFile } from "../utils";
103
+ import { isImageFile, isVideoFile } from "../utils";
104
+ var VideoThumbnail = function VideoThumbnail(param) {
105
+ var src = param.src, className = param.className, style = param.style;
106
+ return /*#__PURE__*/ React.createElement("div", {
107
+ className: className,
108
+ style: _object_spread_props(_object_spread({}, style), {
109
+ position: 'relative',
110
+ overflow: 'hidden',
111
+ borderRadius: 'var(--radius-base)',
112
+ flexShrink: 0
113
+ })
114
+ }, /*#__PURE__*/ React.createElement("video", {
115
+ src: src,
116
+ preload: "metadata",
117
+ muted: true,
118
+ playsInline: true,
119
+ style: {
120
+ width: '100%',
121
+ height: '100%',
122
+ objectFit: 'cover',
123
+ display: 'block'
124
+ }
125
+ }), /*#__PURE__*/ React.createElement("div", {
126
+ style: {
127
+ position: 'absolute',
128
+ inset: 0,
129
+ display: 'flex',
130
+ alignItems: 'center',
131
+ justifyContent: 'center',
132
+ background: 'rgba(0, 0, 0, 0.35)',
133
+ pointerEvents: 'none'
134
+ }
135
+ }, /*#__PURE__*/ React.createElement(Play, {
136
+ style: {
137
+ width: 24,
138
+ height: 24,
139
+ color: '#fff'
140
+ }
141
+ })));
142
+ };
143
+ /**
144
+ * 从 File 创建并使用 object URL 的视频缩略图,避免每次渲染都创建新 URL
145
+ */ var VideoThumbnailFromBlob = function VideoThumbnailFromBlob(param) {
146
+ var file = param.file, className = param.className, style = param.style;
147
+ var _useState = _sliced_to_array(useState(function() {
148
+ return null;
149
+ }), 2), objectUrl = _useState[0], setObjectUrl = _useState[1];
150
+ useEffect(function() {
151
+ var url = URL.createObjectURL(file);
152
+ setObjectUrl(url);
153
+ return function() {
154
+ URL.revokeObjectURL(url);
155
+ };
156
+ }, [
157
+ file
158
+ ]);
159
+ if (!objectUrl) return null;
160
+ return /*#__PURE__*/ React.createElement(VideoThumbnail, {
161
+ src: objectUrl,
162
+ className: className,
163
+ style: style
164
+ });
165
+ };
6
166
  /**
7
167
  * AttachmentFileIcon 组件 - 附件文件图标组件
8
168
  *
@@ -65,6 +225,25 @@ export var AttachmentFileIcon = function AttachmentFileIcon(props) {
65
225
  alt: file.name
66
226
  });
67
227
  }
228
+ // 视频文件缩略图预览(与图片类似,带播放按钮)
229
+ if (isVideoFile(file)) {
230
+ var videoUrl = file.previewUrl || file.url;
231
+ if (videoUrl) {
232
+ return /*#__PURE__*/ React.createElement(VideoThumbnail, {
233
+ src: videoUrl,
234
+ className: className,
235
+ style: IMAGE_STYLE
236
+ });
237
+ }
238
+ if (file.size) {
239
+ return /*#__PURE__*/ React.createElement(VideoThumbnailFromBlob, {
240
+ key: "".concat(file.name, "-").concat(file.size, "-").concat(file.lastModified || 0),
241
+ file: file,
242
+ className: className,
243
+ style: IMAGE_STYLE
244
+ });
245
+ }
246
+ }
68
247
  // 其他类型文件图标
69
248
  var fileType = (_file_type = file.type) === null || _file_type === void 0 ? void 0 : _file_type.split('/').at(-1);
70
249
  return getFileTypeIcon(fileType, '', file.name);
@@ -25,6 +25,18 @@ export declare const kbToSize: (kb: number) => string;
25
25
  * @returns {boolean} 是否为图片文件
26
26
  */
27
27
  export declare const isImageFile: (file: File) => boolean;
28
+ /**
29
+ * 检查文件是否为视频类型
30
+ * 通过 MIME 类型和文件扩展名双重判断
31
+ *
32
+ * @param {File} file - 要检查的文件(含 AttachmentFile)
33
+ * @returns {boolean} 是否为视频文件
34
+ */
35
+ export declare const isVideoFile: (file: File) => boolean;
36
+ /**
37
+ * 检查文件是否为可展示的媒体类型(图片或视频)
38
+ */
39
+ export declare const isMediaFile: (file: File) => boolean;
28
40
  /**
29
41
  * 获取设备品牌
30
42
  *
@@ -64,6 +64,48 @@
64
64
  return fileName.endsWith(ext);
65
65
  });
66
66
  };
67
+ var VIDEO_EXTENSIONS = [
68
+ '.mp4',
69
+ '.webm',
70
+ '.ogg',
71
+ '.ogv',
72
+ '.mov',
73
+ '.avi',
74
+ '.wmv',
75
+ '.flv',
76
+ '.m4v',
77
+ '.mkv'
78
+ ];
79
+ var hasVideoExtension = function hasVideoExtension(pathOrName) {
80
+ var lower = (pathOrName === null || pathOrName === void 0 ? void 0 : pathOrName.toLowerCase()) || '';
81
+ var beforeQuery = lower.split('?')[0];
82
+ return VIDEO_EXTENSIONS.some(function(ext) {
83
+ return beforeQuery.endsWith(ext);
84
+ });
85
+ };
86
+ /**
87
+ * 检查文件是否为视频类型
88
+ * 通过 MIME 类型和文件扩展名双重判断
89
+ *
90
+ * @param {File} file - 要检查的文件(含 AttachmentFile)
91
+ * @returns {boolean} 是否为视频文件
92
+ */ export var isVideoFile = function isVideoFile(file) {
93
+ var _file_type;
94
+ if ((_file_type = file.type) === null || _file_type === void 0 ? void 0 : _file_type.startsWith('video/')) {
95
+ return true;
96
+ }
97
+ if (hasVideoExtension(file.name)) {
98
+ return true;
99
+ }
100
+ var attachmentFile = file;
101
+ var url = attachmentFile.previewUrl || attachmentFile.url;
102
+ return !!url && hasVideoExtension(url);
103
+ };
104
+ /**
105
+ * 检查文件是否为可展示的媒体类型(图片或视频)
106
+ */ export var isMediaFile = function isMediaFile(file) {
107
+ return isImageFile(file) || isVideoFile(file);
108
+ };
67
109
  /**
68
110
  * 设备品牌匹配列表
69
111
  */ var UA_MATCH_LIST = [
@@ -185,12 +185,12 @@ function _ts_generator(thisArg, body) {
185
185
  };
186
186
  }
187
187
  }
188
- import { FileSearch } from "@sofa-design/icons";
189
- import { ConfigProvider, Image } from "antd";
188
+ import { FileSearch, Play } from "@sofa-design/icons";
189
+ import { ConfigProvider, Image, Modal } from "antd";
190
190
  import classNames from "clsx";
191
191
  import { motion } from "framer-motion";
192
192
  import React, { useContext, useMemo, useState } from "react";
193
- import { isImageFile } from "../AttachmentButton/utils";
193
+ import { isImageFile, isVideoFile } from "../AttachmentButton/utils";
194
194
  import { FileMapViewItem } from "./FileMapViewItem";
195
195
  import { useStyle } from "./style";
196
196
  /**
@@ -255,26 +255,48 @@ import { useStyle } from "./style";
255
255
  }, [
256
256
  fileList
257
257
  ]);
258
- // 所有非图片文件列表
259
- var allNoImageFiles = useMemo(function() {
258
+ // 视频列表,与图片一样以缩略图形式展示
259
+ var videoList = useMemo(function() {
260
260
  return fileList.filter(function(file) {
261
- return !isImageFile(file);
261
+ return isVideoFile(file);
262
262
  });
263
263
  }, [
264
264
  fileList
265
265
  ]);
266
- // 根据 maxDisplayCount 限制显示的非图片文件列表
267
- var noImageFileList = useMemo(function() {
268
- // 如果已展开所有文件,或者未设置最大显示数量,则显示所有文件
266
+ // 所有非图片、非视频文件列表
267
+ var allNoMediaFiles = useMemo(function() {
268
+ return fileList.filter(function(file) {
269
+ return !isImageFile(file) && !isVideoFile(file);
270
+ });
271
+ }, [
272
+ fileList
273
+ ]);
274
+ // 根据 maxDisplayCount 限制显示的非媒体文件列表
275
+ var noMediaFileList = useMemo(function() {
269
276
  if (showAllFiles || props.maxDisplayCount === undefined) {
270
- return allNoImageFiles;
277
+ return allNoMediaFiles;
271
278
  }
272
- return allNoImageFiles.slice(0, Math.max(0, props.maxDisplayCount));
279
+ return allNoMediaFiles.slice(0, Math.max(0, props.maxDisplayCount));
273
280
  }, [
274
- allNoImageFiles,
281
+ allNoMediaFiles,
275
282
  props.maxDisplayCount,
276
283
  showAllFiles
277
284
  ]);
285
+ var _useState1 = _sliced_to_array(useState(false), 2), videoModalOpen = _useState1[0], setVideoModalOpen = _useState1[1];
286
+ var _useState2 = _sliced_to_array(useState(null), 2), previewingVideo = _useState2[0], setPreviewingVideo = _useState2[1];
287
+ var handleVideoClick = function handleVideoClick(file) {
288
+ if (file.status === 'error') return;
289
+ if (props.onPreview) {
290
+ props.onPreview(file);
291
+ } else {
292
+ setPreviewingVideo(file);
293
+ setVideoModalOpen(true);
294
+ }
295
+ };
296
+ var handleVideoModalClose = function handleVideoModalClose() {
297
+ setVideoModalOpen(false);
298
+ setPreviewingVideo(null);
299
+ };
278
300
  var handleViewAllClick = function handleViewAllClick() {
279
301
  return _async_to_generator(function() {
280
302
  var shouldExpand;
@@ -350,7 +372,81 @@ import { useStyle } from "./style";
350
372
  src: file.previewUrl || file.url,
351
373
  key: file.uuid || file.name || index
352
374
  });
353
- }))), /*#__PURE__*/ React.createElement(motion.div, {
375
+ }))), videoList.length > 0 && /*#__PURE__*/ React.createElement(motion.div, {
376
+ variants: {
377
+ visible: {
378
+ opacity: 1
379
+ },
380
+ hidden: {
381
+ opacity: 0
382
+ }
383
+ },
384
+ whileInView: "visible",
385
+ initial: "hidden",
386
+ animate: "visible",
387
+ className: classNames("".concat(prefix, "-video-row"), "".concat(prefix, "-video-row-").concat(placement), hashId),
388
+ style: props.style
389
+ }, videoList.map(function(file, index) {
390
+ var videoUrl = file.previewUrl || file.url || '';
391
+ var isSingleVideo = videoList.length === 1;
392
+ var thumbSize = isSingleVideo ? {
393
+ width: 330,
394
+ height: 188
395
+ } : {
396
+ width: 124,
397
+ height: 124
398
+ };
399
+ return /*#__PURE__*/ React.createElement("div", {
400
+ role: "button",
401
+ tabIndex: 0,
402
+ className: classNames("".concat(prefix, "-image"), "".concat(prefix, "-video-thumb"), hashId),
403
+ key: file.uuid || file.name || index,
404
+ onClick: function onClick() {
405
+ return handleVideoClick(file);
406
+ },
407
+ onKeyDown: function onKeyDown(e) {
408
+ if (e.key === 'Enter' || e.key === ' ') {
409
+ e.preventDefault();
410
+ handleVideoClick(file);
411
+ }
412
+ },
413
+ "aria-label": "播放视频:".concat(file.name),
414
+ style: thumbSize
415
+ }, /*#__PURE__*/ React.createElement("video", {
416
+ src: videoUrl,
417
+ preload: "metadata",
418
+ muted: true,
419
+ playsInline: true,
420
+ style: {
421
+ width: '100%',
422
+ height: '100%',
423
+ objectFit: 'cover'
424
+ }
425
+ }), /*#__PURE__*/ React.createElement("div", {
426
+ className: classNames("".concat(prefix, "-video-play-overlay"), hashId),
427
+ "aria-hidden": true
428
+ }, /*#__PURE__*/ React.createElement(Play, null)));
429
+ })), /*#__PURE__*/ React.createElement(Modal, {
430
+ open: videoModalOpen,
431
+ onCancel: handleVideoModalClose,
432
+ footer: null,
433
+ width: "auto",
434
+ centered: true,
435
+ destroyOnClose: true,
436
+ styles: {
437
+ body: {
438
+ padding: 0
439
+ }
440
+ }
441
+ }, previewingVideo && /*#__PURE__*/ React.createElement("video", {
442
+ src: previewingVideo.previewUrl || previewingVideo.url,
443
+ controls: true,
444
+ autoPlay: true,
445
+ style: {
446
+ maxWidth: '80vw',
447
+ maxHeight: '80vh'
448
+ }
449
+ })), /*#__PURE__*/ React.createElement(motion.div, {
354
450
  variants: {
355
451
  visible: {
356
452
  opacity: 1,
@@ -371,7 +467,7 @@ import { useStyle } from "./style";
371
467
  animate: 'visible',
372
468
  className: classNames(prefix, hashId, props.className, "".concat(prefix, "-").concat(placement), "".concat(prefix, "-vertical")),
373
469
  style: props.style
374
- }, noImageFileList.map(function(file, index) {
470
+ }, noMediaFileList.map(function(file, index) {
375
471
  var _props_style;
376
472
  return /*#__PURE__*/ React.createElement(FileMapViewItem, {
377
473
  style: {
@@ -393,7 +489,7 @@ import { useStyle } from "./style";
393
489
  className: classNames(hashId, "".concat(prefix, "-item")),
394
490
  file: file
395
491
  });
396
- }), props.maxDisplayCount !== undefined && allNoImageFiles.length > props.maxDisplayCount && !showAllFiles ? /*#__PURE__*/ React.createElement("div", {
492
+ }), props.maxDisplayCount !== undefined && allNoMediaFiles.length > props.maxDisplayCount && !showAllFiles ? /*#__PURE__*/ React.createElement("div", {
397
493
  style: {
398
494
  width: (_props_style = props.style) === null || _props_style === void 0 ? void 0 : _props_style.width
399
495
  },
@@ -137,6 +137,51 @@ var genStyle = function genStyle(token) {
137
137
  transition: 'transform 0.3s'
138
138
  }
139
139
  }
140
+ }), _define_property(_obj, '&-video-row', {
141
+ display: 'flex',
142
+ flexWrap: 'wrap',
143
+ gap: 8,
144
+ width: 'fit-content',
145
+ '&-right': {
146
+ alignSelf: 'flex-end'
147
+ }
148
+ }), _define_property(_obj, '&-video-thumb', {
149
+ position: 'relative',
150
+ cursor: 'pointer',
151
+ opacity: 1,
152
+ background: 'var(--color-gray-bg-card-white)',
153
+ boxSizing: 'border-box',
154
+ boxShadow: 'var(--shadow-control-base)',
155
+ borderRadius: 'var(--radius-card-base)',
156
+ border: 'none',
157
+ overflow: 'hidden',
158
+ display: 'flex',
159
+ alignItems: 'center',
160
+ justifyContent: 'center',
161
+ '&:hover': {
162
+ boxShadow: 'var(--shadow-control-lg)',
163
+ '& video': {
164
+ transform: 'scale(1.05)'
165
+ }
166
+ },
167
+ '& video': {
168
+ transition: 'transform 0.3s'
169
+ }
170
+ }), _define_property(_obj, '&-video-play-overlay', {
171
+ position: 'absolute',
172
+ inset: 0,
173
+ display: 'flex',
174
+ alignItems: 'center',
175
+ justifyContent: 'center',
176
+ background: 'rgba(0, 0, 0, 0.35)',
177
+ borderRadius: 'inherit',
178
+ pointerEvents: 'none',
179
+ opacity: 0.9,
180
+ '& svg': {
181
+ width: 48,
182
+ height: 48,
183
+ color: '#fff'
184
+ }
140
185
  }), _define_property(_obj, '&-image-list-view', {
141
186
  background: 'var(--color-gray-bg-tip)',
142
187
  padding: '4px',
@@ -1,4 +1,19 @@
1
1
  import React from 'react';
2
+ import { LocalKeys } from '../I18n';
3
+ /**
4
+ * 将毫秒转换为可读的时间格式
5
+ *
6
+ * @param {number|undefined} ms - 毫秒数
7
+ * @param {LocalKeys} locale - 本地化配置
8
+ * @returns {string} 格式化后的时间字符串
9
+ *
10
+ * @example
11
+ * msToTimes(1500, locale) // "1.5s"
12
+ * msToTimes(65000, locale) // "1m 5s"
13
+ * msToTimes(3665000, locale) // "1H 1m 5s"
14
+ */
15
+ /** 导出供单测覆盖 undefined/null 分支 */
16
+ export declare const msToTimes: (ms: number | undefined | null, locale: LocalKeys) => string;
2
17
  /**
3
18
  * CostMillis 组件 - 耗时显示组件
4
19
  *
@@ -13,7 +13,7 @@ import { I18nContext } from "../I18n";
13
13
  * msToTimes(1500, locale) // "1.5s"
14
14
  * msToTimes(65000, locale) // "1m 5s"
15
15
  * msToTimes(3665000, locale) // "1H 1m 5s"
16
- */ var msToTimes = function msToTimes(ms, locale) {
16
+ */ /** 导出供单测覆盖 undefined/null 分支 */ export var msToTimes = function msToTimes(ms, locale) {
17
17
  if (ms === undefined || ms === null) {
18
18
  return '';
19
19
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ant-design/agentic-ui",
3
- "version": "2.29.26",
3
+ "version": "2.29.28",
4
4
  "description": "面向智能体的 UI 组件库,提供多步推理可视化、工具调用展示、任务执行协同等 Agentic UI 能力",
5
5
  "repository": "git@github.com:ant-design/agentic-ui.git",
6
6
  "license": "MIT",