修复:粘贴解析跳过只有触发符的退化 token,不再错认正文里的裸 @
buildContentFromPastedText 组装候选时跳过 token 长度小于等于 1 的条目(引用名为空时 token 就是裸 `@` / `$`):边界规则会让它在正文里匹配到任何一处触发符,认下来等于把无关文字错认成一条引用 函数注释的保守规则从两条补成三条,写明空名字按「宁可不成 chip」的同一口径跳过 新增用例:空名字引用的 token 是 `@` 时不认领正文里的裸 `@`、其余 token 照常解析;去掉该跳过分支后用例变红 同步 AGC 聊天素材引用功能说明的粘贴解析小节
This commit is contained in:
@@ -464,9 +464,11 @@ function countMentionTokenOccurrences(value: string, token: string) {
|
||||
* `chatReferenceMentionToken`——与显示口径逐字同一个字符串,所以「从气泡复制再粘贴」不需要
|
||||
* 任何兼容别名:`@显示名` / `$名称` 认得出,`@hero.png`、resourceId、大小写变体一律不认。
|
||||
*
|
||||
* 两条保守规则:
|
||||
* 三条保守规则:
|
||||
*
|
||||
* - 同名多候选(同一个 token 对应多条引用身份)一律按文本保留——宁可不成 chip,也不能认错引用。
|
||||
* - 退化 token(只有触发符的裸 `@` / `$`,即引用名为空)不参与解析:边界规则会把正文里
|
||||
* 任何一处裸 `@` 当成它,等于把无关文字错认成一条引用。
|
||||
* - 只把正文里真的出现过的 token 交给 `buildContentFromTextTokens`,不做「未命中候选补到末尾」
|
||||
* (那是润色回包的语义,照搬会把整份清单追加到粘贴文本后面)。同一 token 出现几次就展开几条
|
||||
* 候选,所以重复出现的引用会各自原位成 chip。
|
||||
@@ -492,6 +494,9 @@ export function buildContentFromPastedText(
|
||||
}
|
||||
const candidates: ContentTokenCandidate[] = [];
|
||||
for (const [token, bucket] of referencesByToken) {
|
||||
// 名字为空的引用只会给出一个裸触发符:它能在正文里匹配到任何一处 `@` / `$`,
|
||||
// 认下来就是错认。空名字是上游数据问题,这里按「宁可不成 chip」的同一口径跳过。
|
||||
if (token.length <= 1) continue;
|
||||
if (bucket.length !== 1) continue;
|
||||
const reference = bucket[0]!;
|
||||
const part = chatReferenceToContentPart(reference);
|
||||
|
||||
@@ -252,6 +252,29 @@ describe('粘贴文本反解析', () => {
|
||||
).toBe('dotenv');
|
||||
});
|
||||
|
||||
it('退化 token(名字为空只剩触发符)不参与解析,正文里的裸 `@` 逐字保留', () => {
|
||||
// 上游仍然可能送来空名字的引用(显示名兜底只是把常见路径堵上),裸 `@` token 会在正文里
|
||||
// 匹配到任何一处 `@`,认下来就是错认:这里必须按「宁可不成 chip」跳过。
|
||||
const nameless: ChatReference = {
|
||||
...resourceReferenceFromAsset(
|
||||
assetEntry('dotenv', '.env'),
|
||||
'asset-picker',
|
||||
),
|
||||
label: '',
|
||||
};
|
||||
expect(chatReferenceMentionToken(nameless)).toBe('@');
|
||||
expect(
|
||||
buildContentFromPastedText('@ 单独一个 @ 符号,看 @hero', [
|
||||
nameless,
|
||||
hero,
|
||||
]),
|
||||
).toEqual([
|
||||
{ type: 'input_text', text: '@ 单独一个 @ 符号,看 ' },
|
||||
{ type: 'agc_resource_reference', resourceId: 'hero' },
|
||||
]);
|
||||
expect(buildContentFromPastedText('@ 只有裸符号', [nameless])).toBeNull();
|
||||
});
|
||||
|
||||
it('与展示口径互为逆运算:用户气泡文本再粘贴回来得到同一份 content', () => {
|
||||
const content: DirectCodexUserContentPart[] = [
|
||||
{ type: 'input_text', text: '看 ' },
|
||||
|
||||
@@ -37,6 +37,7 @@ AGC 聊天输入框支持以结构化引用标记当前项目已登记素材,
|
||||
|
||||
- 只认与显示口径逐字一致的 token:`@显示名`(素材)与 `$名称`(Skill)。`@hero.png`、资源 ID、大小写变体、全角 `@` 都不解析;token 前后必须是行首 / 行尾或空白(与出站文本「token 前后各留一个空白」自洽),所以从用户消息气泡复制出来的那段文字粘回来会重建同一批芯片。
|
||||
- 宁可不成芯片也不能认错:同名多候选(同一个 token 对应多条引用身份)一律按文本保留;未命中的 token 静默保留,不提示、也不猜文件名或路径。
|
||||
- 名字为空的引用(token 只剩一个裸触发符)同样不参与解析:裸 `@` / `$` 会在正文里匹配到任何一处触发符,认下来就是把无关文字错认成引用。显示名一侧另有兜底(见下节「词干为空的素材名回退 `asset.id`」),这条是解析侧的最后一道。
|
||||
- 附件与运行画面区域的 token(`@附件名` / `@区域标签`)不参与解析——这两类引用没有候选,身份来自文件与运行记录,纯文本重建不出来,所以粘贴时按文本保留。
|
||||
- 只有真的解析出引用时才接管粘贴:同 namespace 的 Lexical 负载(跨输入区复制芯片)、不含 token 的纯文本、图片文件粘贴都继续走编辑器默认导入,现有行为不变。接管时整段文本在一次编辑更新内插入,一次 Ctrl+Z 就是一次撤销。
|
||||
- 候选来源按「模糊 / 精确」分两个口:菜单走 `fuzzyLookup(query)`(包含匹配 + 截断到候选上限),粘贴解析走 `lookup()`(就绪的全量候选,不模糊、不截断)。
|
||||
|
||||
Reference in New Issue
Block a user