From 5bd12bcfe6dd071655296191488e5a9860c359fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=8E=8B=E5=BE=B7=E5=AE=87?= Date: Tue, 15 Sep 2026 17:19:43 +0800 Subject: [PATCH] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E8=BE=93=E5=85=A5=E6=A1=86Sk?= =?UTF-8?q?ill=E6=8F=90=E5=8F=8A=E5=80=99=E9=80=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 扩展聊天引用模型支持 Skill 节点与 canonical content 复用 Lexical typeahead 增加 $Skill 候选和芯片展示  Conflicts:  apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx --- .../src/agent/direct_codex_user_item/model.rs | 2 + .../ResourceReferenceChip.tsx | 15 +- .../ResourceReferenceInput.tsx | 242 ++++++++++++------ .../generated/DirectCodexUserContentPart.ts | 1 + .../project-workspace/resourceReferences.ts | 21 +- 5 files changed, 201 insertions(+), 80 deletions(-) diff --git a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/model.rs b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/model.rs index e739b420b..911ba2f81 100644 --- a/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/model.rs +++ b/apps/ai-game-creator-shell/src-tauri/src/agent/direct_codex_user_item/model.rs @@ -34,6 +34,8 @@ pub(crate) enum DirectCodexUserContentPart { InputText { text: String }, #[serde(rename = "agc_resource_reference")] AgcResourceReference { resource_id: String }, + #[serde(rename = "agc_skill_reference")] + AgcSkillReference { name: String }, #[serde(rename = "agc_runtime_region_reference")] AgcRuntimeRegionReference(DirectCodexUserRuntimeRegionPart), /// Uploaded project attachment kept inline in canonical content. diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceChip.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceChip.tsx index 79fc67c3e..d9806292f 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceChip.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceChip.tsx @@ -21,18 +21,25 @@ export function ResourceReferenceChip({ data-runtime-region-reference={ reference.type === 'runtime-region' ? 'true' : undefined } + data-skill-reference-name={ + reference.type === 'skill' ? reference.name : undefined + } contentEditable={false} title={ reference.type === 'resource' ? `${reference.label} · ${reference.kind}` - : `${reference.label} · 运行区域` + : reference.type === 'skill' + ? `${reference.name} · Skill` + : `${reference.label} · 运行区域` } > - - {reference.label} + + + {reference.type === 'skill' ? reference.name : reference.label} + - ))} - , - document.body, - ); - }, - [rootRef], - ); + const renderMentionMenu: MenuRenderFn< + ResourceMentionOption | SkillMentionOption + > = useCallback((_anchorElementRef, itemProps) => { + const inputRect = rootRef.current?.getBoundingClientRect(); + if (!inputRect || itemProps.options.length === 0) { + return null; + } + const viewportPadding = 12; + const menuWidth = Math.min( + Math.max(280, inputRect.width), + Math.min(420, window.innerWidth - viewportPadding * 2), + ); + const left = Math.min( + Math.max(viewportPadding, inputRect.left), + Math.max( + viewportPadding, + window.innerWidth - menuWidth - viewportPadding, + ), + ); + const availableAbove = Math.max(0, inputRect.top - viewportPadding - 8); + const availableBelow = Math.max( + 0, + window.innerHeight - inputRect.bottom - viewportPadding - 8, + ); + const openAbove = availableBelow < 160 && availableAbove > availableBelow; + const maxHeight = Math.max( + 120, + Math.min(240, openAbove ? availableAbove : availableBelow), + ); + const top = openAbove + ? Math.max(viewportPadding, inputRect.top - maxHeight - 8) + : inputRect.bottom + 8; + return createPortal( +
+ {itemProps.options.map((option, index) => ( + + ))} +
, + document.body, + ); + }, [rootRef]); const pickerReferences = useMemo(() => { return scopeReferences.filter((reference) => { @@ -1056,7 +1135,22 @@ function ResourceReferenceEditor({ onSelectOption={(option, textNode, closeMenu) => handleSelectMention(option, textNode, closeMenu) } - menuRenderFn={renderMentionMenu} + menuRenderFn={ + renderMentionMenu as unknown as MenuRenderFn + } + anchorClassName="resource-reference-menu-anchor" + preselectFirstItem + /> + + options={skillOptions} + triggerFn={skillTriggerFn} + onQueryChange={setSkillQuery} + onSelectOption={(option, textNode, closeMenu) => + handleSelectSkill(option, textNode, closeMenu) + } + menuRenderFn={ + renderMentionMenu as unknown as MenuRenderFn + } anchorClassName="resource-reference-menu-anchor" preselectFirstItem /> diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserContentPart.ts b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserContentPart.ts index c3b7b0ea1..06bfcb92c 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserContentPart.ts +++ b/apps/ai-game-creator-shell/src/features/project-workspace/generated/DirectCodexUserContentPart.ts @@ -5,6 +5,7 @@ import type { DirectCodexUserRuntimeRegionPart } from './DirectCodexUserRuntimeR export type DirectCodexUserContentPart = | { type: 'input_text'; text: string } | { type: 'agc_resource_reference'; resourceId: string } + | { type: 'agc_skill_reference'; name: string } | ({ type: 'agc_runtime_region_reference'; } & DirectCodexUserRuntimeRegionPart) diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts b/apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts index 72f46c677..8daf77df1 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts +++ b/apps/ai-game-creator-shell/src/features/project-workspace/resourceReferences.ts @@ -46,7 +46,16 @@ export type RuntimeRegionReference = { source: 'runtime-picker'; }; -export type ChatReference = ResourceReference | RuntimeRegionReference; +export type SkillReference = { + type: 'skill'; + name: string; + description?: string; +}; + +export type ChatReference = + | ResourceReference + | RuntimeRegionReference + | SkillReference; export type ChatComposerDraft = { text: string; @@ -99,6 +108,9 @@ export function chatReferenceToContentPart( if (reference.type === 'resource') { return { type: 'agc_resource_reference', resourceId: reference.resourceId }; } + if (reference.type === 'skill') { + return { type: 'agc_skill_reference', name: reference.name }; + } return { type: 'agc_runtime_region_reference', label: reference.label, @@ -341,6 +353,9 @@ function chatReferenceKey(reference: ChatReference) { if (reference.type === 'resource') { return `resource:${reference.resourceId}:${reference.source}`; } + if (reference.type === 'skill') { + return `skill:${reference.name}`; + } return `runtime-region:${runtimeRegionReferenceDiscriminators(reference)}`; } @@ -353,7 +368,9 @@ export function chatReferenceListKey(references: ChatReference[]) { .map((reference) => reference.type === 'resource' ? `resource:${reference.resourceId}:${reference.source}:${reference.label}` - : `runtime-region:${runtimeRegionReferenceDiscriminators(reference)}`, + : reference.type === 'skill' + ? `skill:${reference.name}` + : `runtime-region:${runtimeRegionReferenceDiscriminators(reference)}`, ) .join('\u0001'); }