Files
Genarrative/apps/ai-game-creator-shell/tests/resourceReferences.test.ts
T
k88936 7e009a4c94 引用输入区改为宿主注入引用来源,素材选择器与输入区分离
- 新增 features/project-workspace/reference-source(types + 资源/Skill/附件/运行画面区域四个 provider 工厂),输入区按 providers 顺序取第一个非空回答,不再判断引用种类
- ResourceReferenceInput 删除 assets / versions / activeVersionId / skills / showTriggerButton / openPicker,改为 providers / inputActions / submitSuppressed / containerRef 四个通用接缝
- 新增 ResourceReferencePicker 与 ResourceReferencePickerAction:面板自己拿数据(素材、版本、缩略图预览 invoke)并自管筛选态,由宿主渲染,确认后只经 insertReferences 交回输入区
- 四个宿主改为按需注入 provider 并各自渲染选择器:DirectProject 注入资源+Skill+附件+运行区域,策划输入盒 / 画布生成面板 / 资源卡快速编辑只注入资源+两个静默 provider,非 DirectProject 宿主不再出现 $ Skill 候选
- 附件并入 ChatReference,编辑器收敛为单一 ResourceReferenceNode(删除 AttachmentReferenceNode),附件 chip 的 DOM 契约(data-attachment-reference / data-attachment-status / title / 移除按钮 aria-label)逐字保留
- 附件改为导入成功即以芯片进入正文、失败与异常一律不插入;控制器删除待发附件状态与 removeAttachment,ComposerPendingAttachments 与其提交时拼接一并删除,单次上限按草稿中的附件芯片数计算,导入进行中禁止发送
- directCodexContentToPromptText 的引用 token 前后各留一个空白(不重复),显示名反查改由调用方注入 resourceLabelResolver(assets),并补 TODO:后续改为按组件渲染引用
- 队列 chip 文案、快速编辑出站提示词、润色回填的引用恢复统一走 joinMentionText 的同一份 token 口径
- 新增 tests/referenceSourceProviders.test.ts 规则矩阵单测(触发符、候选过滤与截断、toReference、refresh、mentionToken、Skill 目录懒加载与失败重试),既有输入区/聊天用例迁移为注入 provider
- styles.css 删除随面板外移与待发附件列表一起失效的规则
2026-09-22 16:01:54 +08:00

111 lines
3.3 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import {
type ChatComposerDraft,
chatComposerDraftToDirectCodexUserItem,
directCodexContentToPromptText,
hasMeaningfulDirectCodexContent,
resourceLabelResolver,
} from '../src/features/project-workspace/resourceReferences';
describe('DirectProject user Response item', () => {
it('保留 Lexical content 的文本与引用交错顺序', () => {
const draft: ChatComposerDraft = {
content: [
{ type: 'input_text', text: '先看 ' },
{ type: 'agc_resource_reference', resourceId: 'asset-hero' },
{ type: 'input_text', text: ' 再看 ' },
{
type: 'agc_runtime_region_reference',
label: '主画面',
runId: 'run-1',
versionId: 'version-1',
elementTag: 'canvas',
elementRole: 'playfield',
text: '可试玩区域',
width: 320,
height: 180,
resourceIds: ['asset-hero'],
},
],
};
expect(
chatComposerDraftToDirectCodexUserItem(draft, 'turn-1:user'),
).toEqual({
type: 'message',
role: 'user',
id: 'turn-1:user',
content: draft.content,
});
});
it('资源引用只投影稳定 resourceId,不携带展示字段', () => {
const draft: ChatComposerDraft = {
content: [
{ type: 'input_text', text: '请使用素材' },
{ type: 'agc_resource_reference', resourceId: 'asset-hero' },
],
};
expect(
chatComposerDraftToDirectCodexUserItem(draft, 'turn-2:user'),
).toEqual({
type: 'message',
role: 'user',
id: 'turn-2:user',
content: [
{ type: 'input_text', text: '请使用素材' },
{ type: 'agc_resource_reference', resourceId: 'asset-hero' },
],
});
});
it('原样保留纯空白 input_text,不替用户改写提示词', () => {
const draft: ChatComposerDraft = {
content: [
{ type: 'input_text', text: '先看' },
{ type: 'input_text', text: ' ' },
{ type: 'agc_resource_reference', resourceId: 'asset-hero' },
{ type: 'input_text', text: '\n' },
],
};
expect(
chatComposerDraftToDirectCodexUserItem(draft, 'turn-3:user').content,
).toEqual(draft.content);
});
it('只有最终 content 全为空白时才判定为空输入', () => {
expect(
hasMeaningfulDirectCodexContent([{ type: 'input_text', text: ' \n ' }]),
).toBe(false);
expect(hasMeaningfulDirectCodexContent([])).toBe(false);
// 空白文本仍然保留,但只要有实际文本或引用就不能当空输入拒发。
expect(
hasMeaningfulDirectCodexContent([
{ type: 'input_text', text: ' \n' },
{ type: 'input_text', text: '看' },
]),
).toBe(true);
expect(
hasMeaningfulDirectCodexContent([
{ type: 'agc_resource_reference', resourceId: 'asset-hero' },
]),
).toBe(true);
});
it('展示用文本由 content 派生,引用按 @ 显示名展开', () => {
expect(
directCodexContentToPromptText(
[
{ type: 'input_text', text: '用 ' },
{ type: 'agc_resource_reference', resourceId: 'asset-hero' },
{ type: 'input_text', text: ' 做主视觉' },
],
resourceLabelResolver([]),
),
).toBe('用 @asset-hero 做主视觉');
});
});