17768119ea
恢复画布底部工具栏的视频生成和 UI 设计入口 恢复视频生成弹窗的参数、模型和提交状态处理 补齐规范参考、快捷键和生成流程相关测试
357 lines
13 KiB
TypeScript
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}
|
|
</>
|
|
);
|
|
}
|