WIP: AGC 资源画布与替换改造 V3.0 #316

Draft
suzmii wants to merge 131 commits from feat/agc-canvas-resource-workbench-v3 into master
3 changed files with 147 additions and 48 deletions
Showing only changes of commit 664c8fbaf0 - Show all commits
@@ -1,8 +1,7 @@
import { ArrowUp, Loader2 } from 'lucide-react';
import { ArrowUp, AtSign, Loader2 } from 'lucide-react';
import type {
ComponentProps,
FormEventHandler,
Ref,
RefObject,
UIEventHandler,
} from 'react';
@@ -70,7 +69,7 @@ type ProjectSupervisorViewProps = RuntimePanelProps & {
chatInput: string;
chatReferences: ChatReference[];
chatProjectAssets: import('../../../../../packages/shared/src/contracts/gameCreationApp').GameCreationAppAssetManifestEntry[];
composerRef?: Ref<ResourceReferenceInputHandle>;
composerRef?: RefObject<ResourceReferenceInputHandle | null>;
directCodex?: boolean;
directStatus?: GameCreatorDirectTurnUpdateStatus | null;
directProcessDetail?: string;
@@ -162,6 +161,24 @@ export function ProjectSupervisorView({
const [modelValidating, setModelValidating] = useState(false);
const modelSelectRef = useRef<ConversationModelSelectHandle>(null);
const modelValidateInFlightRef = useRef(false);
const submitButton = (
<button
type="submit"
aria-label={submitLabel}
title={submitLabel}
disabled={
runtimePanelProps.controlBusy ||
needsUserInput ||
(directCodex && (!modelReady || modelValidating))
}
>
{submitting ? (
<Loader2 size={16} aria-hidden="true" className="animate-spin" />
) : (
<ArrowUp size={16} aria-hidden="true" />
)}
</button>
);
return (
<section
className={`project-supervisor-surface${directCodex ? ' is-direct-codex' : ''}`}
@@ -336,7 +353,9 @@ export function ProjectSupervisorView({
</div>
) : null}
<form
className="project-supervisor-composer"
className={`project-supervisor-composer${
directCodex ? ' is-direct-codex' : ''
}`}
onSubmit={(event) => {
if (!directCodex) {
onSubmit(event);
@@ -371,6 +390,7 @@ export function ProjectSupervisorView({
rows={3}
value={chatInput}
references={chatReferences}
showTriggerButton={!directCodex}
placeholder={
directCodex
? '告诉陶泥儿接下来要做什么,或输入 @ 选择资源'
@@ -379,31 +399,28 @@ export function ProjectSupervisorView({
onChange={onChatInputChange}
/>
{directCodex ? (
<ConversationModelSelect
ref={modelSelectRef}
// 允许在对话进行中切换模型:写回的是客户端配置,只影响后续轮次,
// 当前回合不受影响;发送按钮仍由 controlBusy / modelReady 把关。
disabled={needsUserInput}
onReady={setModelReady}
projectPath={projectPath}
/>
) : null}
<button
type="submit"
aria-label={submitLabel}
title={submitLabel}
disabled={
runtimePanelProps.controlBusy ||
needsUserInput ||
(directCodex && (!modelReady || modelValidating))
}
>
{submitting ? (
<Loader2 size={16} aria-hidden="true" className="animate-spin" />
) : (
<ArrowUp size={16} aria-hidden="true" />
)}
</button>
<div className="project-supervisor-composer-controls">
<ConversationModelSelect
ref={modelSelectRef}
disabled={runtimePanelProps.controlBusy || needsUserInput}
onReady={setModelReady}
projectPath={projectPath}
/>
<button
type="button"
className="project-supervisor-reference-trigger"
aria-label="插入素材引用"
title="插入素材引用"
disabled={runtimePanelProps.controlBusy || needsUserInput}
onClick={() => composerRef?.current?.openPicker()}
>
<AtSign size={15} aria-hidden="true" />
</button>
{submitButton}
</div>
) : (
submitButton
)}
</form>
<small className="project-supervisor-workspace-status">
{projectWorkspaceStatusForDisplay(workspaceStatus)}
@@ -84,11 +84,13 @@ type ResourceReferenceInputProps = {
ariaLabel: string;
multiline?: boolean;
rows?: number;
showTriggerButton?: boolean;
inputRef?: RefObject<HTMLDivElement | null>;
};
export type ResourceReferenceInputHandle = {
insertReferences: (references: ChatReference[]) => void;
openPicker: () => void;
focus: () => void;
};
@@ -184,6 +186,7 @@ function ResourceReferenceEditor({
ariaLabel,
multiline,
rows,
showTriggerButton = true,
inputRef,
composerRef,
}: ResourceReferenceInputProps & {
@@ -254,13 +257,21 @@ function ResourceReferenceEditor({
[editor],
);
const openPicker = useCallback(() => {
setPickerQuery('');
setPickerFilter('all');
setSelectedResourceIds([]);
setPickerOpen(true);
}, []);
useImperativeHandle(
composerRef,
() => ({
insertReferences,
openPicker,
focus: () => editor.focus(),
}),
[editor, insertReferences],
[editor, insertReferences, openPicker],
);
useEffect(() => {
@@ -459,24 +470,21 @@ function ResourceReferenceEditor({
}
ErrorBoundary={LexicalErrorBoundary}
/>
<div className="resource-reference-input-actions">
<button
type="button"
className="resource-reference-input-at"
aria-label="插入素材引用"
title="插入素材引用"
disabled={disabled}
onMouseDown={(event) => event.preventDefault()}
onClick={() => {
setPickerQuery('');
setPickerFilter('all');
setSelectedResourceIds([]);
setPickerOpen(true);
}}
>
<AtSign size={15} aria-hidden="true" />
</button>
</div>
{showTriggerButton ? (
<div className="resource-reference-input-actions">
<button
type="button"
className="resource-reference-input-at"
aria-label="插入素材引用"
title="插入素材引用"
disabled={disabled}
onMouseDown={(event) => event.preventDefault()}
onClick={openPicker}
>
<AtSign size={15} aria-hidden="true" />
</button>
</div>
) : null}
<LexicalTypeaheadMenuPlugin<ResourceMentionOption>
options={mentionOptions}
triggerFn={triggerFn}
+74
View File
@@ -9032,6 +9032,80 @@ iframe.preview-frame {
background: #fff;
font-size: 12px;
/* Direct Codex composer: keep the editor and its actions in one vertical
structure instead of positioning each control independently. */
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer.is-direct-codex {
display: grid;
grid-template-columns: minmax(0, 1fr);
grid-template-rows: auto auto;
gap: 8px;
align-items: stretch;
position: absolute;
right: 0;
bottom: 0;
left: 0;
z-index: 5;
padding: 0;
background: transparent;
pointer-events: none;
}
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer.is-direct-codex
.resource-reference-input {
width: 100%;
min-height: 72px;
padding: 12px 15px 10px;
pointer-events: auto;
}
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer.is-direct-codex
.project-supervisor-composer-controls {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 8px;
min-width: 0;
pointer-events: auto;
}
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer.is-direct-codex
.project-supervisor-composer-controls
.conversation-model-select {
position: static !important;
right: auto !important;
bottom: auto !important;
max-width: min(220px, 40%);
}
.game-workbench-chat
.project-supervisor-surface.is-direct-codex
.project-supervisor-composer.is-direct-codex
.project-supervisor-composer-controls
> button {
position: static !important;
right: auto !important;
bottom: auto !important;
display: grid;
width: 30px;
min-width: 30px;
height: 30px;
min-height: 30px;
padding: 0;
border: 0;
border-radius: 9px;
background: var(--platform-button-primary-fill);
color: var(--platform-button-primary-text);
place-items: center;
}
.project-supervisor-composer .resource-reference-input {
min-height: 72px;
}