import ToolBenefitOperator from '@aiComponents/BenefitOperator';
import AiToolBlockItem from '@aiComponents/BlockItem';
import { AiToolSquareCoverCard } from '@aiComponents/CardItems';
import AiToolConfigProvider from '@aiComponents/ConfigProvider';
import DrawSizeControls from '@aiComponents/DrawSizeControls';
import DrawStepOperateLayoutPC from '@aiComponents/DrawStepOperateLayout/pc';
import AiToolGenerateModal from '@aiComponents/GenerateModal';
import ToolVerticalIntroduceLayoutPC2 from '@aiComponents/IntroduceLayout/pc/vertical2';
import ToolVerticalIntroduceLayoutContent1 from '@aiComponents/IntroduceLayout/pc/vertical2/componetns/content1';
import MaterialIntroduceModal from '@aiComponents/MaterialIntroduceModal';
import { AiToolVipOptionItem } from '@aiComponents/OptionItems';
import ToggleGenerateInfoPanelItem from '@aiComponents/ToggleInfoPanel/PanelItem.tsx';
import { QuestionCircleOutlined } from '@ant-design/icons';
import IconFont from '@components/IconFont';
import ImageSelectorWrapModal from '@components/ImageSelectorWrapModal';
import ProductAuth from '@components/ProductAuth';
import ThemeButton from '@components/ThemeButton';
import ThemeConfigProvider from '@components/ThemeConfigProvider';
import ThemeSegmented from '@components/ThemeSegmented';
import ThemeTextArea from '@components/ThemeTextArea';
import ToolEquityCountWrap from '@components/ToolEquityCountWrap';
import { requestAdvanceDrawSubStyleConfigCategory } from '@services/advanceDraw/config.ts';
import { requestComfyUIDrawGenerate } from '@services/advanceDraw/generate.ts';
import { requestModelRender } from '@services/comfyUI.ts';
import { CoolImage } from '@wander/common-components';
import { Collapse, Image, message, Popover, Typography } from 'antd';
import classNames from 'classnames/bind';
import type { CSSProperties } from 'react';
import { useTranslation } from 'react-i18next';
import type { UploadImageType } from '@/components/UploadImageCard';
import UploadImageCard from '@/components/UploadImageCard';
import { enlargeDownloadOptions } from '@/config/aiTool.ts';
import { jzxzComfyUIDrawSizeTypeList } from '@/config/comfyUI.ts';
import KjlResultQuickAccessBox from '@/pages/AiTools/components/KjlResultQuickAccessBox';
import ToggleGenerateInfoPopover from '@/pages/AiTools/components/ToggleInfoPanel';
import { lockingMaterialList } from '@/pages/AiTools/Draw/configs.ts';
import GenerateCountLimitModal from '@/pages/AiTools/InspirationDraw/Operate/jzxz/components/GenerateCountLimitModal';
import {
modelRenderGenerateCountList,
modelRenderSpeedupKey,
modelRenderToolKey,
modelRenderToolName,
TRACK_MODEL_RENDER_NAME,
} from '@/pages/AiTools/ModelRender/config.ts';
import JzxzResultQuickAccessBox from '@/pages/AiTools/ModelRender/Operate/components/JzxzQuickToolMenu';
import ModelRenderStyleCategoryGrid from '@/pages/AiTools/ModelRender/Operate/components/StyleCategoryGrid';
import useModelRender from '@/pages/AiTools/ModelRender/Operate/hooks.ts';
import { LockingMaterialTypeEnum } from '@/types/draw.ts';
import styles from './index.module.less';
const cx = classNames.bind(styles);
const { Paragraph } = Typography;
const { Panel } = Collapse;
const ModelRenderOperatePC = () => {
const {
spatialPositionConfig,
setSpatialCategoryIndex,
imageSelectorWrapModalRef,
materialIntroduceModalRef,
generateComfyUiModalRef,
setSelectPositionIndex,
generateParams,
resultImageSelectIndex,
setLockingMaterialType,
introduceLayoutShow,
setIntroduceLayoutShow,
setDescription,
modelScenarioActiveIndex,
onBaseImageFinish,
setPreciseRender,
onComfyUiGenerateSuccess,
onBaseImageChange,
onStepStart,
styleCategoryList,
spatialCategoryIndex,
baseImageCardRef,
productType,
styleCategoryGridRef,
setStyleCategoryList,
toolEquityCountWrapRef,
drawStepOperateLayoutRef,
generateComfyUiPreciseRenderingModalRef,
description,
lockingMaterialType,
preciseRender,
selectPositionIndex,
styleImageCardRef,
handleScenarioClick,
setFilteredStyleCategoryList,
jzxzCategoryList,
filteredStyleCategoryList,
scenarioKey,
onGenerateCountsChange,
generateCounts,
sizeType,
setSizeType,
limitModalOpen,
setLimitModalOpen,
generateCountLimitModalRef,
baseImageTagList,
} = useModelRender();
const { t } = useTranslation();
// 第一步 底图的上传
const firstStepRender = (uploadImage: UploadImageType | null) => {
return (
<>
<div className={cx('first_content')}>
<UploadImageCard.Card
style={{
minHeight: '100%',
height: 'auto',
}}
ref={baseImageCardRef}
onUploadImageChange={onBaseImageChange}
enableKjlUpload
enableScreenshot={productType === 'JZXZ'}
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?.getOriginImgInfo();
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>
</>
);
};
// JKJL 第二步内容
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);
styleCategoryGridRef.current?.resetSecondCategoryIndex?.();
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>
</>
);
// JZXZ第二步
const jzxzSecondStepRender = () => {
const currentList =
filteredStyleCategoryList.length > 0 ? filteredStyleCategoryList : styleCategoryList;
// 获取当前选中的类目MID
const currentCategoryMid = currentList[spatialCategoryIndex]?.mid;
return (
<>
<div className={cx('first_category_wrap')}>
{currentList.map((value, index) => (
<div
key={value.mid}
className={cx('first_category_item', {
active: index === spatialCategoryIndex,
})}
onClick={async () => {
setSpatialCategoryIndex(index);
console.log('index', index);
styleCategoryGridRef.current?.setFirstCategoryIndex(index);
if (!value.children && value.justCategory) {
try {
styleCategoryGridRef.current?.setSecondCategoryRequesting(true);
const subCategoryList = await requestAdvanceDrawSubStyleConfigCategory(
value.mid,
);
// 更新原始列表
setStyleCategoryList((prev) =>
prev.map((item) =>
item.mid === value.mid ? { ...item, children: subCategoryList } : item,
),
);
// 更新过滤列表
setFilteredStyleCategoryList((prev) =>
prev.map((item) =>
item.mid === value.mid ? { ...item, children: subCategoryList } : item,
),
);
styleCategoryGridRef.current?.setSecondCategoryRequesting(false);
} catch (e) {
styleCategoryGridRef.current?.setSecondCategoryRequesting(false);
console.error(e);
}
}
}}
>
{value.name}
</div>
))}
</div>
{currentCategoryMid && spatialPositionConfig[currentCategoryMid] && (
<div className={cx('spatial_position_wrap')}>
{spatialPositionConfig[currentCategoryMid].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 secondCategoryPreciseRendering = (url: string) => (
<>
{/* 二级类目前新增精确渲染 */}
<div
className={cx('second_category_item', {
active: preciseRender,
})}
onClick={() => {
setPreciseRender(true);
}}
>
<AiToolSquareCoverCard
coverUrl={url}
extra={
<>
<div className={cx('second_category_recommended')}>
<span>推荐</span>
</div>
<span className={cx('second_category_name')}>精确渲染</span>
</>
}
/>
</div>
</>
);
// 第三步内容
const thirdStepRender = (
<>
<ModelRenderStyleCategoryGrid
ref={styleCategoryGridRef}
categoryList={styleCategoryList}
onCategoryListChange={(list) => {
setStyleCategoryList(list);
}}
secondCategoryExtra={[
secondCategoryPreciseRendering(
'https://gd-hbimg.huaban.com/d113fdba55b570c14f9e6dd85053e20fe66e59f5cd9c6-KWo2yS_fw1200webp',
),
secondCategoryPreciseRendering(
'https://gd-hbimg.huaban.com/4d623892958842956954557d10427f47f7331c3bcfbdc-Xa8fgu_fw1200webp',
),
]}
hideActive={preciseRender}
onSecondCategoryChange={() => {
setPreciseRender(false);
}}
scenarioKey={scenarioKey}
/>
{!preciseRender ||
(scenarioKey !== 'indoor' && (
<>
<div className={cx('step_title')} style={{ marginTop: 40 }}>
更多设置
</div>
<ProductAuth productTypes={['JKJL']}>
<div className={cx('step_sub_title')}>
上传风格参考图和添加更多设置可以定制化生成对应风格,进一步加强生成图的风格准确性,此处做选填。
</div>
</ProductAuth>
<Collapse ghost expandIconPosition="end">
<ProductAuth productTypes={['JKJL']}>
<Panel header="上传参考图(选填)" key="reference">
<div className={cx('reference_image_wrap')}>
<UploadImageCard>
{() => <UploadImageCard.Card hideDeleteBtn={false} ref={styleImageCardRef} />}
</UploadImageCard>
</div>
</Panel>
</ProductAuth>
<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) => (
<>
<ProductAuth productTypes={['JKJL']}>
<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?.getOriginImgInfo();
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?.generateImageList[
resultImageSelectIndex
];
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>
}
/>
</ProductAuth>
<ProductAuth productTypes={['JZXZ']}>
<DrawStepOperateLayoutPC
trackToolName={TRACK_MODEL_RENDER_NAME}
ref={drawStepOperateLayoutRef}
downloadConfig={{
enlargeDownloadOptions,
}}
items={[
{
title: '选择模型场景',
subTitle: '请根据实际情况选择合适的渲染场景',
content: (
<>
<div className={cx('scenario_wrap')}>
{jzxzCategoryList.map((item, index) => (
<div
key={item.key}
className={cx('scenario_item', productType.toLowerCase(), {
active: modelScenarioActiveIndex === index,
})}
onClick={() => handleScenarioClick(item, index)}
>
<div className={cx('scenario_item_image')}>
<img
src={item.coverUrl}
alt={item.name}
className={cx('scenario_image')}
/>
</div>
<ThemeButton
size="large"
shape="circle"
className={cx('start_btn', {
no_active: modelScenarioActiveIndex !== index,
})}
>
{item.name}
</ThemeButton>
</div>
))}
</div>
</>
),
},
{
title: '上传模型底图',
subTitle: '大师级渲染,从上传底图开始!',
content: firstStepRender(uploadImage),
beforeNextStep: async () => {
// if (!baseImageCardRef.current?.uploadImage) {
// message.warn('请先上传底图');
// return false;
// }
if (modelScenarioActiveIndex === 1) {
return true;
} else {
drawStepOperateLayoutRef.current?.setCurrent(3);
return false;
}
},
},
{
title: '选择空间位置',
subTitle: '选择正确的位置可以确保生成图更准确。',
content: jzxzSecondStepRender(),
},
{
title: '选择渲染风格',
subTitle: '选择想要生成的风格大模型,可以让生成结果更偏向自己的喜好。',
content: thirdStepRender,
previousStep: async () => {
if (modelScenarioActiveIndex === 1) {
return true;
} else {
drawStepOperateLayoutRef.current?.setCurrent(1);
return false;
}
},
beforeNextStep: () => {
return Promise.resolve(false);
},
nextStepBtn: (
<Popover
trigger={scenarioKey === 'indoor' ? [] : 'hover'}
open={scenarioKey === 'indoor' ? false : undefined}
showArrow={false}
overlayInnerStyle={{ borderRadius: 10, paddingTop: 8 }}
content={
<AiToolConfigProvider
themeConfig={{
OptionItem: {
'--text-color': '#919191',
'--box-background': '#f2f2f2',
'--border-radius': '10px',
} as CSSProperties,
}}
>
<div className={cx('popover_content')}>
{/* 生成尺寸 */}
<AiToolBlockItem title={t('AiTools-MixedRawImage-Operate-pc-Jcode')}>
<DrawSizeControls
value={sizeType}
list={jzxzComfyUIDrawSizeTypeList}
onChange={(item) => {
setSizeType(item);
}}
/>
</AiToolBlockItem>
{/* 生成张数 */}
<AiToolBlockItem
title={t('AiTools-Draw-ConfigPopover-GenerateNum-kijAD')}
>
<div className={cx('size_grid')}>
{modelRenderGenerateCountList.map((item, index) => (
<AiToolVipOptionItem
availableType={item.availableType}
label={t(item.labelKey)}
key={index}
active={item.value === generateCounts}
onClick={() => {
onGenerateCountsChange(item);
}}
/>
))}
</div>
</AiToolBlockItem>
</div>
</AiToolConfigProvider>
}
>
<ThemeButton className={cx('btn_start')} size="large" onClick={onStepStart}>
开始渲染
<IconFont
hidden={scenarioKey === 'indoor'}
className={cx('icon_down')}
type="micro-icon-arrow-down"
/>
</ThemeButton>
</Popover>
),
},
]}
uploadImage={uploadImage}
resultExtra={
<>
<JzxzResultQuickAccessBox className={cx('quick_access_box')} />
</>
}
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>
}
/>
</ProductAuth>
</>
)}
</UploadImageCard>
{/* 迁移内容的原图片效果图库 */}
<ImageSelectorWrapModal
ref={imageSelectorWrapModalRef}
queryTagImages={{
categoryList: baseImageTagList,
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={requestModelRender}
speedupKey={modelRenderSpeedupKey}
pollingConfig={{
processingType: 'COMFYUI_COMPOSE',
imageDetectMode: 'NONE',
}}
onGenerateSuccess={onComfyUiGenerateSuccess}
onCancel={() => {
// 蒙层关闭 回到进度展示
drawStepOperateLayoutRef.current?.setShowType('STEP');
}}
/>
<MaterialIntroduceModal ref={materialIntroduceModalRef} />
<GenerateCountLimitModal
ref={generateCountLimitModalRef}
open={limitModalOpen}
onCancel={() => {
setLimitModalOpen(false);
}}
/>
</>
);
};
export default ModelRenderOperatePC;import { AiToolImageCoverCard, AiToolSquareCoverCard } from '@aiComponents/CardItems';
import AiToolGridList from '@aiComponents/GridList';
import Loading from '@components/Loading';
import ProductAuth from '@components/ProductAuth';
import { requestAdvanceDrawSubStyleConfigCategory } from '@services/advanceDraw/config.ts';
import { useMount } from 'ahooks';
import classNames from 'classnames/bind';
import { floor } from 'lodash';
import type { Dispatch, ReactNode, SetStateAction } from 'react';
import { useCallback } from 'react';
import { forwardRef, useImperativeHandle, useMemo, useRef, useState } from 'react';
import Media from 'react-media';
import type { GetAdvanceDrawLeafGenerateArgsByMidsType } from '@/pages/AiTools/AdvanceDraw/utils.ts';
import { getComfyUIDrawLeafGenerateArgsByMids } from '@/pages/AiTools/AdvanceDraw/utils.ts';
import { useStore } from '@/store/createStore.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;
// 二级类目名称获取
getSelectedSecondCategoryName: () => string;
// 重置二级类目
resetSecondCategoryIndex: () => void;
};
type Props = {
categoryList: AdvanceDrawStyleCategoryItem[];
onCategoryListChange: (list: AdvanceDrawStyleCategoryItem[]) => void;
secondCategoryExtra: ReactNode[];
hideActive: boolean;
onSecondCategoryChange: (index: number) => void;
// 场景key值
scenarioKey: string;
};
// 二级类目最小宽度
const MIN_SECOND_CATEGORY_CARD_WIDTH = 150;
// 二级类目间距
const SECOND_CATEGORY_GRID_GAP = 16;
/**
* 模型渲染风格类目
*/
const ModelRenderStyleCategoryGrid = forwardRef<ModelRenderStyleCategoryGridRef, Props>(
(
{
categoryList,
onCategoryListChange,
secondCategoryExtra,
hideActive,
onSecondCategoryChange,
scenarioKey,
},
ref,
) => {
const { productType } = useStore((state) => state.productInfo);
const secondCategoryRef = useRef<HTMLDivElement | null>(null);
// 二级类目 列数
const [secondCategoryGridColumns, setSecondCategoryGridColumns] = useState(0);
// 一级类目索引
const [firstCategoryIndex, setFirstCategoryIndex] = useState(0);
// 二级类目索引
const [secondCategoryIndex, setSecondCategoryIndex] = useState(0);
// 二级类目请求中
const [secondCategoryRequesting, setSecondCategoryRequesting] = useState(false);
// 所有层级分类列表
const { secondCategoryList } = useMemo(() => {
if (!categoryList.length) {
return {
secondCategoryList: [],
};
}
// 二级类目列表
const secondCategoryList = categoryList[firstCategoryIndex]?.children || [];
return {
secondCategoryList,
};
}, [categoryList, firstCategoryIndex, secondCategoryIndex]);
// 计算列数
const calculateColumns = useCallback(() => {
if (secondCategoryRef.current?.offsetWidth) {
const secondContainerWidth = secondCategoryRef.current?.offsetWidth;
// 计算列数
setSecondCategoryGridColumns(
floor(
(secondContainerWidth + SECOND_CATEGORY_GRID_GAP) /
(MIN_SECOND_CATEGORY_CARD_WIDTH + SECOND_CATEGORY_GRID_GAP),
),
);
}
}, []);
useMount(() => {
calculateColumns();
});
useImperativeHandle(ref, () => ({
setFirstCategoryIndex,
setSecondCategoryRequesting,
getComfyUIDrawLeafGenerateArgsByMids: () => {
const firstCategory = categoryList[firstCategoryIndex];
const secondeCategory = firstCategory.children?.[secondCategoryIndex];
const selectMids = [firstCategory.mid];
if (secondeCategory) {
selectMids.push(secondeCategory.mid);
}
return getComfyUIDrawLeafGenerateArgsByMids(categoryList, selectMids);
},
getSelectedSecondCategoryName: () => {
if (categoryList.length > firstCategoryIndex) {
const secondCategoryList = categoryList[firstCategoryIndex]?.children || [];
if (secondCategoryList.length > secondCategoryIndex) {
return secondCategoryList[secondCategoryIndex]?.name || '';
}
}
return '';
},
resetSecondCategoryIndex: () => {
setSecondCategoryIndex(0);
},
}));
console.log('setFirstCategoryIndex', firstCategoryIndex);
return (
<>
<div
className={cx('first_category_wrap')}
hidden={scenarioKey !== 'indoor' && productType === 'JZXZ'}
>
{categoryList.map((value, index) => (
<div
key={value.mid}
className={cx('first_category_item', {
active: index === firstCategoryIndex,
})}
onClick={async () => {
setFirstCategoryIndex(index);
setSecondCategoryIndex(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>
<Media
queries={{
small: '(max-width: 768px)',
large: '(min-width: 769px)',
}}
>
{(matches) => (
<>
{matches.large && (
<>
{secondCategoryRequesting && <Loading height={200} iconWidth={100} />}
<div
className={cx('second_category_wrap')}
style={{
gridTemplateColumns: `repeat(${secondCategoryGridColumns}, 1fr)`,
}}
ref={secondCategoryRef}
>
<ProductAuth productTypes={['JKJL']}>
<div hidden={!secondCategoryList.length}>
{secondCategoryExtra[firstCategoryIndex]}
</div>
</ProductAuth>
{secondCategoryList.map((value, index) => (
<div
className={cx('second_category_item', {
active:
productType === 'JKJL'
? index === secondCategoryIndex && !hideActive
: index === secondCategoryIndex,
})}
key={value.mid}
onClick={() => {
setSecondCategoryIndex(index);
onSecondCategoryChange(index);
}}
>
<AiToolSquareCoverCard
coverUrl={value.coverUrl}
extra={<span className={cx('second_category_name')}>{value.name}</span>}
/>
</div>
))}
</div>
</>
)}
{matches.small && (
<>
{secondCategoryRequesting && <Loading height={200} iconWidth={100} />}
<div className={cx('second_category_wrap')} ref={secondCategoryRef}>
{/* {secondCategoryList.map((value, index) => (*/}
{/* <div*/}
{/* className={cx('second_category_item', {*/}
{/* active:*/}
{/* productType === 'JKJL'*/}
{/* ? index === secondCategoryIndex && !hideActive*/}
{/* : index === secondCategoryIndex,*/}
{/* })}*/}
{/* key={value.mid}*/}
{/* onClick={() => {*/}
{/* setSecondCategoryIndex(index);*/}
{/* onSecondCategoryChange(index);*/}
{/* }}*/}
{/* >*/}
{/* <AiToolSquareCoverCard*/}
{/* coverUrl={value.coverUrl}*/}
{/* extra={<span className={cx('second_category_name')}>{value.name}</span>}*/}
{/* />*/}
{/* </div>*/}
{/* ))}*/}
<>
<AiToolGridList
list={secondCategoryList}
renderItem={(item, index) => {
return (
<AiToolImageCoverCard
label={item.name}
active={index === secondCategoryIndex}
coverUrl={item.coverUrl}
onClick={() => {
setSecondCategoryIndex(index);
onSecondCategoryChange(index);
}}
/>
);
}}
/>
</>
</div>
</>
)}
</>
)}
</Media>
</>
);
},
);
export default ModelRenderStyleCategoryGrid;
为什么第一次点击的时候styleCategoryGridRef.current?.setFirstCategoryIndex(index);没有生效firstCategoryIndex依旧是默认的0,第二次点击就生效了
最新发布