diff --git a/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx b/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx index 0621835e6..5a9d59a71 100644 --- a/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx +++ b/apps/ai-game-creator-shell/src/features/project-workspace/ResourceReferenceInput.tsx @@ -34,7 +34,6 @@ import { Loader2, Music2, RotateCcw, - Search, Sparkles, Video, } from 'lucide-react'; @@ -51,6 +50,7 @@ import { } from 'react'; import { createPortal, flushSync } from 'react-dom'; +import { PlatformResourceFilterBar } from '../../../../../packages/shared/src/components/PlatformResourceFilterBar'; import { PlatformSegmentedTabs } from '../../../../../packages/shared/src/components/PlatformSegmentedTabs'; import { type GameCreationAppAssetManifestEntry, @@ -90,7 +90,9 @@ import { resourceReferenceFromAsset, resourceReferenceMatchesCategoryFilter, resourceReferenceMatchesQuery, + resourceReferenceMatchesTagSelection, type ResourceReferenceScope, + resourceReferenceTagLibrary, } from './resourceReferences'; import { usePromptPolish } from './usePromptPolish'; @@ -114,6 +116,14 @@ type ResourceReferenceInputProps = { multiline?: boolean; rows?: number; showTriggerButton?: boolean; + /** + * 内置「AI 润色 / 恢复原文」动作是否渲染,默认 `true`。 + * + * 聊天输入区用内置那一份(不带资源编辑场景上下文);资源侧快速编辑由宿主自己的 + * `ResourcePromptPolishSlot` 承担润色(带场景上下文与长度上限),传 `false` + * 避免同一个面板里出现两个润色入口。 + */ + showPolishAction?: boolean; inputRef?: RefObject; }; @@ -121,10 +131,17 @@ type ResourcePickerScopeState = { query: string; filter: ResourceReferenceFilter; selectedResourceIds: string[]; + /** 已选标签(多选),与搜索词、功能分类三者叠加生效。 */ + selectedTags: string[]; }; function createResourcePickerScopeState(): ResourcePickerScopeState { - return { query: '', filter: 'all', selectedResourceIds: [] }; + return { + query: '', + filter: 'all', + selectedResourceIds: [], + selectedTags: [], + }; } function createResourcePickerScopeStates(): Record< @@ -291,6 +308,7 @@ function ResourceReferenceEditor({ multiline, rows, showTriggerButton = true, + showPolishAction = true, inputRef, composerRef, }: ResourceReferenceInputProps & { @@ -336,12 +354,19 @@ function ResourceReferenceEditor({ const pickerQuery = pickerScopeState.query; const pickerFilter = pickerScopeState.filter; const selectedResourceIds = pickerScopeState.selectedResourceIds; + const selectedTags = pickerScopeState.selectedTags; const scopeReferences = pickerScope === 'current-version' ? currentVersionAssetReferences : assetReferences; + // 标签库按当前页签的候选派生:两个页签各自一份,标签与计数都不跨页签串。 + const scopeTagItems = useMemo( + () => resourceReferenceTagLibrary(scopeReferences), + [scopeReferences], + ); + const updatePickerScopeState = useCallback( (patch: Partial) => { setPickerScopeStates((current) => ({ @@ -479,24 +504,23 @@ function ResourceReferenceEditor({ }); }, [editor, refreshResourceReferenceLabels]); - useEffect( - () => - editor.registerCommand( - KEY_ENTER_COMMAND, - (event) => { - if (!event || event.shiftKey || event.isComposing) { - return false; - } - event.preventDefault(); - (event.target as HTMLElement | null) - ?.closest('form') - ?.requestSubmit(); - return true; - }, - COMMAND_PRIORITY_HIGH, - ), - [editor], - ); + // Enter 提交只属于单行输入区(聊天 / 项目总控)。多行输入区(资源卡快速编辑提示词) + // 的 Enter 必须留给换行,否则用户没法在提示词里分行。 + useEffect(() => { + if (multiline) return undefined; + return editor.registerCommand( + KEY_ENTER_COMMAND, + (event) => { + if (!event || event.shiftKey || event.isComposing) { + return false; + } + event.preventDefault(); + (event.target as HTMLElement | null)?.closest('form')?.requestSubmit(); + return true; + }, + COMMAND_PRIORITY_HIGH, + ); + }, [editor, multiline]); useEffect( () => @@ -727,9 +751,12 @@ function ResourceReferenceEditor({ const pickerReferences = useMemo(() => { return scopeReferences.filter((reference) => { if (!resourceReferenceMatchesQuery(reference, pickerQuery)) return false; - return resourceReferenceMatchesCategoryFilter(reference, pickerFilter); + if (!resourceReferenceMatchesCategoryFilter(reference, pickerFilter)) { + return false; + } + return resourceReferenceMatchesTagSelection(reference, selectedTags); }); - }, [pickerFilter, pickerQuery, scopeReferences]); + }, [pickerFilter, pickerQuery, scopeReferences, selectedTags]); const visiblePickerReferences = pickerReferences.slice(0, 120); @@ -805,38 +832,46 @@ function ResourceReferenceEditor({