one-design-next 0.0.113 → 0.0.115
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/_genui-types.d.ts +42 -3
- package/dist/_genui-types.js +19 -3
- package/dist/collapse/primitive.d.ts +1 -1
- package/dist/composer/caret.d.ts +74 -0
- package/dist/composer/caret.js +207 -1
- package/dist/composer/editor.d.ts +17 -3
- package/dist/composer/editor.js +423 -19
- package/dist/composer/hooks/useChipManager.d.ts +14 -2
- package/dist/composer/hooks/useChipManager.js +31 -16
- package/dist/composer/index.d.ts +40 -0
- package/dist/composer/index.js +72 -19
- package/dist/composer/inline-ref.d.ts +6 -1
- package/dist/composer/inline-ref.js +6 -1
- package/dist/composer/insert.d.ts +75 -0
- package/dist/composer/insert.js +130 -0
- package/dist/composer/segments.d.ts +32 -4
- package/dist/composer/segments.js +86 -5
- package/dist/composer/style/index.css +22 -0
- package/dist/composer/utils.d.ts +30 -1
- package/dist/composer/utils.js +65 -3
- package/dist/icon/types.d.ts +1 -1
- package/dist/index.d.ts +3 -2
- package/dist/index.js +1 -1
- package/dist/user-bubble/render-segments.d.ts +1 -1
- package/dist/user-bubble/render-segments.js +11 -2
- package/dist/user-bubble/style/index.css +8 -0
- package/package.json +1 -1
|
@@ -16,7 +16,7 @@ import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
|
|
|
16
16
|
*/
|
|
17
17
|
|
|
18
18
|
import { useCallback, useRef, useState } from 'react';
|
|
19
|
-
import { createLineRoot, ensureLineRoots, forEachMarkerSegment, getCurrentLineRoot, LINE_ATTR_NAME } from "../utils";
|
|
19
|
+
import { createLineRoot, ensureCaretAnchorAfter, ensureLineRoots, forEachMarkerSegment, getCurrentLineRoot, LINE_ATTR_NAME, stripInvisibleChars } from "../utils";
|
|
20
20
|
|
|
21
21
|
/** chip 在 React 状态中的形态:data + 对应的 DOM host。 */
|
|
22
22
|
|
|
@@ -120,17 +120,27 @@ onAfterMutate) {
|
|
|
120
120
|
var newRange = document.createRange();
|
|
121
121
|
var parent = host.parentNode;
|
|
122
122
|
if (parent) {
|
|
123
|
-
var
|
|
123
|
+
var _next$textContent, _nextText$textContent, _stripInvisibleChars$;
|
|
124
|
+
/* insertNode 在文本末尾切一刀会留下空文本节点,它等同于「后面没内容」,
|
|
125
|
+
* 不能当成「后面有正文」(否则行尾 chip 的 hint 永远不出现)。
|
|
126
|
+
* 纯 ZWSP 锚点文本(caret 落点)同理——它不是正文。 */
|
|
124
127
|
var next = host.nextSibling;
|
|
125
|
-
var nextText = (next === null || next === void 0 ? void 0 : next.nodeType) === Node.TEXT_NODE ? next : null;
|
|
126
|
-
var
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
128
|
+
var nextText = (next === null || next === void 0 ? void 0 : next.nodeType) === Node.TEXT_NODE && ((_next$textContent = next.textContent) !== null && _next$textContent !== void 0 ? _next$textContent : '') !== '' ? next : null;
|
|
129
|
+
var nextRaw = (_nextText$textContent = nextText === null || nextText === void 0 ? void 0 : nextText.textContent) !== null && _nextText$textContent !== void 0 ? _nextText$textContent : '';
|
|
130
|
+
/* 跳过零宽锚点前缀后的第一个字符;空格也算用户已有的内容。 */
|
|
131
|
+
var firstContentChar = (_stripInvisibleChars$ = stripInvisibleChars(nextRaw)[0]) !== null && _stripInvisibleChars$ !== void 0 ? _stripInvisibleChars$ : '';
|
|
132
|
+
|
|
133
|
+
/* 插入 chip 从不往内容里写空格:补出来的空格会进 value / segments,
|
|
134
|
+
* 变成用户没输入过的字符。间隔由 chip 自身的 padding(0 4px)提供。
|
|
135
|
+
* - 后面紧跟正文:caret 直接落到正文起点
|
|
136
|
+
* - 后面是另一个 chip / 行尾 / 只剩派生 hint / 只有 ZWSP 锚点:垫一个
|
|
137
|
+
* 不进 value 的 ZWSP 锚点给 caret 落位 */
|
|
138
|
+
if (nextText && firstContentChar) {
|
|
139
|
+
newRange.setStart(nextText, nextRaw.indexOf(firstContentChar));
|
|
131
140
|
} else {
|
|
132
|
-
var
|
|
133
|
-
|
|
141
|
+
var _anchor$textContent;
|
|
142
|
+
var anchor = ensureCaretAnchorAfter(host);
|
|
143
|
+
newRange.setStart(anchor, ((_anchor$textContent = anchor.textContent) !== null && _anchor$textContent !== void 0 ? _anchor$textContent : '').length);
|
|
134
144
|
}
|
|
135
145
|
} else {
|
|
136
146
|
newRange.setStartAfter(host);
|
|
@@ -143,7 +153,7 @@ onAfterMutate) {
|
|
|
143
153
|
});
|
|
144
154
|
onAfterMutate === null || onAfterMutate === void 0 || onAfterMutate();
|
|
145
155
|
}, [insertChipAtRange, onAfterMutate]);
|
|
146
|
-
var insertSerializedAtRange = useCallback(function (value, chipList, range, editor) {
|
|
156
|
+
var insertSerializedAtRange = useCallback(function (value, chipList, range, editor, options) {
|
|
147
157
|
var chipById = new Map(chipList.map(function (c) {
|
|
148
158
|
return [c.id, c];
|
|
149
159
|
}));
|
|
@@ -227,7 +237,7 @@ onAfterMutate) {
|
|
|
227
237
|
}
|
|
228
238
|
|
|
229
239
|
// caret 落「拼回点」——粘贴段末尾、原 caret 后内容的起点。
|
|
230
|
-
var sel = window.getSelection();
|
|
240
|
+
var sel = (options === null || options === void 0 ? void 0 : options.caret) === false ? null : window.getSelection();
|
|
231
241
|
if (sel && lastLine) {
|
|
232
242
|
var r = document.createRange();
|
|
233
243
|
r.setStart(lastLine, caretBoundaryChildCount);
|
|
@@ -243,7 +253,7 @@ onAfterMutate) {
|
|
|
243
253
|
_line.appendChild(lineFrags[_i]);
|
|
244
254
|
editor.appendChild(_line);
|
|
245
255
|
}
|
|
246
|
-
var _sel = window.getSelection();
|
|
256
|
+
var _sel = (options === null || options === void 0 ? void 0 : options.caret) === false ? null : window.getSelection();
|
|
247
257
|
var _lastLine = editor.querySelector(":scope > [".concat(LINE_ATTR_NAME, "]:last-of-type"));
|
|
248
258
|
if (_sel && _lastLine) {
|
|
249
259
|
var _r = document.createRange();
|
|
@@ -260,9 +270,14 @@ onAfterMutate) {
|
|
|
260
270
|
chipsRef.current = next;
|
|
261
271
|
setChips(next);
|
|
262
272
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
273
|
+
|
|
274
|
+
/* 默认保持原行为(粘贴 / Tab 采纳都发生在已聚焦的编辑器里,focus 是幂等的)。
|
|
275
|
+
* 首屏还原内容必须显式关掉:mount 期间 focus() 会连带触发页面滚动。 */
|
|
276
|
+
if ((options === null || options === void 0 ? void 0 : options.focus) !== false) {
|
|
277
|
+
requestAnimationFrame(function () {
|
|
278
|
+
return editor.focus();
|
|
279
|
+
});
|
|
280
|
+
}
|
|
266
281
|
onAfterMutate === null || onAfterMutate === void 0 || onAfterMutate();
|
|
267
282
|
}, [onAfterMutate]);
|
|
268
283
|
var syncChipsAfterDomMutation = useCallback(function (editor) {
|
package/dist/composer/index.d.ts
CHANGED
|
@@ -2,8 +2,10 @@ import React from 'react';
|
|
|
2
2
|
import type { Attachment, ChipData, ComposerSegment, SendMeta, SkillItem } from '../_genui-types';
|
|
3
3
|
import type { IconName } from '../icon';
|
|
4
4
|
import { type SkillSlotProps } from '../skill-slot';
|
|
5
|
+
import { type ComposerInsertOptions, type ComposerInsertPart, type ComposerInsertResult } from './insert';
|
|
5
6
|
import type { ComposerParamPanelContext } from './param-panel';
|
|
6
7
|
import './style';
|
|
8
|
+
export type { ComposerInsertOptions, ComposerInsertPart, ComposerInsertResult, } from './insert';
|
|
7
9
|
export type { ComposerParamPanelContext } from './param-panel';
|
|
8
10
|
export type ComposerTool = 'attachment' | 'webSearch' | 'skill';
|
|
9
11
|
export type ComposerVariant = 'full' | 'lite';
|
|
@@ -48,6 +50,38 @@ export interface ComposerRef {
|
|
|
48
50
|
* 返回插入的 chip 实例 id,可用于后续 `updateChipData` 更新状态。
|
|
49
51
|
*/
|
|
50
52
|
insertSkill: (skill: SkillItem) => string;
|
|
53
|
+
/**
|
|
54
|
+
* 命令式:插入一段内容,支持文本 / skill / 自定义 chip 混合,且只触发一次 onChange。
|
|
55
|
+
*
|
|
56
|
+
* 插入位置由 `options.position` 显式声明(当前仅支持 `'end'`):忽略 caret,
|
|
57
|
+
* 始终落在已有内容末尾——适合 Welcome Card / 建议项 / 外部面板「把东西塞进
|
|
58
|
+
* 输入框」的场景。与 `insertSkill`(依赖当前 caret)互补。
|
|
59
|
+
*
|
|
60
|
+
* `parts` 也接受单个 part 或一个字符串(等价于单个 text part)。
|
|
61
|
+
*
|
|
62
|
+
* 约定:
|
|
63
|
+
* - **不自动补空格**:文本与 chip 的间隔由业务在 text part 里显式写。
|
|
64
|
+
* - `skill` 走与菜单插入相同的 chip 结构(含 `hint` / `initialState`);
|
|
65
|
+
* `initialState: 'pending'` 且提供 `renderParamPanel` 时自动打开参数面板
|
|
66
|
+
* (`focus: false` 的后台预填不打开)。
|
|
67
|
+
* - `chip` 是逃生舱:mention / 自定义 kind / 已存草稿都可以直接传。
|
|
68
|
+
* - chip id 与编辑器已有 chip 冲突时自动换新 id,以返回的 `chipIds` 为准。
|
|
69
|
+
* - 受控 `value` 场景请在 onChange 内回写,与手动输入一致(否则一帧后会被
|
|
70
|
+
* controlled sync 拉回旧值)。
|
|
71
|
+
*
|
|
72
|
+
* @example
|
|
73
|
+
* ```ts
|
|
74
|
+
* composerRef.current?.insertContent(
|
|
75
|
+
* [
|
|
76
|
+
* { type: 'text', text: '请基于这份数据' },
|
|
77
|
+
* { type: 'skill', skill: reportSkill },
|
|
78
|
+
* { type: 'text', text: '生成复盘报告' },
|
|
79
|
+
* ],
|
|
80
|
+
* { position: 'end' },
|
|
81
|
+
* );
|
|
82
|
+
* ```
|
|
83
|
+
*/
|
|
84
|
+
insertContent: (parts: ComposerInsertPart | ComposerInsertPart[] | string, options: ComposerInsertOptions) => ComposerInsertResult;
|
|
51
85
|
/**
|
|
52
86
|
* 更新编辑器内指定 chip 的数据(partial merge)。
|
|
53
87
|
* 典型用法:`updateChipData(chipId, { state: 'pending' })` 将 chip 置为待补充态。
|
|
@@ -76,6 +110,12 @@ export interface ComposerProps {
|
|
|
76
110
|
* `segments`:有序内容 IR,业务按数组顺序自定义拼后端。
|
|
77
111
|
* `meta`:附件 / 联网搜索等非正文数据;为空时不传。
|
|
78
112
|
*
|
|
113
|
+
* **派生 `hint` 段**:当最后一个 chip 是带 `SkillItem.hint` 的 skill、且其后
|
|
114
|
+
* 一个字符都没有(空格 / 换行都算内容)时,该提示文案会作为
|
|
115
|
+
* `{ type: 'hint', data: { text } }` 出现在 segments 末尾——与输入框里那段
|
|
116
|
+
* 灰字同源同谓词,不会漂移。不需要它的业务直接 filter / `isHintSegment`
|
|
117
|
+
* 判一下即可。
|
|
118
|
+
*
|
|
79
119
|
* 兼容旧链路(需要单串文本):业务可调 `segmentsToReadableText(segments)`。
|
|
80
120
|
*
|
|
81
121
|
* **返回值语义**:
|
package/dist/composer/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import _extends from "@babel/runtime/helpers/esm/extends";
|
|
2
2
|
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
3
|
-
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
|
|
4
3
|
import _createForOfIteratorHelper from "@babel/runtime/helpers/esm/createForOfIteratorHelper";
|
|
5
4
|
import _regeneratorRuntime from "@babel/runtime/helpers/esm/regeneratorRuntime";
|
|
6
5
|
import _asyncToGenerator from "@babel/runtime/helpers/esm/asyncToGenerator";
|
|
6
|
+
import _toConsumableArray from "@babel/runtime/helpers/esm/toConsumableArray";
|
|
7
7
|
import _slicedToArray from "@babel/runtime/helpers/esm/slicedToArray";
|
|
8
8
|
import React, { forwardRef, useCallback, useEffect, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from 'react';
|
|
9
9
|
import { SKILL_CATEGORY_ICON } from "../_genui-inline-ref";
|
|
@@ -18,20 +18,10 @@ import SkillSlot from "../skill-slot";
|
|
|
18
18
|
import Tooltip from "../tooltip";
|
|
19
19
|
import { COMPOSER_EDITOR_MAX_ROWS_PX, ComposerEditor } from "./editor";
|
|
20
20
|
import { chipHasEditableParams, isMentionChip } from "./inline-ref";
|
|
21
|
+
import { buildInsertPayload, createSkillChipData } from "./insert";
|
|
21
22
|
import { rawValueToSegments, segmentsHasContent } from "./segments";
|
|
22
23
|
import "./style";
|
|
23
24
|
import { COMPOSER_SKILL_MENU_DENSITY_CAP_PX, filterSkillsByQuery, resolveComposerSkillMenuMaxHeight, resolveComposerSkillMenuWidth, shouldOpenSlashTriggerMenu } from "./utils";
|
|
24
|
-
function createInvocationChipData(skill) {
|
|
25
|
-
return {
|
|
26
|
-
id: "skill-".concat(skill.id, "-").concat(Date.now().toString(36), "-").concat(Math.random().toString(36).slice(2, 6)),
|
|
27
|
-
label: skill.label,
|
|
28
|
-
icon: skill.icon,
|
|
29
|
-
kind: 'invocation',
|
|
30
|
-
state: skill.initialState,
|
|
31
|
-
refId: skill.id,
|
|
32
|
-
payload: {}
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
25
|
var TOOL_BTN_ACTIVE = 'color-mix(in srgb, var(--odn-color-brand-6) 10%, transparent)';
|
|
36
26
|
var DEFAULT_ACCEPT = 'image/*,.pdf,.doc,.docx,.xls,.xlsx,.ppt,.pptx,.txt,.md,.csv,.json,.xml,.html,.css,.js,.ts,.jsx,.tsx,.py,.java,.go,.rs,.c,.cpp,.zip,.rar,.7z';
|
|
37
27
|
function formatSizeMb(bytes) {
|
|
@@ -319,12 +309,49 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
319
309
|
},
|
|
320
310
|
insertSkill: function insertSkill(skill) {
|
|
321
311
|
var _editorRef$current8, _editorRef$current9;
|
|
322
|
-
var data =
|
|
312
|
+
var data = createSkillChipData(skill);
|
|
323
313
|
(_editorRef$current8 = editorRef.current) === null || _editorRef$current8 === void 0 || _editorRef$current8.insertChip(data);
|
|
324
314
|
(_editorRef$current9 = editorRef.current) === null || _editorRef$current9 === void 0 || _editorRef$current9.focus();
|
|
325
315
|
if (skill.initialState === 'pending') openParamPanel(data.id);
|
|
326
316
|
return data.id;
|
|
327
317
|
},
|
|
318
|
+
insertContent: function insertContent(parts, options) {
|
|
319
|
+
var editor = editorRef.current;
|
|
320
|
+
if (!editor) return {
|
|
321
|
+
chipIds: [],
|
|
322
|
+
value: ''
|
|
323
|
+
};
|
|
324
|
+
|
|
325
|
+
/* 类型层 position 目前只开 'end';这里兜住 JS / any 调用方,
|
|
326
|
+
* 避免未来扩 'caret' 前被静默当成末尾处理。 */
|
|
327
|
+
var position = options === null || options === void 0 ? void 0 : options.position;
|
|
328
|
+
if (process.env.NODE_ENV !== 'production' && position && position !== 'end') {
|
|
329
|
+
// eslint-disable-next-line no-console
|
|
330
|
+
console.warn("[Composer] insertContent \u5F53\u524D\u4EC5\u652F\u6301 position: \"end\"\uFF0C\u6536\u5230\u300C".concat(position, "\u300D\uFF0C\u5DF2\u6309\u672B\u5C3E\u63D2\u5165\u5904\u7406\u3002"));
|
|
331
|
+
}
|
|
332
|
+
var payload = buildInsertPayload(parts, editor.getChips());
|
|
333
|
+
if (!payload) {
|
|
334
|
+
return {
|
|
335
|
+
chipIds: [],
|
|
336
|
+
value: editor.getValue()
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
editor.insertSerializedAtEnd(payload.value, payload.chips, options);
|
|
340
|
+
|
|
341
|
+
/* 与 insertSkill 对齐:pending skill 插入后自动打开参数面板。
|
|
342
|
+
* 一次 insertContent 可能带多个 pending,只开最后一个(用户最后看到的那块)。
|
|
343
|
+
* `focus: false` 是后台预填语义,不主动弹面板。 */
|
|
344
|
+
var pendingChip = _toConsumableArray(payload.chips).reverse().find(function (chip) {
|
|
345
|
+
return chip.state === 'pending' && !isMentionChip(chip);
|
|
346
|
+
});
|
|
347
|
+
if (pendingChip && (options === null || options === void 0 ? void 0 : options.focus) !== false) {
|
|
348
|
+
openParamPanel(pendingChip.id);
|
|
349
|
+
}
|
|
350
|
+
return {
|
|
351
|
+
chipIds: payload.chipIds,
|
|
352
|
+
value: editor.getValue()
|
|
353
|
+
};
|
|
354
|
+
},
|
|
328
355
|
updateChipData: function updateChipData(id, patch) {
|
|
329
356
|
var _editorRef$current10;
|
|
330
357
|
return (_editorRef$current10 = editorRef.current) === null || _editorRef$current10 === void 0 ? void 0 : _editorRef$current10.updateChipData(id, patch);
|
|
@@ -417,6 +444,10 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
417
444
|
* - segments:有序 IR(text / invocation / mention 交替)
|
|
418
445
|
* - meta:attachments / webSearch 等非正文数据;都为空时不传
|
|
419
446
|
*
|
|
447
|
+
* 派生 hint:最后一个 chip 是 skill 且其后没有内容时,`rawValueToSegments`
|
|
448
|
+
* 会在末尾追加一段 `hint`(与该 chip 后面渲染的灰字同源、同谓词)。
|
|
449
|
+
* 它不属于用户输入,也不是受控 value 的一部分。
|
|
450
|
+
*
|
|
420
451
|
* 需要单串文本的业务方在 onSend 内自行调 segmentsToReadableText。
|
|
421
452
|
* onSend 返回 false 或 Promise<false> 时视为发送失败,不清空;否则正常清空。
|
|
422
453
|
*
|
|
@@ -637,7 +668,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
637
668
|
* source = 'trigger' → editor.replaceTriggerWithChip 替换 `/` + query 整段
|
|
638
669
|
* 任一入口选完后都关闭对应 menu。chip data 用 skill.id + 一次性实例 id。 */
|
|
639
670
|
var handleSelectSkill = useCallback(function (skill, source) {
|
|
640
|
-
var data =
|
|
671
|
+
var data = createSkillChipData(skill);
|
|
641
672
|
if (source === 'trigger') {
|
|
642
673
|
var _editorRef$current16;
|
|
643
674
|
(_editorRef$current16 = editorRef.current) === null || _editorRef$current16 === void 0 || _editorRef$current16.replaceTriggerWithChip(data);
|
|
@@ -681,7 +712,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
681
712
|
|
|
682
713
|
/* editor 在 trigger 激活时把 ↑↓Enter Esc Tab 丢过来。返回 true = editor 别管了。
|
|
683
714
|
* - ↑↓ 改高亮 index(夹在 [0, max] 内)
|
|
684
|
-
* - Enter / Tab
|
|
715
|
+
* - Enter / Tab 选中当前高亮项(Tab 默认落在第一个可用技能上)
|
|
685
716
|
* - Escape 关 menu,焦点保留在 editor */
|
|
686
717
|
var handleTriggerKeyDown = useCallback(function (e) {
|
|
687
718
|
if (!triggerMenuVisible) return false;
|
|
@@ -700,7 +731,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
700
731
|
(_triggerSkillSlotRef$2 = triggerSkillSlotRef.current) === null || _triggerSkillSlotRef$2 === void 0 || _triggerSkillSlotRef$2.moveActive('up');
|
|
701
732
|
return true;
|
|
702
733
|
}
|
|
703
|
-
if (e.key === 'Enter' && !e.shiftKey
|
|
734
|
+
if (e.key === 'Enter' && !e.shiftKey) {
|
|
704
735
|
var _triggerSkillSlotRef$3, _triggerSkillSlotRef$4;
|
|
705
736
|
e.preventDefault();
|
|
706
737
|
var _skill = (_triggerSkillSlotRef$3 = (_triggerSkillSlotRef$4 = triggerSkillSlotRef.current) === null || _triggerSkillSlotRef$4 === void 0 ? void 0 : _triggerSkillSlotRef$4.getActiveSkill()) !== null && _triggerSkillSlotRef$3 !== void 0 ? _triggerSkillSlotRef$3 : filteredSkills === null || filteredSkills === void 0 ? void 0 : filteredSkills[triggerActiveIndex];
|
|
@@ -708,6 +739,20 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
708
739
|
handleSelectSkill(_skill, 'trigger');
|
|
709
740
|
return true;
|
|
710
741
|
}
|
|
742
|
+
/* Tab 与 Enter 同义:接受当前高亮项。query 变化时高亮已复位到第一个
|
|
743
|
+
* 可用项,所以「/ + 关键字有命中 → Tab」就是插入第一个匹配技能。
|
|
744
|
+
* 没有可选项时不 preventDefault,把 Tab 让回浏览器(editor 里还可能
|
|
745
|
+
* 落到 hint 采纳)。 */
|
|
746
|
+
if (e.key === 'Tab') {
|
|
747
|
+
var _ref4, _ref5, _triggerSkillSlotRef$5, _triggerSkillSlotRef$6;
|
|
748
|
+
var _skill2 = (_ref4 = (_ref5 = (_triggerSkillSlotRef$5 = (_triggerSkillSlotRef$6 = triggerSkillSlotRef.current) === null || _triggerSkillSlotRef$6 === void 0 ? void 0 : _triggerSkillSlotRef$6.getActiveSkill()) !== null && _triggerSkillSlotRef$5 !== void 0 ? _triggerSkillSlotRef$5 : filteredSkills === null || filteredSkills === void 0 ? void 0 : filteredSkills[triggerActiveIndex]) !== null && _ref5 !== void 0 ? _ref5 : filteredSkills === null || filteredSkills === void 0 ? void 0 : filteredSkills.find(function (s) {
|
|
749
|
+
return !s.disabled;
|
|
750
|
+
})) !== null && _ref4 !== void 0 ? _ref4 : null;
|
|
751
|
+
if (!_skill2) return false;
|
|
752
|
+
e.preventDefault();
|
|
753
|
+
handleSelectSkill(_skill2, 'trigger');
|
|
754
|
+
return true;
|
|
755
|
+
}
|
|
711
756
|
if (e.key === 'Escape') {
|
|
712
757
|
var _editorRef$current19;
|
|
713
758
|
e.preventDefault();
|
|
@@ -741,8 +786,8 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
741
786
|
useLayoutEffect(function () {
|
|
742
787
|
if (!toolMenuOpen && !triggerMenuVisible) return undefined;
|
|
743
788
|
var sync = function sync() {
|
|
744
|
-
var _skillTriggerRef$curr, _skillTriggerRef$curr2,
|
|
745
|
-
var triggerTop = toolMenuOpen ? (_skillTriggerRef$curr = (_skillTriggerRef$curr2 = skillTriggerRef.current) === null || _skillTriggerRef$curr2 === void 0 ? void 0 : _skillTriggerRef$curr2.getBoundingClientRect().top) !== null && _skillTriggerRef$curr !== void 0 ? _skillTriggerRef$curr : null : (
|
|
789
|
+
var _skillTriggerRef$curr, _skillTriggerRef$curr2, _ref6, _triggerInfo$rect$top, _triggerAnchorRef$cur;
|
|
790
|
+
var triggerTop = toolMenuOpen ? (_skillTriggerRef$curr = (_skillTriggerRef$curr2 = skillTriggerRef.current) === null || _skillTriggerRef$curr2 === void 0 ? void 0 : _skillTriggerRef$curr2.getBoundingClientRect().top) !== null && _skillTriggerRef$curr !== void 0 ? _skillTriggerRef$curr : null : (_ref6 = (_triggerInfo$rect$top = triggerInfo === null || triggerInfo === void 0 ? void 0 : triggerInfo.rect.top) !== null && _triggerInfo$rect$top !== void 0 ? _triggerInfo$rect$top : (_triggerAnchorRef$cur = triggerAnchorRef.current) === null || _triggerAnchorRef$cur === void 0 ? void 0 : _triggerAnchorRef$cur.getBoundingClientRect().top) !== null && _ref6 !== void 0 ? _ref6 : null;
|
|
746
791
|
if (triggerTop === null) return;
|
|
747
792
|
var next = resolveComposerSkillMenuMaxHeight({
|
|
748
793
|
triggerTop: triggerTop,
|
|
@@ -848,7 +893,10 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
848
893
|
activeColor: TOOL_BTN_ACTIVE,
|
|
849
894
|
disabled: btnDisabled
|
|
850
895
|
}, /*#__PURE__*/React.createElement("button", {
|
|
851
|
-
type: "button"
|
|
896
|
+
type: "button"
|
|
897
|
+
/* 不进入 Tab 焦点序列:composer 内的 Tab 只用于 hint 采纳,
|
|
898
|
+
* 不允许它逐个跳到工具栏按钮上(鼠标点击仍可正常使用)。 */,
|
|
899
|
+
tabIndex: -1,
|
|
852
900
|
onClick: function onClick() {
|
|
853
901
|
var _fileInputRef$current;
|
|
854
902
|
onToolClick === null || onToolClick === void 0 || onToolClick('attachment');
|
|
@@ -888,6 +936,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
888
936
|
disabled: disabled || webSearch
|
|
889
937
|
}, /*#__PURE__*/React.createElement("button", {
|
|
890
938
|
type: "button",
|
|
939
|
+
tabIndex: -1,
|
|
891
940
|
onClick: function onClick() {
|
|
892
941
|
onToolClick === null || onToolClick === void 0 || onToolClick('webSearch');
|
|
893
942
|
setWebSearch(function (v) {
|
|
@@ -965,6 +1014,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
965
1014
|
disabled: disabled || toolMenuOpen
|
|
966
1015
|
}, /*#__PURE__*/React.createElement("button", {
|
|
967
1016
|
type: "button",
|
|
1017
|
+
tabIndex: -1,
|
|
968
1018
|
disabled: disabled,
|
|
969
1019
|
onMouseDown: function onMouseDown(e) {
|
|
970
1020
|
return e.preventDefault();
|
|
@@ -1038,6 +1088,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
1038
1088
|
* 因为整体禁用语义包含"现在什么都不能做",包括中断。 */
|
|
1039
1089
|
var sendOrStopButton = !disabled && isGenerating ? /*#__PURE__*/React.createElement("button", {
|
|
1040
1090
|
type: "button",
|
|
1091
|
+
tabIndex: -1,
|
|
1041
1092
|
onClick: onStop,
|
|
1042
1093
|
"data-odn-composer-stop-btn": true,
|
|
1043
1094
|
title: "\u505C\u6B62\u751F\u6210"
|
|
@@ -1046,6 +1097,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
1046
1097
|
size: 18
|
|
1047
1098
|
})) : /*#__PURE__*/React.createElement("button", {
|
|
1048
1099
|
type: "button",
|
|
1100
|
+
tabIndex: -1,
|
|
1049
1101
|
onClick: handleSend,
|
|
1050
1102
|
disabled: disabled || !canSend,
|
|
1051
1103
|
"data-odn-composer-send-btn": true,
|
|
@@ -1222,6 +1274,7 @@ export var Composer = /*#__PURE__*/forwardRef(function Composer(_ref, ref) {
|
|
|
1222
1274
|
placement: "top"
|
|
1223
1275
|
}, /*#__PURE__*/React.createElement("button", {
|
|
1224
1276
|
type: "button",
|
|
1277
|
+
tabIndex: -1,
|
|
1225
1278
|
"data-odn-composer-expand-btn": true,
|
|
1226
1279
|
"aria-label": fullExpandLabel,
|
|
1227
1280
|
"aria-expanded": fullExpanded,
|
|
@@ -10,6 +10,11 @@ export interface ComposerInlineRefProps {
|
|
|
10
10
|
/** 有参数面板时由 Composer 注入;点击打开面板 */
|
|
11
11
|
onClick?: (anchor: HTMLElement) => void;
|
|
12
12
|
}
|
|
13
|
-
/**
|
|
13
|
+
/**
|
|
14
|
+
* Composer 内联引用视觉:按 kind 派发 Invocation(/)或 Mention(@)。
|
|
15
|
+
*
|
|
16
|
+
* 注:派生 hint 不在 host 内渲染——它是 host 的兄弟节点(见 `editor.tsx` 的
|
|
17
|
+
* `syncHintNode`),否则「chip 之后、hint 之前」这个 caret 位置会不存在。
|
|
18
|
+
*/
|
|
14
19
|
export declare function ComposerInlineRef({ data, active, onClick, }: ComposerInlineRefProps): React.JSX.Element;
|
|
15
20
|
export default ComposerInlineRef;
|
|
@@ -16,7 +16,12 @@ export function chipHasEditableParams(chip) {
|
|
|
16
16
|
return v !== null && v !== undefined && v !== '';
|
|
17
17
|
});
|
|
18
18
|
}
|
|
19
|
-
/**
|
|
19
|
+
/**
|
|
20
|
+
* Composer 内联引用视觉:按 kind 派发 Invocation(/)或 Mention(@)。
|
|
21
|
+
*
|
|
22
|
+
* 注:派生 hint 不在 host 内渲染——它是 host 的兄弟节点(见 `editor.tsx` 的
|
|
23
|
+
* `syncHintNode`),否则「chip 之后、hint 之前」这个 caret 位置会不存在。
|
|
24
|
+
*/
|
|
20
25
|
export function ComposerInlineRef(_ref) {
|
|
21
26
|
var data = _ref.data,
|
|
22
27
|
_ref$active = _ref.active,
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Composer · 命令式插入内容(insertContent)
|
|
3
|
+
*
|
|
4
|
+
* 业务场景:Welcome Card / 建议项 / 外部面板希望在「已有输入内容的末尾」一次性
|
|
5
|
+
* 塞入文本、skill chip 等,而不是依赖当前 caret(用户可能停在任意位置)。
|
|
6
|
+
*
|
|
7
|
+
* 职责边界:
|
|
8
|
+
* - 本模块只把 parts 编成 `{ value, chips }`(含 marker 的 raw value + 新 chip
|
|
9
|
+
* 快照),真正的 DOM 插入由 editor 的 `insertSerializedAtEnd` →
|
|
10
|
+
* `insertSerializedAtRange` 完成——与粘贴 / 草稿还原共用同一条链路,
|
|
11
|
+
* 因此 onChange / 派生 hint / 高度 / trigger 只会重算一次。
|
|
12
|
+
* - **不自动补空格**:文本与 chip 之间的间隔由业务自己在 text part 里写,
|
|
13
|
+
* 避免 value / segments 里出现用户没输入过的字符(与插入 skill 的既有约定一致)。
|
|
14
|
+
*/
|
|
15
|
+
import type { ChipData, SkillItem } from '../_genui-types';
|
|
16
|
+
/**
|
|
17
|
+
* insertContent 支持的内容片段:
|
|
18
|
+
* - `text`:普通文本(支持 `\n`;`\r\n` 会归一成 `\n`)
|
|
19
|
+
* - `skill`:按技能目录的条目生成一个 invocation chip(含 hint / initialState)
|
|
20
|
+
* - `chip`:逃生舱,业务自带完整 chip 数据(mention / 自定义 kind / 已存草稿)
|
|
21
|
+
*/
|
|
22
|
+
export type ComposerInsertPart = {
|
|
23
|
+
type: 'text';
|
|
24
|
+
text: string;
|
|
25
|
+
} | {
|
|
26
|
+
type: 'skill';
|
|
27
|
+
skill: SkillItem;
|
|
28
|
+
} | {
|
|
29
|
+
type: 'chip';
|
|
30
|
+
chip: ChipData;
|
|
31
|
+
};
|
|
32
|
+
export interface ComposerInsertOptions {
|
|
33
|
+
/**
|
|
34
|
+
* 插入位置。
|
|
35
|
+
*
|
|
36
|
+
* 当前仅支持 `'end'`(已有内容末尾);`'caret'` 预留在同一入口,
|
|
37
|
+
* 未来扩展时**不会改变现有调用**,`'end'` 的语义也保持不变。
|
|
38
|
+
*/
|
|
39
|
+
position: 'end';
|
|
40
|
+
/**
|
|
41
|
+
* 插入后是否聚焦编辑器,并把 caret 落到新内容末尾(同时滚到可见区)。
|
|
42
|
+
* 传 `false` 时只改内容,不动焦点、不滚动(如后台预填草稿)。
|
|
43
|
+
* @default true
|
|
44
|
+
*/
|
|
45
|
+
focus?: boolean;
|
|
46
|
+
}
|
|
47
|
+
export interface ComposerInsertResult {
|
|
48
|
+
/** 实际插入的 chip id,顺序与 parts 中 chip 出现顺序一致;id 冲突时已换新 */
|
|
49
|
+
chipIds: string[];
|
|
50
|
+
/** 插入后的 raw value(含 marker),等价于本次 onChange 第一参 */
|
|
51
|
+
value: string;
|
|
52
|
+
}
|
|
53
|
+
/** buildInsertPayload 的产物:可直接交给 editor 插入。 */
|
|
54
|
+
export interface ComposerInsertPayload {
|
|
55
|
+
/** 待插入的 raw value(唯一数据源:marker 与 text 交替) */
|
|
56
|
+
value: string;
|
|
57
|
+
/** 待插入 chip 的完整数据(只含本次新增,不含编辑器里已有的) */
|
|
58
|
+
chips: ChipData[];
|
|
59
|
+
/** 本次实际使用的 chip id(与 chips 一一对应) */
|
|
60
|
+
chipIds: string[];
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* 按 SkillItem 生成 chip 实例数据。
|
|
64
|
+
*
|
|
65
|
+
* hint 随 chip 快照下来(而不是渲染时回查技能目录):草稿用
|
|
66
|
+
* `defaultValue + defaultChips` 还原时,目录可能已经变了。
|
|
67
|
+
*/
|
|
68
|
+
export declare function createSkillChipData(skill: SkillItem): ChipData;
|
|
69
|
+
/**
|
|
70
|
+
* 把 parts 编成一次插入所需的 raw value + chips。
|
|
71
|
+
*
|
|
72
|
+
* 返回 `null` 表示没有可插入内容(空数组 / 全空文本),调用方应直接 no-op,
|
|
73
|
+
* 不要触发 onChange。
|
|
74
|
+
*/
|
|
75
|
+
export declare function buildInsertPayload(parts: ComposerInsertPart | ComposerInsertPart[] | string, existingChips?: readonly ChipData[]): ComposerInsertPayload | null;
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import _objectSpread from "@babel/runtime/helpers/esm/objectSpread2";
|
|
2
|
+
import _typeof from "@babel/runtime/helpers/esm/typeof";
|
|
3
|
+
import _createForOfIteratorHelper from "@babel/runtime/helpers/esm/createForOfIteratorHelper";
|
|
4
|
+
/**
|
|
5
|
+
* Composer · 命令式插入内容(insertContent)
|
|
6
|
+
*
|
|
7
|
+
* 业务场景:Welcome Card / 建议项 / 外部面板希望在「已有输入内容的末尾」一次性
|
|
8
|
+
* 塞入文本、skill chip 等,而不是依赖当前 caret(用户可能停在任意位置)。
|
|
9
|
+
*
|
|
10
|
+
* 职责边界:
|
|
11
|
+
* - 本模块只把 parts 编成 `{ value, chips }`(含 marker 的 raw value + 新 chip
|
|
12
|
+
* 快照),真正的 DOM 插入由 editor 的 `insertSerializedAtEnd` →
|
|
13
|
+
* `insertSerializedAtRange` 完成——与粘贴 / 草稿还原共用同一条链路,
|
|
14
|
+
* 因此 onChange / 派生 hint / 高度 / trigger 只会重算一次。
|
|
15
|
+
* - **不自动补空格**:文本与 chip 之间的间隔由业务自己在 text part 里写,
|
|
16
|
+
* 避免 value / segments 里出现用户没输入过的字符(与插入 skill 的既有约定一致)。
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { createMarker, stripInvisibleChars } from "./utils";
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* insertContent 支持的内容片段:
|
|
23
|
+
* - `text`:普通文本(支持 `\n`;`\r\n` 会归一成 `\n`)
|
|
24
|
+
* - `skill`:按技能目录的条目生成一个 invocation chip(含 hint / initialState)
|
|
25
|
+
* - `chip`:逃生舱,业务自带完整 chip 数据(mention / 自定义 kind / 已存草稿)
|
|
26
|
+
*/
|
|
27
|
+
|
|
28
|
+
/** buildInsertPayload 的产物:可直接交给 editor 插入。 */
|
|
29
|
+
|
|
30
|
+
var randomSuffix = function randomSuffix() {
|
|
31
|
+
return "".concat(Date.now().toString(36), "-").concat(Math.random().toString(36).slice(2, 6));
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 按 SkillItem 生成 chip 实例数据。
|
|
36
|
+
*
|
|
37
|
+
* hint 随 chip 快照下来(而不是渲染时回查技能目录):草稿用
|
|
38
|
+
* `defaultValue + defaultChips` 还原时,目录可能已经变了。
|
|
39
|
+
*/
|
|
40
|
+
export function createSkillChipData(skill) {
|
|
41
|
+
return {
|
|
42
|
+
id: "skill-".concat(skill.id, "-").concat(randomSuffix()),
|
|
43
|
+
label: skill.label,
|
|
44
|
+
icon: skill.icon,
|
|
45
|
+
kind: 'invocation',
|
|
46
|
+
state: skill.initialState,
|
|
47
|
+
refId: skill.id,
|
|
48
|
+
payload: {},
|
|
49
|
+
hint: skill.hint
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* 文本预处理:
|
|
55
|
+
* - `\r\n` / `\r` → `\n`,交给行模型拆行
|
|
56
|
+
* - 剔除零宽 marker 字符,避免业务文案里混入 `\u200b\u2063` 后被解析成 chip
|
|
57
|
+
* - 不 trim:首尾空格是业务显式传入的内容,库不替它做决定
|
|
58
|
+
*/
|
|
59
|
+
function normalizeText(text) {
|
|
60
|
+
return stripInvisibleChars(text.replace(/\r\n?/g, '\n'));
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* chip id 必须唯一:marker、Portal key、removeChip / updateChipData 都靠它定位。
|
|
65
|
+
* 冲突时换一个新 id,并在开发期提示——返回值里的 `chipIds` 才是最终生效的 id。
|
|
66
|
+
*/
|
|
67
|
+
function resolveUniqueChipId(chip, usedIds) {
|
|
68
|
+
if (chip.id && !usedIds.has(chip.id)) return chip.id;
|
|
69
|
+
var nextId = "".concat(chip.id || 'chip', "-").concat(randomSuffix());
|
|
70
|
+
while (usedIds.has(nextId)) nextId = "chip-".concat(randomSuffix());
|
|
71
|
+
if (process.env.NODE_ENV !== 'production') {
|
|
72
|
+
// eslint-disable-next-line no-console
|
|
73
|
+
console.warn("[Composer] insertContent \u6536\u5230\u91CD\u590D\u6216\u4E3A\u7A7A\u7684 chip id\u300C".concat(chip.id, "\u300D\uFF0C") + "\u5DF2\u6539\u7528\u300C".concat(nextId, "\u300D\u3002\u8BF7\u4EE5\u8FD4\u56DE\u7684 chipIds \u4E3A\u51C6\u3002"));
|
|
74
|
+
}
|
|
75
|
+
return nextId;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* 把 parts 编成一次插入所需的 raw value + chips。
|
|
80
|
+
*
|
|
81
|
+
* 返回 `null` 表示没有可插入内容(空数组 / 全空文本),调用方应直接 no-op,
|
|
82
|
+
* 不要触发 onChange。
|
|
83
|
+
*/
|
|
84
|
+
export function buildInsertPayload(parts) {
|
|
85
|
+
var existingChips = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];
|
|
86
|
+
var list = typeof parts === 'string' ? [{
|
|
87
|
+
type: 'text',
|
|
88
|
+
text: parts
|
|
89
|
+
}] : Array.isArray(parts) ? parts : [parts];
|
|
90
|
+
var usedIds = new Set(existingChips.map(function (chip) {
|
|
91
|
+
return chip.id;
|
|
92
|
+
}));
|
|
93
|
+
var chips = [];
|
|
94
|
+
var chipIds = [];
|
|
95
|
+
var value = '';
|
|
96
|
+
var _iterator = _createForOfIteratorHelper(list),
|
|
97
|
+
_step;
|
|
98
|
+
try {
|
|
99
|
+
for (_iterator.s(); !(_step = _iterator.n()).done;) {
|
|
100
|
+
var part = _step.value;
|
|
101
|
+
if (!part || _typeof(part) !== 'object') continue;
|
|
102
|
+
if (part.type === 'text') {
|
|
103
|
+
if (typeof part.text === 'string') value += normalizeText(part.text);
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
var chip = null;
|
|
107
|
+
if (part.type === 'skill') {
|
|
108
|
+
chip = part.skill ? createSkillChipData(part.skill) : null;
|
|
109
|
+
} else if (part.chip) {
|
|
110
|
+
chip = _objectSpread({}, part.chip);
|
|
111
|
+
}
|
|
112
|
+
if (!chip) continue;
|
|
113
|
+
chip.id = resolveUniqueChipId(chip, usedIds);
|
|
114
|
+
usedIds.add(chip.id);
|
|
115
|
+
chips.push(chip);
|
|
116
|
+
chipIds.push(chip.id);
|
|
117
|
+
value += createMarker(chip.id);
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
_iterator.e(err);
|
|
121
|
+
} finally {
|
|
122
|
+
_iterator.f();
|
|
123
|
+
}
|
|
124
|
+
if (!value) return null;
|
|
125
|
+
return {
|
|
126
|
+
value: value,
|
|
127
|
+
chips: chips,
|
|
128
|
+
chipIds: chipIds
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -5,18 +5,44 @@
|
|
|
5
5
|
*
|
|
6
6
|
* - `rawValueToSegments`:库内部用,把含零宽 marker 的 raw value + chip 列表
|
|
7
7
|
* 还原为 `ComposerSegment[]`。
|
|
8
|
+
* - `resolveHintTarget`:结构谓词——「最后一个 chip 之后一个字符都没有
|
|
9
|
+
* (空格 / 换行都算内容)+ 该 chip 带 hint」,渲染与发送都靠它。
|
|
8
10
|
* - `segmentsToReadableText`:业务可选工具,按指定占位符格式把 segments 拼成
|
|
9
11
|
* 一句话(默认 `[Skill: label]` / `[@label]`,与 ComposerMention 兼容)。
|
|
10
12
|
* - `segmentsHasContent`:是否含可见内容;库内 send 校验用,业务可复用。
|
|
11
13
|
*/
|
|
12
|
-
import type { ChipData, ComposerInvocationSegment, ComposerMentionSegment, ComposerSegment, ComposerTextSegment, TextData } from '../_genui-types';
|
|
14
|
+
import type { ChipData, ComposerHintSegment, ComposerInvocationSegment, ComposerMentionSegment, ComposerSegment, ComposerTextSegment, HintData, TextData } from '../_genui-types';
|
|
13
15
|
import { ComposerSegmentType } from '../_genui-types';
|
|
14
|
-
export type { ComposerInvocationSegment, ComposerMentionSegment, ComposerSegment, ComposerTextSegment, TextData, };
|
|
15
16
|
export { ComposerSegmentType };
|
|
17
|
+
export type { ComposerHintSegment, ComposerInvocationSegment, ComposerMentionSegment, ComposerSegment, ComposerTextSegment, HintData, TextData, };
|
|
16
18
|
export declare const isTextSegment: (s: ComposerSegment) => s is ComposerTextSegment;
|
|
17
19
|
export declare const isInvocationSegment: (s: ComposerSegment) => s is ComposerInvocationSegment;
|
|
18
20
|
export declare const isMentionSegment: (s: ComposerSegment) => s is ComposerMentionSegment;
|
|
19
|
-
|
|
21
|
+
export declare const isHintSegment: (s: ComposerSegment) => s is ComposerHintSegment;
|
|
22
|
+
/** hint 命中结果。 */
|
|
23
|
+
export interface ComposerHintTarget {
|
|
24
|
+
/** 命中 hint 的 chip id(渲染时用它定位到具体 chip) */
|
|
25
|
+
chipId: string;
|
|
26
|
+
/** 提示文本(skill 模板文案,发送时原样作为 hint 段的 data.text) */
|
|
27
|
+
text: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* 判定当前内容是否命中 hint:
|
|
31
|
+
* 1. `value` 里存在 marker(即至少有一个 chip)
|
|
32
|
+
* 2. 最后一个 marker 之后**一个字符都没有**(空格 / 换行都算内容)
|
|
33
|
+
* 3. 该 marker 对应的 chip 带 hint 文本(`@` 触发的 mention chip 不参与)
|
|
34
|
+
*/
|
|
35
|
+
export declare function resolveHintTarget(raw: string, chips: ChipData[]): ComposerHintTarget | null;
|
|
36
|
+
/**
|
|
37
|
+
* 从含零宽 marker 的 raw value + chip 列表构建有序 segments。
|
|
38
|
+
*
|
|
39
|
+
* 这是「编辑器状态 → 发送内容」的**唯一映射**:
|
|
40
|
+
* 1. marker 依次还原为 invocation / mention,与文本段按出现顺序交替
|
|
41
|
+
* 2. 末尾按结构谓词补一段派生的 `hint`(skill 在最后且其后一个字符都没有时)
|
|
42
|
+
*
|
|
43
|
+
* 因此 `rawValueToSegments(value, chips)` 与 `onSend` 收到的 segments 一致,
|
|
44
|
+
* 业务可以放心用它做预览 / 调试。
|
|
45
|
+
*/
|
|
20
46
|
export declare function rawValueToSegments(raw: string, chips: ChipData[]): ComposerSegment[];
|
|
21
47
|
export interface SegmentsToReadableTextOptions {
|
|
22
48
|
/** invocation 占位格式,默认 `[Skill: {label}]` */
|
|
@@ -27,7 +53,9 @@ export interface SegmentsToReadableTextOptions {
|
|
|
27
53
|
/**
|
|
28
54
|
* 把 segments 拼成一条字符串,供需要单串 string 的 LLM / 旧后端使用。
|
|
29
55
|
* 库**不内置调用**此函数;由业务在 onSend 内自行选择。
|
|
56
|
+
*
|
|
57
|
+
* `hint` 段直接输出原文(它本身就是给模型的模板文案,不加 `[Skill: …]` 包裹)。
|
|
30
58
|
*/
|
|
31
59
|
export declare function segmentsToReadableText(segments: ComposerSegment[], options?: SegmentsToReadableTextOptions): string;
|
|
32
|
-
/** segments
|
|
60
|
+
/** segments 是否含可见内容(文本非空、含内联引用,或含派生 hint)。 */
|
|
33
61
|
export declare function segmentsHasContent(segments: ComposerSegment[]): boolean;
|