WIP: AGC 资源画布与替换改造 V3.0 #316
@@ -10691,6 +10691,9 @@ export function App({
|
||||
(asset) => asset.localPath && !asset.localPath.startsWith('.agent/'),
|
||||
)
|
||||
.slice(0, 8);
|
||||
const chatProjectAssets = manifest.assets.filter(
|
||||
(asset) => asset.localPath && !asset.localPath.startsWith('.agent/'),
|
||||
);
|
||||
const visibleMainProjectCheckpoints = projectCheckpoints.slice(0, 5);
|
||||
const currentProjectTitle = localProject
|
||||
? manifest.name.trim() || projectNameFromPath(localProject.projectPath)
|
||||
@@ -10940,6 +10943,7 @@ export function App({
|
||||
chatInput={chatInput}
|
||||
chatReferences={chatReferences}
|
||||
composerRef={chatComposerRef}
|
||||
chatProjectAssets={chatProjectAssets}
|
||||
messagesRef={supervisorChatMessagesRef}
|
||||
onCancelConfirmation={cancelUiCommandConfirmation}
|
||||
onChatInputChange={handleChatComposerChange}
|
||||
@@ -10968,7 +10972,6 @@ export function App({
|
||||
hiddenConversationCount={hiddenConversationCount}
|
||||
needsUserInput={projectSupervisorNeedsUserInput}
|
||||
visibleMessages={visibleMessages}
|
||||
visibleMainProjectAssets={visibleMainProjectAssets}
|
||||
workspaceStatus={workspaceStatus}
|
||||
expectedRunId={projectSupervisorExpectedRunId}
|
||||
/>
|
||||
@@ -10981,6 +10984,7 @@ export function App({
|
||||
chatInput={chatInput}
|
||||
chatReferences={chatReferences}
|
||||
composerRef={chatComposerRef}
|
||||
chatProjectAssets={chatProjectAssets}
|
||||
directCodex={directCodexProductRuntime}
|
||||
directStatus={directCodexProductRuntime ? directCodexStatus : null}
|
||||
directProcessDetail={
|
||||
@@ -11011,7 +11015,6 @@ export function App({
|
||||
: projectSupervisorTransientReply
|
||||
}
|
||||
visibleMessages={visibleMessages}
|
||||
visibleMainProjectAssets={visibleMainProjectAssets}
|
||||
visibleProfessionalAgentCards={visibleProfessionalAgentCards}
|
||||
showProfessionalCollaboration={
|
||||
!directCodexProductRuntime && orchestrationMode === 'professional-dag'
|
||||
@@ -11062,6 +11065,7 @@ export function App({
|
||||
chatInput={chatInput}
|
||||
chatReferences={chatReferences}
|
||||
composerRef={chatComposerRef}
|
||||
chatProjectAssets={chatProjectAssets}
|
||||
chatInputRef={chatInputRef}
|
||||
confirmProjectCreateInNonEmptyFolder={
|
||||
confirmProjectCreateInNonEmptyFolder
|
||||
|
||||
@@ -69,6 +69,7 @@ function directStatusTitle(status: string | null | undefined) {
|
||||
type ProjectSupervisorViewProps = RuntimePanelProps & {
|
||||
chatInput: string;
|
||||
chatReferences: ChatReference[];
|
||||
chatProjectAssets: import('../../../../../packages/shared/src/contracts/gameCreationApp').GameCreationAppAssetManifestEntry[];
|
||||
composerRef?: Ref<ResourceReferenceInputHandle>;
|
||||
directCodex?: boolean;
|
||||
directStatus?: GameCreatorDirectTurnUpdateStatus | null;
|
||||
@@ -103,12 +104,12 @@ type ProjectSupervisorViewProps = RuntimePanelProps & {
|
||||
comment: string | null,
|
||||
) => Promise<void>;
|
||||
onMakeGameFromApprovedGdd?: () => Promise<void>;
|
||||
visibleMainProjectAssets?: import('../../../../../packages/shared/src/contracts/gameCreationApp').GameCreationAppAssetManifestEntry[];
|
||||
};
|
||||
|
||||
export function ProjectSupervisorView({
|
||||
chatInput,
|
||||
chatReferences,
|
||||
chatProjectAssets,
|
||||
composerRef,
|
||||
directCodex = false,
|
||||
directStatus = null,
|
||||
@@ -140,7 +141,6 @@ export function ProjectSupervisorView({
|
||||
onPlanGddRefresh,
|
||||
onPlanGddDecision,
|
||||
onMakeGameFromApprovedGdd,
|
||||
visibleMainProjectAssets = [],
|
||||
...runtimePanelProps
|
||||
}: ProjectSupervisorViewProps) {
|
||||
const [expandedProcessKey, setExpandedProcessKey] = useState<string | null>(
|
||||
@@ -363,7 +363,7 @@ export function ProjectSupervisorView({
|
||||
<ResourceReferenceInput
|
||||
ref={composerRef}
|
||||
ariaLabel={directCodex ? '陶泥儿对话内容' : '项目需求'}
|
||||
assets={visibleMainProjectAssets}
|
||||
assets={chatProjectAssets}
|
||||
projectPath={projectPath}
|
||||
disabled={
|
||||
runtimePanelProps.controlBusy || needsUserInput || modelValidating
|
||||
|
||||
+3
-1
@@ -65,6 +65,7 @@ type ProjectWorkspaceChatPaneProps = {
|
||||
chatAgentBusy: boolean;
|
||||
chatInput: string;
|
||||
chatReferences: ChatReference[];
|
||||
chatProjectAssets: GameCreationAppAssetManifestEntry[];
|
||||
composerRef?: Ref<ResourceReferenceInputHandle>;
|
||||
chatInputRef: RefObject<HTMLDivElement | null>;
|
||||
confirmProjectCreateInNonEmptyFolder: () => void;
|
||||
@@ -211,6 +212,7 @@ export function ProjectWorkspaceChatPane({
|
||||
chatAgentBusy,
|
||||
chatInput,
|
||||
chatReferences,
|
||||
chatProjectAssets,
|
||||
composerRef,
|
||||
chatInputRef,
|
||||
confirmProjectCreateInNonEmptyFolder,
|
||||
@@ -945,7 +947,7 @@ export function ProjectWorkspaceChatPane({
|
||||
ref={composerRef}
|
||||
inputRef={chatInputRef}
|
||||
ariaLabel="创作想法"
|
||||
assets={visibleMainProjectAssets}
|
||||
assets={chatProjectAssets}
|
||||
projectPath={projectPath}
|
||||
disabled={chatAgentBusy || projectSupervisorNeedsUserInput}
|
||||
multiline={false}
|
||||
|
||||
+11
-13
@@ -206,7 +206,6 @@ function ResourceReferenceEditor({
|
||||
width: number;
|
||||
} | null>(null);
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const previewScopeIdRef = useRef(createProjectResourcePreviewScopeId());
|
||||
|
||||
const assetReferences = useMemo(
|
||||
() =>
|
||||
@@ -297,13 +296,6 @@ function ResourceReferenceEditor({
|
||||
lastEmittedDraftRef.current = nextDraft;
|
||||
}, [editor, references, value]);
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
cancelLocalProjectResourcePreviewScope(previewScopeIdRef.current);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(
|
||||
() =>
|
||||
editor.registerCommand(
|
||||
@@ -578,7 +570,6 @@ function ResourceReferenceEditor({
|
||||
(asset) => asset.id === reference.resourceId,
|
||||
)}
|
||||
projectPath={projectPath}
|
||||
scopeId={previewScopeIdRef.current}
|
||||
/>
|
||||
<span>
|
||||
<strong>{reference.label}</strong>
|
||||
@@ -637,17 +628,24 @@ function ResourceReferenceEditor({
|
||||
function ResourcePickerThumbnail({
|
||||
asset,
|
||||
projectPath,
|
||||
scopeId,
|
||||
}: {
|
||||
asset?: GameCreationAppAssetManifestEntry;
|
||||
projectPath: string;
|
||||
scopeId: string;
|
||||
}) {
|
||||
const previewScopeIdRef = useRef(createProjectResourcePreviewScopeId());
|
||||
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
||||
const [failed, setFailed] = useState(false);
|
||||
const mediaType = asset?.mediaType.toLowerCase() ?? '';
|
||||
const kind = asset?.kind.toLowerCase() ?? '';
|
||||
|
||||
useEffect(
|
||||
() => () => {
|
||||
cancelLocalProjectResourcePreviewScope(previewScopeIdRef.current);
|
||||
previewScopeIdRef.current = createProjectResourcePreviewScopeId();
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
setPreviewUrl(null);
|
||||
setFailed(false);
|
||||
@@ -660,7 +658,7 @@ function ResourcePickerThumbnail({
|
||||
void invoke<{ dataUrl: string }>('read_local_project_image_preview', {
|
||||
projectPath,
|
||||
relativePath: asset.localPath,
|
||||
scopeId,
|
||||
scopeId: previewScopeIdRef.current,
|
||||
requestId: createProjectResourcePreviewRequestId(),
|
||||
})
|
||||
.then((result) => {
|
||||
@@ -672,7 +670,7 @@ function ResourcePickerThumbnail({
|
||||
return () => {
|
||||
cancelled = true;
|
||||
};
|
||||
}, [asset, mediaType, projectPath, scopeId]);
|
||||
}, [asset, mediaType, projectPath]);
|
||||
|
||||
if (previewUrl) {
|
||||
return <img src={previewUrl} alt="" />;
|
||||
|
||||
+3
-3
@@ -45,6 +45,7 @@ type SupervisorChatOnlyViewProps = {
|
||||
chatAgentBusy: boolean;
|
||||
chatInput: string;
|
||||
chatReferences: ChatReference[];
|
||||
chatProjectAssets: import('../../../../../packages/shared/src/contracts/gameCreationApp').GameCreationAppAssetManifestEntry[];
|
||||
composerRef?: Ref<ResourceReferenceInputHandle>;
|
||||
directCodex?: boolean;
|
||||
messagesRef: RefObject<HTMLDivElement | null>;
|
||||
@@ -73,13 +74,13 @@ type SupervisorChatOnlyViewProps = {
|
||||
visibleMessages: ChatMessage[];
|
||||
workspaceStatus: string;
|
||||
expectedRunId?: string | null;
|
||||
visibleMainProjectAssets?: import('../../../../../packages/shared/src/contracts/gameCreationApp').GameCreationAppAssetManifestEntry[];
|
||||
};
|
||||
|
||||
export function SupervisorChatOnlyView({
|
||||
chatAgentBusy,
|
||||
chatInput,
|
||||
chatReferences,
|
||||
chatProjectAssets,
|
||||
composerRef,
|
||||
directCodex = false,
|
||||
messagesRef,
|
||||
@@ -107,7 +108,6 @@ export function SupervisorChatOnlyView({
|
||||
needsUserInput,
|
||||
visibleMessages,
|
||||
workspaceStatus,
|
||||
visibleMainProjectAssets = [],
|
||||
}: SupervisorChatOnlyViewProps) {
|
||||
const shouldFollowLatestRef = useRef(true);
|
||||
const running = Boolean(
|
||||
@@ -312,7 +312,7 @@ export function SupervisorChatOnlyView({
|
||||
<ResourceReferenceInput
|
||||
ref={composerRef}
|
||||
ariaLabel={directCodex ? '陶泥儿对话内容' : '项目总控对话内容'}
|
||||
assets={visibleMainProjectAssets}
|
||||
assets={chatProjectAssets}
|
||||
projectPath={projectPath}
|
||||
disabled={chatAgentBusy || needsUserInput}
|
||||
rows={3}
|
||||
|
||||
@@ -8780,12 +8780,12 @@ iframe.preview-frame {
|
||||
.conversation-model-select {
|
||||
position: absolute;
|
||||
bottom: 10px;
|
||||
right: 48px;
|
||||
right: 86px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 4px;
|
||||
max-width: calc(100% - 64px);
|
||||
max-width: calc(100% - 120px);
|
||||
pointer-events: auto;
|
||||
}
|
||||
.home-input-model-select {
|
||||
@@ -9096,7 +9096,7 @@ iframe.preview-frame {
|
||||
.project-supervisor-composer
|
||||
.resource-reference-input-actions {
|
||||
position: absolute;
|
||||
right: 10px;
|
||||
right: 48px;
|
||||
bottom: 10px;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// @vitest-environment jsdom
|
||||
import { cleanup, render, screen, waitFor } from '@testing-library/react';
|
||||
import userEvent from '@testing-library/user-event';
|
||||
import { StrictMode } from 'react';
|
||||
import { afterEach, describe, expect, test, vi } from 'vitest';
|
||||
|
||||
import type { GameCreationAppAssetManifestEntry } from '../../../packages/shared/src/contracts/gameCreationApp';
|
||||
@@ -78,6 +79,49 @@ describe('ResourceReferenceInput', () => {
|
||||
).not.toBeNull();
|
||||
});
|
||||
|
||||
test('loads image thumbnails through the controlled native preview command', async () => {
|
||||
const user = userEvent.setup();
|
||||
const invoke = vi.fn().mockResolvedValue({
|
||||
dataUrl: 'data:image/png;base64,iVBORw0KGgo=',
|
||||
});
|
||||
(
|
||||
window as unknown as {
|
||||
__TAURI__?: { core?: { invoke?: typeof invoke } };
|
||||
}
|
||||
).__TAURI__ = { core: { invoke } };
|
||||
render(
|
||||
<StrictMode>
|
||||
<ResourceReferenceInput
|
||||
value=""
|
||||
references={[]}
|
||||
onChange={vi.fn()}
|
||||
assets={assets}
|
||||
projectPath="C:/project"
|
||||
ariaLabel="聊天"
|
||||
/>
|
||||
</StrictMode>,
|
||||
);
|
||||
|
||||
await user.click(screen.getByRole('button', { name: '插入素材引用' }));
|
||||
await waitFor(() => {
|
||||
expect(
|
||||
document.querySelector('.resource-reference-picker-list img'),
|
||||
).not.toBeNull();
|
||||
});
|
||||
expect(invoke).toHaveBeenCalledWith(
|
||||
'read_local_project_image_preview',
|
||||
expect.objectContaining({
|
||||
projectPath: 'C:/project',
|
||||
relativePath: 'assets/hero.png',
|
||||
}),
|
||||
);
|
||||
delete (
|
||||
window as unknown as {
|
||||
__TAURI__?: { core?: { invoke?: typeof invoke } };
|
||||
}
|
||||
).__TAURI__;
|
||||
});
|
||||
|
||||
test('filters candidate references by name, id, kind, and media category', () => {
|
||||
const hero = resourceReferenceFromAsset(assets[0]!, 'asset-picker');
|
||||
const theme = resourceReferenceFromAsset(assets[2]!, 'asset-picker');
|
||||
|
||||
Reference in New Issue
Block a user