import { AiToolImageCoverCard, AiToolSquareCoverCard } from '@aiComponents/CardItems';
import Loading from '@components/Loading';
import { requestAdvanceDrawSubStyleConfigCategory } from '@services/advanceDraw/config.ts';
import { useMount } from 'ahooks';
import classNames from 'classnames/bind';
import { floor } from 'lodash';
import type { Dispatch, SetStateAction } from 'react';
import { forwardRef, useCallback, useImperativeHandle, useMemo, useRef, useState } from 'react';
import type { GetAdvanceDrawLeafGenerateArgsByMidsType } from '@/pages/AiTools/AdvanceDraw/utils.ts';
import { getComfyUIDrawLeafGenerateArgsByMids } from '@/pages/AiTools/AdvanceDraw/utils.ts';
import type { AdvanceDrawStyleCategoryItem } from '@/types/advanceDraw';
import styles from './index.module.less';
const cx = classNames.bind(styles);
export type ModelRenderStyleCategoryGridRef = {
setFirstCategoryIndex: Dispatch<SetStateAction<number>>;
setSecondCategoryRequesting: Dispatch<SetStateAction<boolean>>;
getComfyUIDrawLeafGenerateArgsByMids: () =>
| GetAdvanceDrawLeafGenerateArgsByMidsType
| 'PreciseRendering';
};
type Props = {
categoryList: AdvanceDrawStyleCategoryItem[];
onCategoryListChange: (list: AdvanceDrawStyleCategoryItem[]) => void;
};
// 二级类目最小宽度
const MIN_SECOND_CATEGORY_CARD_WIDTH = 150;
// 二级类目间距
const SECOND_CATEGORY_GRID_GAP = 16;
// 三级类目最小宽度
const MIN_THIRD_CATEGORY_CARD_WIDTH = 120;
// 三级类目间距
const THIRD_CATEGORY_GRID_GAP = 12;
// 三级类目padding
const THIRD_CATEGORY_WRAP_PADDING = 16;
/**
* 模型渲染风格类目
*/
const ModelRenderStyleCategoryGrid = forwardRef<ModelRenderStyleCategoryGridRef, Props>(
({ categoryList, onCategoryListChange }, ref) => {
const secondCategoryRef = useRef<HTMLDivElement | null>(null);
// 二级类目 列数
const [secondCategoryGridColumns, setSecondCategoryGridColumns] = useState(0);
// 三 级类目列表
const [thirdCategoryGridColumns, setThirdCategoryGridColumns] = useState(0);
// 一级类目索引
const [firstCategoryIndex, setFirstCategoryIndex] = useState(0);
// 二级类目索引
const [secondCategoryIndex, setSecondCategoryIndex] = useState(-1);
// 三级类目索引
const [thirdCategoryIndex, setThirdCategoryIndex] = useState(0);
// 二级类目请求中
const [secondCategoryRequesting, setSecondCategoryRequesting] = useState(false);
// 所有层级分类列表
const { secondCategoryList, thirdCategoryList } = useMemo(() => {
if (!categoryList.length) {
return {
secondCategoryList: [],
thirdCategoryList: [],
};
}
// 二级类目列表
const secondCategoryList = categoryList[firstCategoryIndex]?.children || [];
// 三级类目列表
const thirdCategoryList = secondCategoryList[secondCategoryIndex]?.children || [];
return {
secondCategoryList,
thirdCategoryList,
};
}, [categoryList, firstCategoryIndex, secondCategoryIndex]);
// 计算列数
const calculateColumns = useCallback(() => {
if (secondCategoryRef.current?.offsetWidth) {
const secondContainerWidth = secondCategoryRef.current?.offsetWidth;
const thirdContainerWidth =
secondCategoryRef.current?.offsetWidth - THIRD_CATEGORY_WRAP_PADDING * 2;
// 计算列数
setSecondCategoryGridColumns(
floor(
(secondContainerWidth + SECOND_CATEGORY_GRID_GAP) /
(MIN_SECOND_CATEGORY_CARD_WIDTH + SECOND_CATEGORY_GRID_GAP),
),
);
setThirdCategoryGridColumns(
floor(
(thirdContainerWidth + THIRD_CATEGORY_GRID_GAP) /
(MIN_THIRD_CATEGORY_CARD_WIDTH + THIRD_CATEGORY_GRID_GAP),
),
);
}
}, []);
useMount(() => {
calculateColumns();
});
useImperativeHandle(ref, () => ({
setFirstCategoryIndex,
setSecondCategoryRequesting,
getComfyUIDrawLeafGenerateArgsByMids: () => {
if (secondCategoryIndex === -1) {
return 'PreciseRendering';
}
const firstCategory = categoryList[firstCategoryIndex];
const secondeCategory = firstCategory.children?.[secondCategoryIndex];
const thirdCategory = secondeCategory?.children?.[thirdCategoryIndex];
const selectMids = [firstCategory.mid];
if (secondeCategory) {
selectMids.push(secondeCategory.mid);
}
if (thirdCategory) {
selectMids.push(thirdCategory.mid);
}
return getComfyUIDrawLeafGenerateArgsByMids(categoryList, selectMids);
},
}));
return (
<>
<div className={cx('first_category_wrap')}>
{categoryList.map((value, index) => (
<div
key={value.mid}
className={cx('first_category_item', {
active: index === firstCategoryIndex,
})}
onClick={async () => {
setFirstCategoryIndex(index);
setSecondCategoryIndex(-1);
setThirdCategoryIndex(0);
if (!value.children && value.justCategory) {
try {
setSecondCategoryRequesting(true);
const subCategoryList = await requestAdvanceDrawSubStyleConfigCategory(
value.mid,
);
// 替换当前类目下的子类目信息
onCategoryListChange(
categoryList.map((value1, index1) => {
if (index1 === index) {
return {
...value1,
children: subCategoryList,
};
}
return value1;
}),
);
setSecondCategoryRequesting(false);
} catch (e) {
setSecondCategoryRequesting(false);
console.error(e);
}
}
}}
>
{value.name}
</div>
))}
</div>
{secondCategoryRequesting && <Loading height={200} iconWidth={100} />}
<div
className={cx('second_category_wrap')}
style={{
gridTemplateColumns: `repeat(${secondCategoryGridColumns}, 1fr)`,
}}
ref={secondCategoryRef}
>
{/* 二级类目前新增精确渲染 */}
<div
className={cx('second_category_item', {
active: -1 === secondCategoryIndex,
})}
onClick={() => {
setSecondCategoryIndex(-1);
setThirdCategoryIndex(0);
}}
>
<AiToolSquareCoverCard
coverUrl={
firstCategoryIndex === 0
? 'https://gd-hbimg.huaban.com/3bc374cfcf042c25555da1aff0f76adec1ec6bbd9dd70-2wtBwE_fw1200webp'
: 'https://gd-hbimg.huaban.com/f4a66d792060691660dff9fdab52b176cf2db0c367f2a-teB6Pa_fw658webp'
}
extra={
<>
<div className={cx('second_category_recommended')}>
<span>推荐</span>
</div>
<span className={cx('second_category_name')}>精确渲染</span>
</>
}
/>
</div>
{secondCategoryList.map((value, index) => (
<div
className={cx('second_category_item', {
active: index === secondCategoryIndex,
})}
key={value.mid}
onClick={() => {
setSecondCategoryIndex(index);
setThirdCategoryIndex(0);
}}
>
<AiToolSquareCoverCard
coverUrl={value.coverUrl}
extra={<span className={cx('second_category_name')}>{value.name}</span>}
/>
</div>
))}
</div>
{!!thirdCategoryList?.length && (
<div
className={cx('third_category_wrap')}
style={{
gridTemplateColumns: `repeat(${thirdCategoryGridColumns}, 1fr)`,
gridColumn: `1 / span ${secondCategoryGridColumns}`,
order:
(floor(secondCategoryIndex / secondCategoryGridColumns) + 1) *
secondCategoryGridColumns *
5 -
1,
}}
>
<div
className={cx('triangle_icon')}
style={{
left: `calc(${floor((((secondCategoryIndex + 1) % secondCategoryGridColumns || secondCategoryGridColumns) / secondCategoryGridColumns - 1 / (secondCategoryGridColumns * 2)) * 100, 2)}% - 6px)`,
}}
/>
{thirdCategoryList.map((item, index) => {
return (
<div
key={index}
style={{
order: index * 5,
}}
>
<AiToolImageCoverCard
active={thirdCategoryIndex === index}
coverUrl={item.coverUrl || ''}
label={item.name}
onClick={() => {
setThirdCategoryIndex(index);
}}
/>
</div>
);
})}
</div>
)}
</>
);
},
);
export default ModelRenderStyleCategoryGrid;import ToolBenefitOperator from '@aiComponents/BenefitOperator';
import AiToolBlockItem from '@aiComponents/BlockItem';
import { AiToolSquareCoverCard } from '@aiComponents/CardItems';
import type { DrawStepOperateLayoutPcRef } from '@aiComponents/DrawStepOperateLayout/pc';
import DrawStepOperateLayoutPC from '@aiComponents/DrawStepOperateLayout/pc';
import AiToolGenerateModal, { type AiToolGenerateModalRef } from '@aiComponents/GenerateModal';
import ToolVerticalIntroduceLayoutPC2 from '@aiComponents/IntroduceLayout/pc/vertical2';
import ToolVerticalIntroduceLayoutContent1 from '@aiComponents/IntroduceLayout/pc/vertical2/componetns/content1';
import ToggleGenerateInfoPanelItem from '@aiComponents/ToggleInfoPanel/PanelItem.tsx';
import type { MaterialIntroduceModalRef } from '@aiDrawComponents/ControlNet/BaseImage/MaterialIntroduceModal.tsx';
import MaterialIntroduceModal from '@aiDrawComponents/ControlNet/BaseImage/MaterialIntroduceModal.tsx';
import { QuestionCircleOutlined } from '@ant-design/icons';
import IconFont from '@components/Iconfont';
import type { ImageSelectorWrapModalRef } from '@components/ImageSelectorWrapModal';
import ImageSelectorWrapModal from '@components/ImageSelectorWrapModal';
import ThemeConfigProvider from '@components/ThemeConfigProvider';
import ThemeSegmented from '@components/ThemeSegmented';
import ThemeTextArea from '@components/ThemeTextArea';
import ToolEquityCountWrap, { type ToolEquityCountWrapRef } from '@components/ToolEquityCountWrap';
import {
requestAdvanceDrawFirstStyleCategoryList,
requestAdvanceDrawSubStyleConfigCategory,
} from '@services/advanceDraw/config.ts';
import {
requestComfyUIDrawGenerate,
type RequestComfyUIDrawParams,
} from '@services/advanceDraw/generate.ts';
import type { RequestModelRenderImageParams } from '@services/comfyUI.ts';
import { requestModelRenderImage } from '@services/comfyUI.ts';
import { getRandomNum, mergeDrawPromptStacks } from '@utils/ai.ts';
import { decodeImageUrl } from '@utils/string.ts';
import { getImageSizeInfo } from '@wander/base-utils';
import { CoolImage } from '@wander/common-components';
import { useAsyncEffect, useMount } from 'ahooks';
import { Collapse, Image, message, Typography } from 'antd';
import classNames from 'classnames/bind';
import type { CSSProperties } from 'react';
import { useRef, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { useSearchParams } from 'react-router-dom';
import type { ImageSelectorModalProps } from '@/components/ImageSelectorModal';
import UploadImageCard, {
type UploadImageCardProps,
type UploadImageCardRef,
} from '@/components/UploadImageCard';
import { enlargeDownloadOptions } from '@/config/aiTool.ts';
import useGetConfigValue from '@/hooks/useGetConfigValue.ts';
import { ADVANCE_DRAW_DEFAULT_NEGATIVE_KEYWORDS } from '@/pages/AiTools/AdvanceDraw/constants.ts';
import type { GetAdvanceDrawLeafGenerateArgsByMidsType } from '@/pages/AiTools/AdvanceDraw/utils.ts';
import KjlResultQuickAccessBox from '@/pages/AiTools/components/KjlResultQuickAccessBox';
import ToggleGenerateInfoPopover from '@/pages/AiTools/components/ToggleInfoPanel';
import { lockingMaterialList } from '@/pages/AiTools/Draw/configs.ts';
import {
modelRenderSpeedupKey,
modelRenderToolKey,
modelRenderToolName,
TRACK_MODEL_RENDER_NAME,
} from '@/pages/AiTools/ModelRender/config.ts';
import type { ModelRenderStyleCategoryGridRef } from '@/pages/AiTools/ModelRender/Operate/components/StyleCategoryGrid';
import ModelRenderStyleCategoryGrid from '@/pages/AiTools/ModelRender/Operate/components/StyleCategoryGrid';
import { useToolRouterStore } from '@/pages/AiTools/Router/store/createStore.ts';
import { useStore } from '@/store/createStore.ts';
import type { AdvanceDrawStyleCategoryItem } from '@/types/advanceDraw.ts';
import type { AiToolProcessSuccessResponse } from '@/types/ai';
import type { LockingMaterialType } from '@/types/draw';
import { LockingMaterialTypeEnum } from '@/types/draw';
import styles from './index.module.less';
const cx = classNames.bind(styles);
const { Paragraph } = Typography;
const { Panel } = Collapse;
// 空间位置显示
type SpatialPositionListItem = {
name: string;
coverUrl: string;
// 描述
description: string;
};
const ModelRenderOperatePC = () => {
const { t } = useTranslation();
const addBaseImage = useToolRouterStore((state) => state.addBaseImage);
const { productType } = useStore((state) => state.productInfo);
const [searchParams, setSearchParams] = useSearchParams();
// 介绍页显示
const [introduceLayoutShow, setIntroduceLayoutShow] = useState(
productType === 'JKJL' && !searchParams.get('url'),
);
// 步骤条ref
const drawStepOperateLayoutRef = useRef<DrawStepOperateLayoutPcRef>(null);
// 底图上传卡片ref
const baseImageCardRef = useRef<UploadImageCardRef>(null);
// 风格参考图ref
const styleImageCardRef = useRef<UploadImageCardRef>(null);
// 迁移内容的原图片效果图库
const imageSelectorWrapModalRef = useRef<ImageSelectorWrapModalRef>(null);
// 锁定材质弹窗ref
const materialIntroduceModalRef = useRef<MaterialIntroduceModalRef>(null);
// 空间位置列表
const spatialPositionConfig =
useGetConfigValue<Record<string, SpatialPositionListItem[]>>({
key: 'jzxz_model_render_spatial_config',
}) || {};
// 风格类目配置
const styleCategoryMids: string[] =
useGetConfigValue<string[][]>({
key: 'jzxz_model_render_style_categories_config',
})?.map((value) => value[0]) || [];
// 空间位置选中索引
const [selectPositionIndex, setSelectPositionIndex] = useState(0);
// 空间位置类目选中索引 (类目可以切换 然后需要分开存储)
const [spatialCategoryIndex, setSpatialCategoryIndex] = useState(0);
// 高级风格类目
const [styleCategoryList, setStyleCategoryList] = useState<AdvanceDrawStyleCategoryItem[]>([]);
// 锁定材质
const [lockingMaterialType, setLockingMaterialType] = useState<LockingMaterialType>('OFF');
// 描述词
const [description, setDescription] = useState('');
// 存储生成参数
const [generateParams, setGenerateParams] = useState({
baseImage: '',
styleImage: '',
styleAlias: [] as string[],
});
// 刷新工具次数
const refreshToolEquityUserUseInfo = useStore((state) => state.refreshToolEquityUserUseInfo);
// comfyUI 创作弹框-高级版
const generateComfyUiModalRef = useRef<AiToolGenerateModalRef<RequestComfyUIDrawParams>>(null);
// comfyUI 创作弹框-精确
const generateComfyUiPreciseRenderingModalRef =
useRef<AiToolGenerateModalRef<RequestModelRenderImageParams>>(null);
// 工具次数ref
const toolEquityCountWrapRef = useRef<ToolEquityCountWrapRef>(null);
// 类目ref
const styleCategoryGridRef = useRef<ModelRenderStyleCategoryGridRef>(null);
// 图片上传成功
const onBaseImageFinish: ImageSelectorModalProps['onFinish'] = (item) => {
baseImageCardRef.current?.setUploadImage(item);
};
// 底图改变事件
const onBaseImageChange: UploadImageCardProps['onUploadImageChange'] = (uploadImage) => {
if (uploadImage) {
addBaseImage({
url: uploadImage.largeUrl,
width: uploadImage.width,
height: uploadImage.height,
});
} else {
addBaseImage(null);
}
};
// 响应-高级版
const onComfyUiGenerateSuccess = async (response: AiToolProcessSuccessResponse) => {
try {
const result = response.imageList[0];
// 刷新次数
refreshToolEquityUserUseInfo(modelRenderToolKey);
const sizeInfo = await getImageSizeInfo(result);
// 设置layout显示
drawStepOperateLayoutRef.current?.setGenerateImage({
url: result,
...sizeInfo,
});
} catch (e) {
console.error(e);
}
};
// 精确渲染
const generatePreciseRendering = () => {
const baseImage = baseImageCardRef.current?.uploadImage;
if (!baseImage) {
return;
}
const styleImage = styleImageCardRef.current?.uploadImage;
drawStepOperateLayoutRef.current?.setShowType('PREVIEW');
setGenerateParams({
baseImage: baseImage.largeUrl || '',
styleImage: styleImage?.largeUrl || '',
styleAlias: [],
});
generateComfyUiPreciseRenderingModalRef.current?.generateTask({
seed: getRandomNum(),
baseImageUrl: baseImage.largeUrl || '',
});
};
// 高级生图渲染
const generateStandardRendering = (styleArgs: GetAdvanceDrawLeafGenerateArgsByMidsType) => {
const baseImage = baseImageCardRef.current?.uploadImage;
if (!baseImage) {
return;
}
const styleImage = styleImageCardRef.current?.uploadImage;
const {
modelName = '',
loraParamses = [],
builtStyleReferImage,
exactRender,
styleAlias,
promptPrefix = '',
negativePromptPrefix = '',
samplerName = 'euler',
scheduler = 'normal',
steps = 30,
cfg = 3.5,
} = styleArgs || {};
const dealPromptsInfo = mergeDrawPromptStacks({
prompts: {
placeholder: description,
keyword: description,
},
promptsPrefix: {
placeholder: '',
keyword: `${promptPrefix},${spatialPositionConfig[styleCategoryList[spatialCategoryIndex].mid][selectPositionIndex].description},${styleAlias?.[0]}`,
},
negativePromptsPrefix: {
placeholder: '',
keyword: negativePromptPrefix,
},
defaultNegativeKeyword: ADVANCE_DRAW_DEFAULT_NEGATIVE_KEYWORDS,
});
drawStepOperateLayoutRef.current?.setShowType('PREVIEW');
setGenerateParams({
baseImage: baseImage.largeUrl || '',
styleImage: styleImage?.largeUrl || '',
styleAlias: styleAlias || [],
});
generateComfyUiModalRef.current?.generateTask({
// 精确渲染
exactRender: exactRender || false,
baseImage: {
url: baseImage.largeUrl,
width: baseImage.width,
height: baseImage.height,
guidanceStart: 0,
guidanceEnd: 1,
weight: 0.6,
model: 'XL/xinsir-controlnet-union-sdxl-1.0_promax.safetensors',
...((styleAlias || []).findIndex((value) => value.search('通用') > -1) > -1
? {
preprocessor: 'none',
controlNetKey: 'fineOutline',
}
: {
preprocessor: 'LineArtPreprocessor',
controlNetKey: 'smoothSontours',
}),
enable: true,
},
...(styleImage
? {
styleReferImage: {
url: styleImage.largeUrl,
weight: 0.6,
guidanceStart: 0,
guidanceEnd: 1,
},
}
: {}),
sizeType: 'ULTRA_2K',
seed: getRandomNum(),
builtStyleReferImage,
prompt: dealPromptsInfo.prompts.keyword,
negativePrompt: dealPromptsInfo.negativePrompts.keyword,
modelName,
loraParamses,
// 材质锁定配置项
lockingMaterialType,
enableSpatialDepth: false,
cfg,
// 迭代步数
steps,
// 采样器名称
samplerName,
// 调度器
scheduler,
});
};
// 点击开始创作
const onStepStart = () => {
if (!toolEquityCountWrapRef.current?.verifyUseStatus()) {
return;
}
const styleArgs = styleCategoryGridRef.current?.getComfyUIDrawLeafGenerateArgsByMids?.();
if (styleArgs === 'PreciseRendering') {
generatePreciseRendering();
} else {
generateStandardRendering(styleArgs as GetAdvanceDrawLeafGenerateArgsByMidsType);
}
};
// 点击一级类目
useAsyncEffect(async () => {
let categoryList = await requestAdvanceDrawFirstStyleCategoryList();
categoryList = categoryList.filter((value) => styleCategoryMids.includes(value.mid));
if (categoryList[0].justCategory) {
const subCategoryList = await requestAdvanceDrawSubStyleConfigCategory(categoryList[0].mid);
// 替换当前类目下的子类目信息
categoryList.splice(0, 1, {
...categoryList[0],
children: subCategoryList,
});
}
setStyleCategoryList(categoryList);
}, []);
useMount(() => {
const url = decodeImageUrl(searchParams.get('url') || '');
const width = searchParams.get('width');
const height = searchParams.get('height');
// 工具快捷使用
if (url && height && width) {
baseImageCardRef.current?.setUploadImage({
largeUrl: url,
thumbUrl: url,
width: Number(width),
height: Number(height),
});
setSearchParams({});
}
});
// 第二步内容
const secondStepRender = (
<>
<div className={cx('first_category_wrap')}>
{styleCategoryList.map((value, index) => (
<div
key={value.mid}
className={cx('first_category_item', {
active: index === spatialCategoryIndex,
})}
onClick={async () => {
setSpatialCategoryIndex(index);
styleCategoryGridRef.current?.setFirstCategoryIndex(index);
if (!value.children && value.justCategory) {
try {
styleCategoryGridRef.current?.setSecondCategoryRequesting(true);
const subCategoryList = await requestAdvanceDrawSubStyleConfigCategory(value.mid);
// 替换当前类目下的子类目信息
setStyleCategoryList(
styleCategoryList.map((value1, index1) => {
if (index1 === index) {
return {
...value1,
children: subCategoryList,
};
}
return value1;
}),
);
styleCategoryGridRef.current?.setSecondCategoryRequesting(false);
} catch (e) {
styleCategoryGridRef.current?.setSecondCategoryRequesting(false);
console.error(e);
}
}
}}
>
{value.name}
</div>
))}
</div>
<div className={cx('spatial_position_wrap')}>
{spatialPositionConfig[styleCategoryList[spatialCategoryIndex]?.mid || '']?.map(
(value, index) => (
<div
className={cx('spatial_position_item', {
active: index === selectPositionIndex,
})}
key={index}
onClick={() => {
setSelectPositionIndex(index);
}}
>
<AiToolSquareCoverCard
coverUrl={value.coverUrl}
extra={<span className={cx('spatial_position_name')}>{value.name}</span>}
/>
</div>
),
)}
</div>
</>
);
// 第三步内容
const thirdStepRender = (
<>
<ModelRenderStyleCategoryGrid
ref={styleCategoryGridRef}
categoryList={styleCategoryList}
onCategoryListChange={(list) => {
setStyleCategoryList(list);
}}
/>
<div className={cx('step_title')} style={{ marginTop: 40 }}>
更多设置
</div>
<div className={cx('step_sub_title')}>
上传风格参考图和添加更多设置可以定制化生成对应风格,进一步加强生成图的风格准确性,此处做选填。
</div>
<Collapse ghost expandIconPosition="end">
<Panel header="上传参考图(选填)" key="reference">
<div className={cx('reference_image_wrap')}>
<UploadImageCard>
{() => <UploadImageCard.Card hideDeleteBtn={false} ref={styleImageCardRef} />}
</UploadImageCard>
</div>
</Panel>
<Panel header="更多设置(选填)" key="more">
<div className={cx('more_settings_wrap')}>
<ThemeConfigProvider
themeConfigs={{
JKJL: {
textArea: {
borderColor: '#dfdfdf',
borderWidth: '1px',
},
segmented: {
'--border-width': '1px',
'--border-color': '#dfdfdf',
} as CSSProperties,
},
}}
>
<AiToolBlockItem title="描述词">
<ThemeTextArea
placeholder="写下你的绘画想法和创意"
value={description}
onChange={(e) => {
setDescription(e.target.value);
}}
/>
</AiToolBlockItem>
<AiToolBlockItem
title={
<span>
{t('AiTools-Draw-ControlNet-BaseImage-WREs5')}
<QuestionCircleOutlined
style={{ marginLeft: 4 }}
onClick={() => {
materialIntroduceModalRef.current?.setModalStatus('INTRODUCE');
}}
/>
</span>
}
extra={
<ThemeSegmented
block
style={{ width: 180 }}
value={lockingMaterialType}
options={lockingMaterialList.map((item) => ({
label: t(item.labelKey),
value: item.value,
}))}
onChange={(item) => {
setLockingMaterialType(item.value);
}}
/>
}
/>
</ThemeConfigProvider>
</div>
</Panel>
</Collapse>
</>
);
// 生成参数弹窗
const generateParamsContent = (
<div>
<ToggleGenerateInfoPanelItem title="创意描述">
{() => {
return (
<Paragraph ellipsis={{ rows: 4 }}>
<span style={{ margin: '10px 0 0', color: 'rgb(255 255 255 / 80%)' }}>
{description || '无'}
</span>
</Paragraph>
);
}}
</ToggleGenerateInfoPanelItem>
<ToggleGenerateInfoPanelItem
title="创作类目"
description={generateParams.styleAlias?.join('/')}
/>
<ToggleGenerateInfoPanelItem
title="空间位置"
description={
spatialPositionConfig[styleCategoryList[spatialCategoryIndex]?.mid || '']?.[
selectPositionIndex
].name || ''
}
/>
<ToggleGenerateInfoPanelItem title="底图">
{(imageCardClassName) => {
return (
<div className={imageCardClassName}>
<Image
src={generateParams.baseImage}
preview
width="100%"
height={154}
style={{
objectFit: 'contain',
}}
/>
</div>
);
}}
</ToggleGenerateInfoPanelItem>
{generateParams.styleImage && (
<ToggleGenerateInfoPanelItem title="参考图">
{(imageCardClassName) => {
return (
<div className={imageCardClassName}>
<Image
src={generateParams.styleImage}
preview
width="100%"
height={154}
style={{
objectFit: 'contain',
}}
/>
</div>
);
}}
</ToggleGenerateInfoPanelItem>
)}
<ToggleGenerateInfoPanelItem
title="材质锁定"
description={t(LockingMaterialTypeEnum[lockingMaterialType || 'OFF'])}
/>
</div>
);
return (
<>
<ToolVerticalIntroduceLayoutPC2
toolName={modelRenderToolName}
description="一键AI渲染酷家乐室内设计模型,比传统渲染方式更加真实,细腻,高效~支持多种室内风格渲染!"
show={introduceLayoutShow}
onClickUseBtn={() => {
setIntroduceLayoutShow(false);
}}
content={
<>
<ToolVerticalIntroduceLayoutContent1
items={[
{
beforeUrl:
'https://gd-hbimg.huaban.com/dc74bbd45daaaf4973d71e64785ed7e2d6cb0eed5ec75-7Gc6aa',
afterUrl:
'https://gd-hbimg.huaban.com/63a044eec1c55566f7304e79d1f1c2828c51c8ba9d774-4jCT7E',
},
{
beforeUrl:
'https://gd-hbimg.huaban.com/ffefde120630092f0244bf346c1d974e51e5dd9bb5fe2-NvzNpK',
afterUrl:
'https://gd-hbimg.huaban.com/2d776cae122ab64c90697906540a706664ce21809d1e6-XyYWf1',
},
{
beforeUrl:
'https://gd-hbimg.huaban.com/6519e79f2c37ee2a1b75025376b10f6d574edfc4f0d17-8ReO7g',
afterUrl:
'https://gd-hbimg.huaban.com/82c0c13e8abcb63243697acde385fc569b9b3fd5ba214-nh471F',
},
]}
/>
</>
}
/>
<UploadImageCard>
{(uploadImage) => (
<>
<DrawStepOperateLayoutPC
trackToolName={TRACK_MODEL_RENDER_NAME}
ref={drawStepOperateLayoutRef}
downloadConfig={{
enlargeDownloadOptions,
}}
items={[
{
title: '上传模型底图',
subTitle: '大师级渲染,从上传底图开始!',
content: (
<div className={cx('first_content')}>
<UploadImageCard.Card
style={{
minHeight: '100%',
height: 'auto',
}}
ref={baseImageCardRef}
onUploadImageChange={onBaseImageChange}
enableKjlUpload
uploadImageIcon={{
customUploadBtn: (className) => {
return (
<div
className={className}
onClick={() => {
imageSelectorWrapModalRef.current?.setModalStatus('SELECT_MODAL');
}}
>
{t('HuabanUploadBtn-rWrt')}
</div>
);
},
}}
onScreenshotSuccess={(value) => {
imageSelectorWrapModalRef.current?.setModalStatus('LEAFER_FRAME');
imageSelectorWrapModalRef.current?.onGenerateFrame({
width: value.width,
height: value.height,
url: value.largeUrl,
});
setTimeout(() => {
imageSelectorWrapModalRef.current?.setOperationType('CLIP_FRAME');
}, 600);
}}
hidden={!!uploadImage}
/>
<div className={cx('image_wrap')} hidden={!uploadImage}>
<CoolImage
placeholder={{ type: 'loadingIcon' }}
className={cx('image')}
style={{ objectFit: 'contain' }}
src={uploadImage?.largeUrl}
alt=""
/>
</div>
<div className={cx('operator_btn_wrap')} hidden={!uploadImage}>
<IconFont
type="micro-icon-edit"
className={cx('operator_icon')}
onClick={() => {
imageSelectorWrapModalRef.current?.setModalStatus('LEAFER_FRAME');
// 画板没有原图信息的时候 先创建画板
const originImageInfo =
imageSelectorWrapModalRef.current?.getOriginalImageInfo();
if (!originImageInfo?.url && uploadImage) {
imageSelectorWrapModalRef.current?.onGenerateFrame({
url: uploadImage?.largeUrl,
width: uploadImage.width,
height: uploadImage.height,
});
}
}}
/>
<IconFont
type="micro-icon-trash-can"
className={cx('operator_icon')}
onClick={() => {
baseImageCardRef.current?.setUploadImage(null);
imageSelectorWrapModalRef.current?.clearFrame();
}}
/>
</div>
</div>
),
beforeNextStep: async () => {
if (!baseImageCardRef.current?.uploadImage) {
message.warn('请先上传底图');
return false;
}
return true;
},
},
{
title: '选择空间位置',
subTitle: '选择正确的位置可以确保生成图更准确。',
content: secondStepRender,
},
{
title: '选择渲染风格',
subTitle: '选择想要生成的风格大模型,可以让生成结果更偏向自己的喜好。',
content: thirdStepRender,
},
]}
onStepStart={onStepStart}
uploadImage={uploadImage}
resultExtra={
<>
{/* 快捷使用 */}
<KjlResultQuickAccessBox
className={cx('quick_access_box')}
extraBottomItems={[
{
title: '迭代渲染',
iconKey: 'micro-icon-iterative-render',
onClick: () => {
const generateImage = drawStepOperateLayoutRef.current?.generateImage;
if (generateImage) {
drawStepOperateLayoutRef.current.setShowType('STEP');
drawStepOperateLayoutRef.current.setCurrent(0);
baseImageCardRef.current?.setUploadImage({
thumbUrl: generateImage.url,
largeUrl: generateImage.url,
width: generateImage.width,
height: generateImage.height,
});
}
},
},
]}
/>
{/* 参数明细 */}
<ToggleGenerateInfoPopover
placement="leftTop"
className={cx('generate_params_wrap')}
content={generateParamsContent}
>
{(open) => {
return <IconFont type={open ? 'micro-icon-close' : 'micro-icon-tip'} />;
}}
</ToggleGenerateInfoPopover>
</>
}
stepFooterExtra={
<ToolEquityCountWrap
toolKey={modelRenderToolKey}
showToolName={modelRenderToolName}
trackToolName={TRACK_MODEL_RENDER_NAME}
ref={toolEquityCountWrapRef}
>
<ToolBenefitOperator
style={{
marginTop: 8,
}}
toolKey={modelRenderToolKey}
trackToolName={TRACK_MODEL_RENDER_NAME}
showToolEquityInfo={() => {
toolEquityCountWrapRef.current?.showToolEquityInfo();
}}
showToolCountUsageIntro={() => {
toolEquityCountWrapRef.current?.showToolCountUsageIntro();
}}
showToolBuyCount={() => {
toolEquityCountWrapRef.current?.showToolBuyCount();
}}
/>
</ToolEquityCountWrap>
}
/>
</>
)}
</UploadImageCard>
{/* 迁移内容的原图片效果图库 */}
<ImageSelectorWrapModal
ref={imageSelectorWrapModalRef}
queryTagImages={{
categoryList: [
{
name: '室内',
tag: '室内底图',
sourceType: 'JZXZ',
},
],
introduce: t('AiTools-DecorationDesign-Operate-pc-f4eJ'),
modalTitle: t('AiTools-Draw-ControlNet-BaseImage-b2tNh'),
iconClassName: cx('style_refer_image'),
}}
onFinish={onBaseImageFinish}
hideDiyDraw
/>
<AiToolGenerateModal
displayType="MASK"
className={cx('generate_mask_container')}
customerRef={generateComfyUiModalRef}
toolKey={modelRenderToolKey}
trackToolName={TRACK_MODEL_RENDER_NAME}
request={requestComfyUIDrawGenerate}
speedupKey={modelRenderSpeedupKey}
pollingConfig={{
processingType: 'COMFYUI_COMPOSE',
imageDetectMode: 'NONE',
}}
onGenerateSuccess={onComfyUiGenerateSuccess}
onCancel={() => {
// 蒙层关闭 回到进度展示
drawStepOperateLayoutRef.current?.setShowType('STEP');
}}
/>
{/* 精确渲染 */}
<AiToolGenerateModal
displayType="MASK"
className={cx('generate_mask_container')}
customerRef={generateComfyUiPreciseRenderingModalRef}
toolKey={modelRenderToolKey}
trackToolName={TRACK_MODEL_RENDER_NAME}
request={requestModelRenderImage}
speedupKey={modelRenderSpeedupKey}
pollingConfig={{
processingType: 'COMFYUI_COMPOSE',
imageDetectMode: 'NONE',
}}
onGenerateSuccess={onComfyUiGenerateSuccess}
onCancel={() => {
// 蒙层关闭 回到进度展示
drawStepOperateLayoutRef.current?.setShowType('STEP');
}}
/>
<MaterialIntroduceModal ref={materialIntroduceModalRef} />
</>
);
};
export default ModelRenderOperatePC;
如果secondCategoryIndex === -1,这部分隐藏 <div className={cx('step_title')} style={{ marginTop: 40 }}>
更多设置
</div>
<div className={cx('step_sub_title')}>
上传风格参考图和添加更多设置可以定制化生成对应风格,进一步加强生成图的风格准确性,此处做选填。
</div>
<Collapse ghost expandIconPosition="end">
<Panel header="上传参考图(选填)" key="reference">
<div className={cx('reference_image_wrap')}>
<UploadImageCard>
{() => <UploadImageCard.Card hideDeleteBtn={false} ref={styleImageCardRef} />}
</UploadImageCard>
</div>
</Panel>
<Panel header="更多设置(选填)" key="more">
<div className={cx('more_settings_wrap')}>
<ThemeConfigProvider
themeConfigs={{
JKJL: {
textArea: {
borderColor: '#dfdfdf',
borderWidth: '1px',
},
segmented: {
'--border-width': '1px',
'--border-color': '#dfdfdf',
} as CSSProperties,
},
}}
>
<AiToolBlockItem title="描述词">
<ThemeTextArea
placeholder="写下你的绘画想法和创意"
value={description}
onChange={(e) => {
setDescription(e.target.value);
}}
/>
</AiToolBlockItem>
<AiToolBlockItem
title={
<span>
{t('AiTools-Draw-ControlNet-BaseImage-WREs5')}
<QuestionCircleOutlined
style={{ marginLeft: 4 }}
onClick={() => {
materialIntroduceModalRef.current?.setModalStatus('INTRODUCE');
}}
/>
</span>
}
extra={
<ThemeSegmented
block
style={{ width: 180 }}
value={lockingMaterialType}
options={lockingMaterialList.map((item) => ({
label: t(item.labelKey),
value: item.value,
}))}
onChange={(item) => {
setLockingMaterialType(item.value);
}}
/>
}
/>
</ThemeConfigProvider>
</div>
</Panel>
</Collapse>完整修改