Files
Genarrative/src/components/image-editor/ImageCanvasSpecGenerationPanelView.tsx
T
JenkenB 17768119ea 恢复画布视频生成入口
恢复画布底部工具栏的视频生成和 UI 设计入口

恢复视频生成弹窗的参数、模型和提交状态处理

补齐规范参考、快捷键和生成流程相关测试
2026-06-17 23:05:17 +08:00

357 lines
13 KiB
TypeScript

import {
type CSSProperties,
type Dispatch,
type ReactNode,
type RefObject,
type SetStateAction,
} from 'react';
import { ImagePlus } from 'lucide-react';
import { PlatformActionButton } from '../common/PlatformActionButton';
import { PlatformFieldLabel } from '../common/PlatformFieldLabel';
import {
PlatformFloatingMenu,
PlatformFloatingMenuItem,
} from '../common/PlatformFloatingMenu';
import { PlatformStatusMessage } from '../common/PlatformStatusMessage';
import {
PlatformSelectField,
PlatformTextField,
} from '../common/PlatformTextField';
import {
CHARACTER_SPEC_VIEW_OPTIONS,
SPEC_GENERATION_COST,
} from './ImageCanvasGenerationModel';
import type {
GenerateDialogState,
SpecFormValues,
SpecGenerationType,
UploadTarget,
} from './ImageCanvasEditorTypes';
type ImageCanvasSpecGenerationPanelViewProps = {
dialog: GenerateDialogState;
style: CSSProperties;
isGenerationReferenceMenuOpen?: boolean;
generationReferenceButtonRef?: RefObject<HTMLButtonElement | null>;
setIsGenerationReferenceMenuOpen?: Dispatch<SetStateAction<boolean>>;
setIsPickingGenerationReferenceFromCanvas?: Dispatch<SetStateAction<boolean>>;
setGenerateDialog?: Dispatch<SetStateAction<GenerateDialogState | null>>;
renderEditorPortal?: (node: ReactNode) => ReactNode;
buildPortalMenuStyle?: (
anchor: HTMLElement | null,
placement: 'above' | 'below',
) => CSSProperties;
onOpenSpecDialog?: (specType: SpecGenerationType) => void;
onUpdateSpecFormValue: (key: keyof SpecFormValues, value: string) => void;
onRequestUpload: (target: UploadTarget) => void;
onSubmit: (dialog: GenerateDialogState) => void;
};
export function ImageCanvasSpecGenerationPanelView({
dialog,
style,
isGenerationReferenceMenuOpen = false,
generationReferenceButtonRef,
setIsGenerationReferenceMenuOpen,
setIsPickingGenerationReferenceFromCanvas,
setGenerateDialog,
renderEditorPortal = (node) => node,
buildPortalMenuStyle = () => ({}),
onOpenSpecDialog,
onUpdateSpecFormValue,
onRequestUpload,
onSubmit,
}: ImageCanvasSpecGenerationPanelViewProps) {
const isUiDesignDialog = dialog.mode === 'ui-design';
const referenceLabel = isUiDesignDialog ? 'UI设计图标素材规范' : '参考图';
const uploadTarget: UploadTarget = isUiDesignDialog
? 'ui-design-icon-spec'
: 'spec-reference';
const reference = isUiDesignDialog
? dialog.uiDesignSpecReference
: dialog.specReference;
const openReferenceMenu = () => {
if (setIsGenerationReferenceMenuOpen) {
setIsGenerationReferenceMenuOpen((open) => !open);
return;
}
onRequestUpload(uploadTarget);
};
return (
<>
<form
className="image-canvas-editor__generation-composer image-canvas-editor__generation-composer--image image-canvas-editor__spec-composer"
style={style}
role="dialog"
aria-label={isUiDesignDialog ? '生成UI设计图' : '生成规范'}
onPointerDown={(event) => event.stopPropagation()}
onSubmit={(event) => {
event.preventDefault();
if (dialog.status !== 'generating') {
onSubmit(dialog);
}
}}
>
<div className="image-canvas-editor__spec-fields">
{isUiDesignDialog ? (
<label className="image-canvas-editor__field-block">
<PlatformFieldLabel
variant="form"
className="image-canvas-editor__field-title"
>
UI设计要求
</PlatformFieldLabel>
<PlatformTextField
variant="textarea"
aria-label="UI设计要求"
value={dialog.prompt}
disabled={dialog.status === 'generating'}
placeholder="描述要生成的UI界面"
size="sm"
density="compact"
className="image-canvas-editor__generation-prompt"
onChange={(event) => {
const nextPrompt = event.target.value;
if (setGenerateDialog) {
setGenerateDialog((currentDialog) =>
currentDialog?.mode === 'ui-design'
? {
...currentDialog,
prompt: nextPrompt,
status:
currentDialog.status === 'failed'
? 'idle'
: currentDialog.status,
errorMessage:
currentDialog.status === 'failed'
? undefined
: currentDialog.errorMessage,
}
: currentDialog,
);
return;
}
onUpdateSpecFormValue('customPrompt', nextPrompt);
}}
/>
</label>
) : dialog.specType === 'custom' ? (
<label className="image-canvas-editor__field-block">
<PlatformFieldLabel
variant="form"
className="image-canvas-editor__field-title"
>
自定义规范提示词
</PlatformFieldLabel>
<PlatformTextField
variant="textarea"
aria-label="自定义规范提示词"
value={dialog.specValues?.customPrompt ?? ''}
disabled={dialog.status === 'generating'}
size="sm"
density="compact"
className="image-canvas-editor__spec-textarea"
onChange={(event) =>
onUpdateSpecFormValue('customPrompt', event.target.value)
}
/>
</label>
) : (
<>
<label className="image-canvas-editor__field-block">
<PlatformFieldLabel
variant="form"
className="image-canvas-editor__field-title"
>
玩法设定
</PlatformFieldLabel>
<PlatformTextField
aria-label="玩法设定"
value={dialog.specValues?.playSetting ?? ''}
disabled={dialog.status === 'generating'}
size="sm"
density="compact"
className="image-canvas-editor__spec-input"
onChange={(event) =>
onUpdateSpecFormValue('playSetting', event.target.value)
}
/>
</label>
<label className="image-canvas-editor__field-block">
<PlatformFieldLabel
variant="form"
className="image-canvas-editor__field-title"
>
美术风格
</PlatformFieldLabel>
<PlatformTextField
aria-label="美术风格"
value={dialog.specValues?.artStyle ?? ''}
disabled={dialog.status === 'generating'}
size="sm"
density="compact"
className="image-canvas-editor__spec-input"
onChange={(event) =>
onUpdateSpecFormValue('artStyle', event.target.value)
}
/>
</label>
{dialog.specType === 'character' ? (
<>
<label className="image-canvas-editor__field-block">
<PlatformFieldLabel
variant="form"
className="image-canvas-editor__field-title"
>
头身比
</PlatformFieldLabel>
<PlatformSelectField
aria-label="头身比"
value={dialog.specValues?.bodyRatio ?? '3'}
disabled={dialog.status === 'generating'}
size="sm"
density="compact"
className="image-canvas-editor__spec-input"
onChange={(event) =>
onUpdateSpecFormValue('bodyRatio', event.target.value)
}
>
{['2', '3', '4', '5', '6'].map((value) => (
<option key={value} value={value}>
{value}
</option>
))}
</PlatformSelectField>
</label>
<label className="image-canvas-editor__field-block">
<PlatformFieldLabel
variant="form"
className="image-canvas-editor__field-title"
>
角色视角
</PlatformFieldLabel>
<PlatformSelectField
aria-label="角色视角"
value={dialog.specValues?.characterView ?? ''}
disabled={dialog.status === 'generating'}
size="sm"
density="compact"
className="image-canvas-editor__spec-input"
onChange={(event) =>
onUpdateSpecFormValue('characterView', event.target.value)
}
>
{CHARACTER_SPEC_VIEW_OPTIONS.map((value) => (
<option key={value} value={value}>
{value}
</option>
))}
</PlatformSelectField>
</label>
</>
) : null}
</>
)}
{isUiDesignDialog || dialog.specType ? (
<div className="image-canvas-editor__field-block image-canvas-editor__generation-ref">
<PlatformFieldLabel
variant="form"
className="image-canvas-editor__field-title"
>
{referenceLabel}
</PlatformFieldLabel>
<button
ref={generationReferenceButtonRef}
type="button"
className="image-canvas-editor__character-spec-ref image-canvas-editor__reference-tile image-canvas-editor__reference-tile--spec"
disabled={dialog.status === 'generating'}
onClick={openReferenceMenu}
aria-label={referenceLabel}
>
<span className="image-canvas-editor__reference-tile-visual">
{reference ? (
<img src={reference.src} alt="" aria-hidden="true" />
) : (
<ImagePlus className="h-4 w-4" aria-hidden="true" />
)}
</span>
<span className="image-canvas-editor__reference-tile-copy">
{reference?.label ?? '添加参考图'}
</span>
</button>
</div>
) : null}
</div>
{dialog.status === 'failed' ? (
<PlatformStatusMessage
tone="error"
surface="platform"
size="xs"
className="image-canvas-editor__generate-status"
role="alert"
>
{dialog.errorMessage}
</PlatformStatusMessage>
) : null}
<div className="image-canvas-editor__generation-composer-footer image-canvas-editor__spec-footer">
<PlatformActionButton
type="submit"
tone="secondary"
size="sm"
className="image-canvas-editor__generation-submit image-canvas-editor__spec-submit"
disabled={dialog.status === 'generating'}
aria-label={isUiDesignDialog ? '生成UI设计图' : '提交生成规范'}
>
{dialog.status === 'generating'
? '生成中'
: `消耗${SPEC_GENERATION_COST}泥点 · 生成`}
</PlatformActionButton>
</div>
</form>
{isGenerationReferenceMenuOpen && generationReferenceButtonRef
? renderEditorPortal(
<PlatformFloatingMenu
className="image-canvas-editor__spec-menu image-canvas-editor__portal-menu"
label="参考图来源"
placement="top-start"
style={buildPortalMenuStyle(
generationReferenceButtonRef.current,
'above',
)}
>
<PlatformFloatingMenuItem
onClick={() => {
setIsGenerationReferenceMenuOpen?.(false);
setIsPickingGenerationReferenceFromCanvas?.(true);
}}
>
从画布中选择
</PlatformFloatingMenuItem>
{isUiDesignDialog ? (
<PlatformFloatingMenuItem
onClick={() => {
setIsGenerationReferenceMenuOpen?.(false);
onOpenSpecDialog?.('icon');
}}
>
新建图标素材规范
</PlatformFloatingMenuItem>
) : null}
<PlatformFloatingMenuItem
onClick={() => {
setIsGenerationReferenceMenuOpen?.(false);
setIsPickingGenerationReferenceFromCanvas?.(false);
onRequestUpload(uploadTarget);
}}
>
上传图片
</PlatformFloatingMenuItem>
</PlatformFloatingMenu>,
)
: null}
</>
);
}