one-design-next 0.0.11 → 0.0.13

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.
Files changed (55) hide show
  1. package/dist/_genui-types.d.ts +45 -10
  2. package/dist/attachments/index.js +67 -10
  3. package/dist/attachments/style/index.css +86 -14
  4. package/dist/collapse/primitive.d.ts +1 -1
  5. package/dist/composer/chip.d.ts +4 -4
  6. package/dist/composer/clipboard.d.ts +12 -0
  7. package/dist/composer/clipboard.js +84 -0
  8. package/dist/composer/editor.d.ts +9 -1
  9. package/dist/composer/editor.js +70 -14
  10. package/dist/composer/hooks/useChipManager.d.ts +6 -1
  11. package/dist/composer/hooks/useChipManager.js +76 -27
  12. package/dist/composer/hooks/useChipSelectionMarker.d.ts +7 -0
  13. package/dist/composer/hooks/useChipSelectionMarker.js +66 -0
  14. package/dist/composer/index.d.ts +58 -1
  15. package/dist/composer/index.js +219 -102
  16. package/dist/composer/inline-ref.d.ts +9 -0
  17. package/dist/composer/inline-ref.js +21 -0
  18. package/dist/composer/send-meta.d.ts +6 -0
  19. package/dist/composer/send-meta.js +67 -0
  20. package/dist/composer/style/index.css +48 -53
  21. package/dist/composer/utils.d.ts +9 -0
  22. package/dist/composer/utils.js +43 -2
  23. package/dist/fab/index.js +3 -16
  24. package/dist/fab/style/index.css +1 -3
  25. package/dist/icon/svg-data.d.ts +4 -0
  26. package/dist/icon/svg-data.js +1 -1
  27. package/dist/icon/types.d.ts +1 -1
  28. package/dist/image/index.d.ts +43 -0
  29. package/dist/image/index.js +51 -0
  30. package/dist/image/style/index.css +59 -0
  31. package/dist/image/style/index.d.ts +2 -0
  32. package/dist/image/style/index.js +2 -0
  33. package/dist/index.d.ts +5 -1
  34. package/dist/index.js +4 -0
  35. package/dist/invocation/index.d.ts +17 -0
  36. package/dist/invocation/index.js +84 -0
  37. package/dist/invocation/style/index.css +58 -0
  38. package/dist/invocation/style/index.d.ts +2 -0
  39. package/dist/invocation/style/index.js +2 -0
  40. package/dist/mention/index.d.ts +17 -0
  41. package/dist/mention/index.js +90 -0
  42. package/dist/mention/style/index.css +58 -0
  43. package/dist/mention/style/index.d.ts +2 -0
  44. package/dist/mention/style/index.js +2 -0
  45. package/dist/message-image/index.d.ts +42 -0
  46. package/dist/message-image/index.js +46 -0
  47. package/dist/message-image/style/index.css +60 -0
  48. package/dist/message-image/style/index.d.ts +2 -0
  49. package/dist/message-image/style/index.js +2 -0
  50. package/dist/preview-panel/index.d.ts +5 -1
  51. package/dist/preview-panel/index.js +27 -2
  52. package/dist/user-bubble/index.d.ts +11 -1
  53. package/dist/user-bubble/index.js +30 -5
  54. package/dist/user-bubble/style/index.css +6 -0
  55. package/package.json +2 -3
@@ -13,12 +13,14 @@ function _arrayWithHoles(arr) { if (Array.isArray(arr)) return arr; }
13
13
  import { useState, useRef, useCallback, useEffect, useLayoutEffect, useMemo, forwardRef, useImperativeHandle } from 'react';
14
14
  import HoverFill from "../hover-fill";
15
15
  import Icon from "../icon";
16
+ import Image from "../image";
16
17
  import Popover from "../popover";
17
18
  import Attachments from "../attachments";
18
19
  import { Message } from "../message";
19
20
  import SkillSlot from "../skill-slot";
20
21
  import useControlledState from "../_util/useControlledState";
21
22
  import { ComposerEditor } from "./editor";
23
+ import { buildSendMetaFromChips } from "./send-meta";
22
24
  import { stripMarkers } from "./utils";
23
25
  import "./style";
24
26
  var TOOL_BTN_ACTIVE = 'color-mix(in srgb, var(--odn-color-cyan-5) 10%, transparent)';
@@ -32,6 +34,8 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
32
34
  onStop = _ref.onStop,
33
35
  _ref$isGenerating = _ref.isGenerating,
34
36
  isGenerating = _ref$isGenerating === void 0 ? false : _ref$isGenerating,
37
+ _ref$disabled = _ref.disabled,
38
+ disabled = _ref$disabled === void 0 ? false : _ref$disabled,
35
39
  _ref$placeholder = _ref.placeholder,
36
40
  placeholder = _ref$placeholder === void 0 ? '发送消息…' : _ref$placeholder,
37
41
  _ref$spacing = _ref.spacing,
@@ -48,57 +52,67 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
48
52
  _ref$submitType = _ref.submitType,
49
53
  submitType = _ref$submitType === void 0 ? 'enter' : _ref$submitType,
50
54
  onEnter = _ref.onEnter,
55
+ attachmentsProp = _ref.attachments,
56
+ onAttachmentsChange = _ref.onAttachmentsChange,
57
+ onAttachFiles = _ref.onAttachFiles,
58
+ onAttachmentRemove = _ref.onAttachmentRemove,
59
+ _ref$attachmentsDisab = _ref.attachmentsDisabled,
60
+ attachmentsDisabled = _ref$attachmentsDisab === void 0 ? false : _ref$attachmentsDisab,
61
+ _ref$maxFileCount = _ref.maxFileCount,
62
+ maxFileCount = _ref$maxFileCount === void 0 ? 5 : _ref$maxFileCount,
63
+ _ref$maxFileSize = _ref.maxFileSize,
64
+ maxFileSize = _ref$maxFileSize === void 0 ? 20 * 1024 * 1024 : _ref$maxFileSize,
51
65
  className = _ref.className,
52
66
  style = _ref.style;
53
67
  var _useControlledState = useControlledState(defaultValue, valueProp),
54
68
  _useControlledState2 = _slicedToArray(_useControlledState, 2),
55
69
  text = _useControlledState2[0],
56
70
  setText = _useControlledState2[1];
57
- var _useState = useState([]),
71
+ var _useControlledState3 = useControlledState([], attachmentsProp),
72
+ _useControlledState4 = _slicedToArray(_useControlledState3, 2),
73
+ attachments = _useControlledState4[0],
74
+ setAttachments = _useControlledState4[1];
75
+ var _useState = useState(false),
58
76
  _useState2 = _slicedToArray(_useState, 2),
59
- attachments = _useState2[0],
60
- setAttachments = _useState2[1];
61
- var _useState3 = useState(false),
62
- _useState4 = _slicedToArray(_useState3, 2),
63
- webSearch = _useState4[0],
64
- setWebSearch = _useState4[1];
77
+ webSearch = _useState2[0],
78
+ setWebSearch = _useState2[1];
65
79
  /**
66
80
  * 工具栏「技能」按钮触发的 menu 显隐。
67
81
  * 跟 trigger 触发的 menu 共用 SkillSlot 渲染,但 anchor / 关闭路径不同。
68
82
  */
69
- var _useState5 = useState(false),
70
- _useState6 = _slicedToArray(_useState5, 2),
71
- toolMenuOpen = _useState6[0],
72
- setToolMenuOpen = _useState6[1];
83
+ var _useState3 = useState(false),
84
+ _useState4 = _slicedToArray(_useState3, 2),
85
+ toolMenuOpen = _useState4[0],
86
+ setToolMenuOpen = _useState4[1];
73
87
  /**
74
88
  * editor 内 `@` / `/` 触发态的实时信号。null 表示无活动 trigger。
75
89
  * 由 editor 的 onTriggerChange 写入;选定 chip 或 cancelTrigger 后置 null。
76
90
  */
77
- var _useState7 = useState(null),
78
- _useState8 = _slicedToArray(_useState7, 2),
79
- triggerInfo = _useState8[0],
80
- setTriggerInfo = _useState8[1];
91
+ var _useState5 = useState(null),
92
+ _useState6 = _slicedToArray(_useState5, 2),
93
+ triggerInfo = _useState6[0],
94
+ setTriggerInfo = _useState6[1];
81
95
  /** trigger 浮层内键盘高亮项 index;query 变化或 menu 关闭时复位 0。 */
82
- var _useState9 = useState(0),
83
- _useState10 = _slicedToArray(_useState9, 2),
84
- triggerActiveIndex = _useState10[0],
85
- setTriggerActiveIndex = _useState10[1];
96
+ var _useState7 = useState(0),
97
+ _useState8 = _slicedToArray(_useState7, 2),
98
+ triggerActiveIndex = _useState8[0],
99
+ setTriggerActiveIndex = _useState8[1];
86
100
  /** lite 模式下由单行升格到多行布局;粘性:只在 text 清空时复位 */
87
- var _useState11 = useState(false),
88
- _useState12 = _slicedToArray(_useState11, 2),
89
- expanded = _useState12[0],
90
- setExpanded = _useState12[1];
101
+ var _useState9 = useState(false),
102
+ _useState10 = _slicedToArray(_useState9, 2),
103
+ expanded = _useState10[0],
104
+ setExpanded = _useState10[1];
91
105
  /** ComposerEditor 反馈的 editor 自然高度(offsetHeight,未 clamp),用于 lite 升格判断 */
92
- var _useState13 = useState(0),
93
- _useState14 = _slicedToArray(_useState13, 2),
94
- editorHeight = _useState14[0],
95
- setEditorHeight = _useState14[1];
106
+ var _useState11 = useState(0),
107
+ _useState12 = _slicedToArray(_useState11, 2),
108
+ editorHeight = _useState12[0],
109
+ setEditorHeight = _useState12[1];
96
110
  /** editor 内容是否真正达到 maxRows 触顶——配合 SCSS 控制 viewport overflow,
97
111
  * 阻止升格 transition / 换行未触顶时的 scrollbar 闪现 */
98
- var _useState15 = useState(false),
99
- _useState16 = _slicedToArray(_useState15, 2),
100
- atMax = _useState16[0],
101
- setAtMax = _useState16[1];
112
+ var _useState13 = useState(false),
113
+ _useState14 = _slicedToArray(_useState13, 2),
114
+ atMax = _useState14[0],
115
+ setAtMax = _useState14[1];
102
116
  var fileInputRef = useRef(null);
103
117
  var editorRef = useRef(null);
104
118
 
@@ -127,7 +141,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
127
141
  skillId: skill.id,
128
142
  label: skill.label,
129
143
  icon: skill.icon,
130
- kind: 'skill'
144
+ kind: 'invocation'
131
145
  };
132
146
  (_editorRef$current4 = editorRef.current) === null || _editorRef$current4 === void 0 || _editorRef$current4.insertChip(data);
133
147
  (_editorRef$current5 = editorRef.current) === null || _editorRef$current5 === void 0 || _editorRef$current5.focus();
@@ -143,17 +157,34 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
143
157
  onChangeProp === null || onChangeProp === void 0 || onChangeProp(v);
144
158
  }, [setText, onChangeProp]);
145
159
 
146
- /* ---- Revoke blob URLs on unmount ---- */
147
- var attachmentsRef = useRef(attachments);
148
- attachmentsRef.current = attachments;
160
+ /* ---- Attachments controlled writer ----
161
+ * useControlledState 只做"value vs innerValue"合流;onAttachmentsChange 需要手动派发。
162
+ * 模式与上面 updateText 一致:内部 set + 对外 callback。 */
163
+ var updateAttachments = useCallback(function (next) {
164
+ setAttachments(next);
165
+ onAttachmentsChange === null || onAttachmentsChange === void 0 || onAttachmentsChange(next);
166
+ }, [setAttachments, onAttachmentsChange]);
167
+
168
+ /* ---- 内部生成的 blob url 记账 ----
169
+ * 仅"非受控 + 未传 onAttachFiles"路径会 createObjectURL;这些 url 由我们负责 revoke。
170
+ * 受控模式下,attachments 里的 url 归业务方所有(如企点 / OSS 云端 url),不能被组件 revoke。
171
+ * 用 Set 当账本,删除/卸载时按 has 判断是否动手。 */
172
+ var internalBlobUrlsRef = useRef(new Set());
149
173
  useEffect(function () {
150
174
  return function () {
151
- attachmentsRef.current.forEach(function (a) {
152
- if (a.url) URL.revokeObjectURL(a.url);
175
+ internalBlobUrlsRef.current.forEach(function (url) {
176
+ return URL.revokeObjectURL(url);
153
177
  });
178
+ internalBlobUrlsRef.current.clear();
154
179
  };
155
180
  }, []);
156
181
 
182
+ /* ---- 受控模式校验:受控但没接 onAttachFiles 时,组件无路径塞文件进 attachments ---- */
183
+ if (process.env.NODE_ENV !== 'production' && attachmentsProp !== undefined && !onAttachFiles) {
184
+ // eslint-disable-next-line no-console
185
+ console.warn('[Composer] 检测到受控 attachments,但未提供 onAttachFiles。' + '内置文件入口(按钮 / 拖入 / 粘贴)将无法把文件写入 attachments。' + '若需让用户继续添加附件,请传入 onAttachFiles 接管 File → Attachment 转换。');
186
+ }
187
+
157
188
  /* ---- lite 升格判断 ----
158
189
  * 基准 32px = lite 单行 SCSS 锁定的 min-height(与 ComposerEditor minRows=1
159
190
  * 算出的 minHeightPx 一致)。直接用常量当基准,不依赖 ResizeObserver 第一次
@@ -181,34 +212,45 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
181
212
  /* ---- Send ----
182
213
  * 内部 text 是 raw value(chip 序列化为不可见 marker 字符)。对外 onSend:
183
214
  * - text 给 stripMarkers 之后的纯文本(旧业务收到的字符串保持纯净)
184
- * - chips 从 editor.getChips() 拿,按 DOM 顺序,业务方按 kind 自行分类
185
- * (BREAKING from v0.0.x:取代 meta.skill 单值字段) */
215
+ * - invocations / mentions 从 editor.getChips() buildSendMetaFromChips 拆分;
216
+ * chips 仍双写(deprecated)
217
+ *
218
+ * 发送后内部状态全部清空:text、attachments、webSearch——与 ChatGPT / Claude /
219
+ * iMessage 等业界对话产品一致。attachments 不 revoke blob URL:业务方在 onSend
220
+ * 回调里可能仍持有 url(消息记录、本地预览等),由消费方负责回收。 */
186
221
  var handleSend = useCallback(function () {
187
- var _editorRef$current$ge, _editorRef$current7;
222
+ var _editorRef$current$ge, _editorRef$current7, _editorRef$current8;
223
+ if (disabled) return;
188
224
  var plain = stripMarkers(text).trim();
189
225
  if (!plain && attachments.length === 0) return;
190
226
  var chips = (_editorRef$current$ge = (_editorRef$current7 = editorRef.current) === null || _editorRef$current7 === void 0 ? void 0 : _editorRef$current7.getChips()) !== null && _editorRef$current$ge !== void 0 ? _editorRef$current$ge : [];
191
- var meta = {
227
+ var meta = buildSendMetaFromChips(chips, {
192
228
  attachments: attachments.length > 0 ? attachments : undefined,
193
- webSearch: webSearch || undefined,
194
- chips: chips.length > 0 ? chips : undefined
195
- };
229
+ webSearch: webSearch || undefined
230
+ });
196
231
  onSend(plain, meta);
232
+ (_editorRef$current8 = editorRef.current) === null || _editorRef$current8 === void 0 || _editorRef$current8.clear();
233
+ setAttachments([]);
234
+ setWebSearch(false);
197
235
  }, [text, attachments, webSearch, onSend]);
198
236
 
199
- /* ---- File upload ---- */
200
- var MAX_FILE_SIZE = 20 * 1024 * 1024;
201
- var MAX_FILE_COUNT = 5;
202
-
203
- /** 从选择器 / 粘贴 / 拖放等入口并入附件,沿用与 file input 相同的数量与体积规则。 */
237
+ /* ---- File upload ----
238
+ * 三入口(按钮 / 拖入 / 粘贴)共用此函数。先做内置校验(数量、单文件体积、重名),
239
+ * 再分两条路径派发:
240
+ * - 传了 onAttachFiles:把通过校验的原生 File[] 交给业务方,组件不再 createObjectURL,
241
+ * 也不入栈。业务方负责上传并通过 onAttachmentsChange 回写 attachments。
242
+ * - 未传 onAttachFiles:沿用旧路径(createObjectURL → updateAttachments),保持
243
+ * demo 与历史业务零改动。生成的 blob url 记入 internalBlobUrlsRef,仅这些会被
244
+ * 组件 revoke。 */
204
245
  var addFilesFromList = useCallback(function (fileArray) {
246
+ if (disabled || attachmentsDisabled) return;
205
247
  if (fileArray.length === 0) return;
206
- var remaining = MAX_FILE_COUNT - attachments.length;
248
+ var remaining = maxFileCount - attachments.length;
207
249
  if (remaining <= 0) {
208
- Message.warning('至多支持添加 5 个文件');
250
+ Message.warning("\u81F3\u591A\u652F\u6301\u6DFB\u52A0 ".concat(maxFileCount, " \u4E2A\u6587\u4EF6"));
209
251
  return;
210
252
  }
211
- var accepted = [];
253
+ var acceptedFiles = [];
212
254
  var oversized = 0;
213
255
  var duplicated = 0;
214
256
  var overCount = 0;
@@ -217,28 +259,23 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
217
259
  try {
218
260
  var _loop = function _loop() {
219
261
  var f = _step.value;
220
- if (accepted.length >= remaining) {
262
+ if (acceptedFiles.length >= remaining) {
221
263
  overCount++;
222
264
  return 0; // continue
223
265
  }
224
- if (f.size > MAX_FILE_SIZE) {
266
+ if (f.size > maxFileSize) {
225
267
  oversized++;
226
268
  return 0; // continue
227
269
  }
228
270
  if (attachments.some(function (a) {
229
271
  return a.name === f.name && a.size === f.size;
230
- }) || accepted.some(function (a) {
231
- return a.name === f.name && a.size === f.size;
272
+ }) || acceptedFiles.some(function (af) {
273
+ return af.name === f.name && af.size === f.size;
232
274
  })) {
233
275
  duplicated++;
234
276
  return 0; // continue
235
277
  }
236
- accepted.push({
237
- name: f.name,
238
- type: f.type,
239
- size: f.size,
240
- url: URL.createObjectURL(f)
241
- });
278
+ acceptedFiles.push(f);
242
279
  },
243
280
  _ret;
244
281
  for (_iterator.s(); !(_step = _iterator.n()).done;) {
@@ -250,32 +287,48 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
250
287
  } finally {
251
288
  _iterator.f();
252
289
  }
253
- if (oversized > 0) Message.warning('文件大小至多为 20MB');
290
+ var sizeLabel = "".concat(Math.round(maxFileSize / 1024 / 1024), "MB");
291
+ if (oversized > 0) Message.warning("\u6587\u4EF6\u5927\u5C0F\u81F3\u591A\u4E3A ".concat(sizeLabel));
254
292
  if (duplicated > 0) Message.warning('请不要重复添加文件');
255
- if (overCount > 0) Message.warning('至多支持添加 5 个文件');
256
- if (accepted.length > 0) setAttachments(function (prev) {
257
- return [].concat(_toConsumableArray(prev), accepted);
293
+ if (overCount > 0) Message.warning("\u81F3\u591A\u652F\u6301\u6DFB\u52A0 ".concat(maxFileCount, " \u4E2A\u6587\u4EF6"));
294
+ if (acceptedFiles.length === 0) return;
295
+ if (onAttachFiles) {
296
+ onAttachFiles(acceptedFiles);
297
+ return;
298
+ }
299
+ var accepted = acceptedFiles.map(function (f) {
300
+ var url = URL.createObjectURL(f);
301
+ internalBlobUrlsRef.current.add(url);
302
+ return {
303
+ name: f.name,
304
+ type: f.type,
305
+ size: f.size,
306
+ url: url
307
+ };
258
308
  });
259
- }, [attachments]);
309
+ updateAttachments([].concat(_toConsumableArray(attachments), _toConsumableArray(accepted)));
310
+ }, [attachments, attachmentsDisabled, disabled, maxFileCount, maxFileSize, onAttachFiles, updateAttachments]);
260
311
  var handleFileChange = function handleFileChange(e) {
261
312
  var files = e.target.files;
262
313
  if (!files) return;
263
314
  addFilesFromList(Array.from(files));
264
315
  e.target.value = '';
265
316
  };
266
- var _useState17 = useState(false),
267
- _useState18 = _slicedToArray(_useState17, 2),
268
- fileDragOver = _useState18[0],
269
- setFileDragOver = _useState18[1];
317
+ var _useState15 = useState(false),
318
+ _useState16 = _slicedToArray(_useState15, 2),
319
+ fileDragOver = _useState16[0],
320
+ setFileDragOver = _useState16[1];
270
321
  var composerBoxFileDragAttrs = fileDragOver ? {
271
322
  'data-odn-composer-file-drag': ''
272
323
  } : {};
273
324
  var handleComposerDragEnter = function handleComposerDragEnter(e) {
274
325
  e.preventDefault();
326
+ if (disabled || attachmentsDisabled) return;
275
327
  if (!_toConsumableArray(e.dataTransfer.types).includes('Files')) return;
276
328
  setFileDragOver(true);
277
329
  };
278
330
  var handleComposerDragOver = function handleComposerDragOver(e) {
331
+ if (disabled || attachmentsDisabled) return;
279
332
  if (!_toConsumableArray(e.dataTransfer.types).includes('Files')) return;
280
333
  e.preventDefault();
281
334
  e.dataTransfer.dropEffect = 'copy';
@@ -288,6 +341,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
288
341
  var _dt$files;
289
342
  e.preventDefault();
290
343
  setFileDragOver(false);
344
+ if (disabled || attachmentsDisabled) return;
291
345
  var dt = e.dataTransfer;
292
346
  if (!((_dt$files = dt.files) !== null && _dt$files !== void 0 && _dt$files.length)) return;
293
347
  addFilesFromList(Array.from(dt.files));
@@ -303,17 +357,17 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
303
357
  skillId: skill.id,
304
358
  label: skill.label,
305
359
  icon: skill.icon,
306
- kind: 'skill'
360
+ kind: 'invocation'
307
361
  };
308
362
  if (source === 'trigger') {
309
- var _editorRef$current8;
310
- (_editorRef$current8 = editorRef.current) === null || _editorRef$current8 === void 0 || _editorRef$current8.replaceTriggerWithChip(data);
363
+ var _editorRef$current9;
364
+ (_editorRef$current9 = editorRef.current) === null || _editorRef$current9 === void 0 || _editorRef$current9.replaceTriggerWithChip(data);
311
365
  setTriggerInfo(null);
312
366
  } else {
313
- var _editorRef$current9, _editorRef$current10;
314
- (_editorRef$current9 = editorRef.current) === null || _editorRef$current9 === void 0 || _editorRef$current9.insertChip(data);
367
+ var _editorRef$current10, _editorRef$current11;
368
+ (_editorRef$current10 = editorRef.current) === null || _editorRef$current10 === void 0 || _editorRef$current10.insertChip(data);
315
369
  setToolMenuOpen(false);
316
- (_editorRef$current10 = editorRef.current) === null || _editorRef$current10 === void 0 || _editorRef$current10.focus();
370
+ (_editorRef$current11 = editorRef.current) === null || _editorRef$current11 === void 0 || _editorRef$current11.focus();
317
371
  }
318
372
  }, []);
319
373
 
@@ -363,9 +417,9 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
363
417
  return true;
364
418
  }
365
419
  if (e.key === 'Escape') {
366
- var _editorRef$current11;
420
+ var _editorRef$current12;
367
421
  e.preventDefault();
368
- (_editorRef$current11 = editorRef.current) === null || _editorRef$current11 === void 0 || _editorRef$current11.cancelTrigger();
422
+ (_editorRef$current12 = editorRef.current) === null || _editorRef$current12 === void 0 || _editorRef$current12.cancelTrigger();
369
423
  setTriggerInfo(null);
370
424
  return true;
371
425
  }
@@ -396,22 +450,31 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
396
450
  console.warn("[Composer] variant=\"lite\" \u4EC5\u652F\u6301\u4E00\u4E2A\u5DE5\u5177\u6309\u94AE\uFF0C\u5DF2\u5FFD\u7565\u5176\u4F59 ".concat(tools.length - 1, " \u4E2A\u3002\u82E5\u9700\u8981\u591A\u4E2A\u5DE5\u5177\u6309\u94AE\uFF0C\u8BF7\u4F7F\u7528 variant=\"full\"\u3002"));
397
451
  }
398
452
 
399
- /* ---- Tool buttons ---- */
453
+ /* ---- Tool buttons ----
454
+ * disabled(整体禁用)合并到各按钮的 disabled 状态:
455
+ * - attachment:disabled || attachmentsDisabled
456
+ * - webSearch / skill:disabled(attachmentsDisabled 不影响这两个)
457
+ * skill 的 Popover 在 disabled 时禁用 trigger,避免弹出菜单。 */
400
458
  var renderAttachmentBtn = function renderAttachmentBtn() {
459
+ var btnDisabled = disabled || attachmentsDisabled;
401
460
  return /*#__PURE__*/React.createElement(HoverFill, {
402
461
  key: "attachment",
403
462
  "data-odn-composer-tool-btn-wrap": true,
404
463
  bgClassName: "odn-composer-tool-btn-bg",
405
- activeColor: TOOL_BTN_ACTIVE
464
+ activeColor: TOOL_BTN_ACTIVE,
465
+ disabled: btnDisabled
406
466
  }, /*#__PURE__*/React.createElement("button", {
407
467
  type: "button",
408
468
  onClick: function onClick() {
409
469
  var _fileInputRef$current;
410
470
  return (_fileInputRef$current = fileInputRef.current) === null || _fileInputRef$current === void 0 ? void 0 : _fileInputRef$current.click();
411
471
  },
472
+ disabled: btnDisabled,
412
473
  "data-odn-composer-tool-btn": true,
413
474
  "data-odn-composer-tool-iconic": !effectiveLabeled || undefined,
475
+ "data-odn-composer-tool-disabled": btnDisabled || undefined,
414
476
  "aria-label": "\u9644\u4EF6",
477
+ "aria-disabled": btnDisabled || undefined,
415
478
  title: effectiveLabeled ? undefined : '附件'
416
479
  }, /*#__PURE__*/React.createElement(Icon, {
417
480
  name: "paperclip",
@@ -424,7 +487,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
424
487
  "data-odn-composer-tool-btn-wrap": true,
425
488
  bgClassName: "odn-composer-tool-btn-bg",
426
489
  activeColor: TOOL_BTN_ACTIVE,
427
- disabled: webSearch
490
+ disabled: disabled || webSearch
428
491
  }, /*#__PURE__*/React.createElement("button", {
429
492
  type: "button",
430
493
  onClick: function onClick() {
@@ -432,11 +495,14 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
432
495
  return !v;
433
496
  });
434
497
  },
498
+ disabled: disabled,
435
499
  "data-odn-composer-tool-btn": true,
436
500
  "data-odn-composer-tool-iconic": !effectiveLabeled || undefined,
437
501
  "data-odn-composer-tool-active": webSearch || undefined,
502
+ "data-odn-composer-tool-disabled": disabled || undefined,
438
503
  "aria-label": "\u8054\u7F51",
439
504
  "aria-pressed": webSearch,
505
+ "aria-disabled": disabled || undefined,
440
506
  title: effectiveLabeled ? undefined : '联网'
441
507
  }, /*#__PURE__*/React.createElement(Icon, {
442
508
  name: "globe",
@@ -451,8 +517,11 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
451
517
  arrowed: false,
452
518
  placement: "topLeft",
453
519
  offset: 4,
454
- visible: toolMenuOpen,
455
- onVisibleChange: setToolMenuOpen,
520
+ visible: !disabled && toolMenuOpen,
521
+ onVisibleChange: function onVisibleChange(v) {
522
+ if (disabled) return;
523
+ setToolMenuOpen(v);
524
+ },
456
525
  popupClassName: "odn-composer-skill-popup",
457
526
  popup: /*#__PURE__*/React.createElement(SkillSlot, {
458
527
  variant: "menu",
@@ -465,13 +534,16 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
465
534
  "data-odn-composer-tool-btn-wrap": true,
466
535
  bgClassName: "odn-composer-tool-btn-bg",
467
536
  activeColor: TOOL_BTN_ACTIVE,
468
- disabled: toolMenuOpen
537
+ disabled: disabled || toolMenuOpen
469
538
  }, /*#__PURE__*/React.createElement("button", {
470
539
  type: "button",
540
+ disabled: disabled,
471
541
  "data-odn-composer-tool-btn": true,
472
542
  "data-odn-composer-tool-iconic": !effectiveLabeled || undefined,
473
543
  "data-odn-composer-tool-active": toolMenuOpen || undefined,
544
+ "data-odn-composer-tool-disabled": disabled || undefined,
474
545
  "aria-label": "\u6280\u80FD",
546
+ "aria-disabled": disabled || undefined,
475
547
  title: effectiveLabeled ? undefined : '技能'
476
548
  }, /*#__PURE__*/React.createElement(Icon, {
477
549
  name: "puzzle",
@@ -486,22 +558,64 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
486
558
  }).filter(Boolean);
487
559
  var hasTools = toolNodes.length > 0;
488
560
 
489
- /* ---- Slots ---- */
561
+ /* ---- Slots ----
562
+ * 待发送区按 type 分流:
563
+ * - 图片(image/* + url)→ Image leaf + 72×72 cover wrap,输入态用模板
564
+ * 驱动尺寸(整齐划一比保留长宽比要紧)
565
+ * - 其他附件 → 走 Attachments,保持原 file-card 渲染
566
+ *
567
+ * 删除:按 internalBlobUrlsRef 判断是否需要 revoke,避免误 revoke 业务方
568
+ * 的云端 url;同时派发 onAttachmentRemove(语义钩子,便于业务调"服务端
569
+ * 删文件")与 onAttachmentsChange。
570
+ *
571
+ * 分流子集与原 attachments 的 index 不一致,对外回调(onAttachmentRemove)
572
+ * 用 attachments.indexOf 拿到原始 index,保持业务方语义。 */
573
+ var isImageAttachment = function isImageAttachment(a) {
574
+ return a.type.startsWith('image/') && !!a.url;
575
+ };
576
+ var composerImages = attachments.filter(isImageAttachment);
577
+ var composerFiles = attachments.filter(function (a) {
578
+ return !isImageAttachment(a);
579
+ });
580
+ var removeAttachmentAt = function removeAttachmentAt(i) {
581
+ var removed = attachments[i];
582
+ if (removed !== null && removed !== void 0 && removed.url && internalBlobUrlsRef.current.has(removed.url)) {
583
+ URL.revokeObjectURL(removed.url);
584
+ internalBlobUrlsRef.current.delete(removed.url);
585
+ }
586
+ var next = attachments.filter(function (_, idx) {
587
+ return idx !== i;
588
+ });
589
+ if (removed) onAttachmentRemove === null || onAttachmentRemove === void 0 || onAttachmentRemove(i, removed);
590
+ updateAttachments(next);
591
+ };
490
592
  var attachmentsSlot = attachments.length > 0 ? /*#__PURE__*/React.createElement("div", {
491
593
  "data-odn-composer-attachments": true
492
- }, /*#__PURE__*/React.createElement(Attachments, {
493
- attachments: attachments,
594
+ }, composerImages.length > 0 && /*#__PURE__*/React.createElement("div", {
595
+ "data-odn-composer-images": true
596
+ }, composerImages.map(function (img) {
597
+ var realIdx = attachments.indexOf(img);
598
+ return /*#__PURE__*/React.createElement("div", {
599
+ key: "".concat(img.name, "-").concat(realIdx),
600
+ "data-odn-composer-image-thumb": true
601
+ }, /*#__PURE__*/React.createElement(Image, {
602
+ url: img.url,
603
+ alt: img.name,
604
+ onRemove: function onRemove() {
605
+ return removeAttachmentAt(realIdx);
606
+ }
607
+ }));
608
+ })), composerFiles.length > 0 && /*#__PURE__*/React.createElement(Attachments, {
609
+ attachments: composerFiles,
494
610
  onRemove: function onRemove(i) {
495
- var removed = attachments[i];
496
- if (removed !== null && removed !== void 0 && removed.url) URL.revokeObjectURL(removed.url);
497
- setAttachments(function (prev) {
498
- return prev.filter(function (_, idx) {
499
- return idx !== i;
500
- });
501
- });
611
+ return removeAttachmentAt(attachments.indexOf(composerFiles[i]));
502
612
  }
503
613
  })) : null;
504
- var sendOrStopButton = isGenerating ? /*#__PURE__*/React.createElement("button", {
614
+
615
+ /* 状态优先级:disabled > isGenerating > canSend
616
+ * disabled 时永远呈现发送按钮(仅置灰),不切到中止——
617
+ * 因为整体禁用语义包含"现在什么都不能做",包括中断。 */
618
+ var sendOrStopButton = !disabled && isGenerating ? /*#__PURE__*/React.createElement("button", {
505
619
  type: "button",
506
620
  onClick: onStop,
507
621
  "data-odn-composer-stop-btn": true,
@@ -512,10 +626,10 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
512
626
  })) : /*#__PURE__*/React.createElement("button", {
513
627
  type: "button",
514
628
  onClick: handleSend,
515
- disabled: !canSend,
629
+ disabled: disabled || !canSend,
516
630
  "data-odn-composer-send-btn": true,
517
- "data-odn-composer-send-disabled": !canSend || undefined,
518
- title: "\u53D1\u9001"
631
+ "data-odn-composer-send-disabled": disabled || !canSend || undefined,
632
+ title: disabled ? undefined : '发送'
519
633
  }, /*#__PURE__*/React.createElement(Icon, {
520
634
  name: "arrow-up",
521
635
  size: 18
@@ -549,7 +663,8 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
549
663
  },
550
664
  placeholder: placeholder,
551
665
  value: text,
552
- onChange: updateText
666
+ onChange: updateText,
667
+ disabled: disabled
553
668
  /* 本轮只接 `/`,`@` 留给业务接 mention 搜索时再加 */,
554
669
  triggers: ['/'],
555
670
  onTriggerChange: setTriggerInfo,
@@ -590,8 +705,8 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
590
705
  visible: triggerMenuVisible,
591
706
  onVisibleChange: function onVisibleChange(v) {
592
707
  if (!v) {
593
- var _editorRef$current12;
594
- (_editorRef$current12 = editorRef.current) === null || _editorRef$current12 === void 0 || _editorRef$current12.cancelTrigger();
708
+ var _editorRef$current13;
709
+ (_editorRef$current13 = editorRef.current) === null || _editorRef$current13 === void 0 || _editorRef$current13.cancelTrigger();
595
710
  setTriggerInfo(null);
596
711
  }
597
712
  },
@@ -624,7 +739,9 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
624
739
  "data-odn-composer-variant": variant,
625
740
  "data-odn-composer-expanded": liteExpanded ? '' : undefined,
626
741
  "data-odn-composer-atmax": atMax ? '' : undefined,
742
+ "data-odn-composer-disabled": disabled || undefined,
627
743
  "data-odn-composer-spacing": spacing === 'drawer' ? 'drawer' : undefined,
744
+ "aria-disabled": disabled || undefined,
628
745
  className: className !== null && className !== void 0 ? className : spacing === 'drawer' ? 'shrink-0' : undefined,
629
746
  style: style
630
747
  }, /*#__PURE__*/React.createElement("div", {
@@ -0,0 +1,9 @@
1
+ /// <reference types="react" />
2
+ import type { ChipData } from './chip';
3
+ export declare function isMentionChip(chip: ChipData): boolean;
4
+ export interface ComposerInlineRefProps {
5
+ data: ChipData;
6
+ }
7
+ /** Composer 内联引用视觉:按 kind 派发 Invocation(/)或 Mention(@)。 */
8
+ export declare function ComposerInlineRef({ data }: ComposerInlineRefProps): import("react").JSX.Element;
9
+ export default ComposerInlineRef;
@@ -0,0 +1,21 @@
1
+ import { Invocation } from "../invocation";
2
+ import { Mention } from "../mention";
3
+ import { chipToInvocationData, chipToMentionData } from "./send-meta";
4
+ export function isMentionChip(chip) {
5
+ return chip.kind === 'mention';
6
+ }
7
+ /** Composer 内联引用视觉:按 kind 派发 Invocation(/)或 Mention(@)。 */
8
+ export function ComposerInlineRef(_ref) {
9
+ var data = _ref.data;
10
+ if (isMentionChip(data)) {
11
+ return /*#__PURE__*/React.createElement(Mention, {
12
+ data: chipToMentionData(data),
13
+ interactive: true
14
+ });
15
+ }
16
+ return /*#__PURE__*/React.createElement(Invocation, {
17
+ data: chipToInvocationData(data),
18
+ interactive: true
19
+ });
20
+ }
21
+ export default ComposerInlineRef;
@@ -0,0 +1,6 @@
1
+ import type { InvocationData, MentionData, SendMeta } from '../_genui-types';
2
+ import type { ChipData } from './chip';
3
+ export declare function chipToInvocationData(chip: ChipData): InvocationData;
4
+ export declare function chipToMentionData(chip: ChipData): MentionData;
5
+ /** 从 editor chip 列表构建 SendMeta(含 invocations / mentions + chips 兼容字段)。 */
6
+ export declare function buildSendMetaFromChips(chips: ChipData[], base?: Omit<SendMeta, 'chips' | 'invocations' | 'mentions'>): SendMeta;