import type { DrawStyleSecondCategoryGridRef } from ‘@aiComponents/DrawStyleCategoryGrid/SecondGrid.tsx’; import type { UseDrawStyleCategoryConfigProps } from ‘@aiComponents/DrawStyleCategoryGrid/useDrawStyleCategoryConfig.ts’; import type { FeatureLeaferFrameLayoutCommonProps, FeatureLeaferFrameLayoutRef, } from ‘@aiComponents/FeatureLeaferFrameLayout/types.ts’; import type { AiToolGenerateModalRef } from ‘@aiComponents/GenerateModal’; import type { QueryTagItem } from ‘@aiComponents/QueryTagImagesSelector/types.ts’; import type { SemanticsSegmentImageControlRef, SemanticsSegmentImageType, } from ‘@aiComponents/SemanticsSegmentImage/types.ts’; import type { StyleConfigTextAreaRef } from ‘@aiComponents/StyleConfigTextArea’; import type { UploadCropperModalProps, UploadCropperModalRef, } from ‘@aiComponents/UploadCropperModal’; import type { AiVipEquityLimitModalRef } from ‘@aiComponents/VipEquityLimitModal’; import type { ToolEquityCountWrapRef } from ‘@components/ToolEquityCountWrap’; import type { RequestDecorationDesignParams } from ‘@services/comfyUI.ts’; import type { RequestJzxzDrawImageParams, RequestTextToImageGenerateParams, } from ‘@services/draw.ts’; import { aiAddEventTrack, combineSentenceBySplitSymbol, getRandomNum, getStyleDataIndexByRefs, } from ‘@utils/ai.ts’; import { userAuth } from ‘@utils/auth.ts’; import { computeContentObjectFit, delayFun } from ‘@utils/common.ts’; import { decodeImageUrl } from ‘@utils/string.ts’; import { microGetHuoshanUploadImageUrl } from ‘@utils/upload.ts’; import type { UploadImageResponse } from ‘@wander/base-utils’; import { fileToBase64 } from ‘@wander/base-utils’; import { base64ToBlob, blobToFile, getImageSizeInfo, isMobile } from ‘@wander/base-utils’; import { useMount } from ‘ahooks’; import { message } from ‘antd’; import type { AxiosRequestConfig } from ‘axios’; import { t } from ‘i18next’; import PubSub from ‘pubsub-js’; import type { ReactNode } from ‘react’; import { useEffect, useRef, useState } from ‘react’; import { useLocation, useSearchParams } from ‘react-router-dom’; import type { CustomUploadProps } from ‘@/components/CustomUpload’; import type { UploadBtnImageResponse } from ‘@/components/HuabanUploadBtn’; import type { ImageSelectorModalProps, ImageSelectorModalRef, } from ‘@/components/ImageSelectorModal’; import type { ThemeSegmentedProps } from ‘@/components/ThemeSegmented’; import type { UploadImageType } from ‘@/components/UploadImageCard’; import { imgToImgSizeConfig, JZXZ_DRAW_INIT_NEGATIVE_PROMPTS } from ‘@/config/aiTool’; import useGetConfigValue from ‘@/hooks/useGetConfigValue.ts’; import { DECORATION_DESIGN_CATEGORY_SELECT_CONFIG_KEY, DECORATION_DESIGN_CHARTLET_CATEGORY_KEY, DECORATION_DESIGN_COMFY_UI_SELECT_CATEGORY_CONFIG_KEY, decorationDesignSpeedModePrompts, decorationDesignToolKey, TRACK_DECORATION_DESIGN_TOOL_NAME, } from ‘@/pages/AiTools/DecorationDesign/config.tsx’; import { commonDrawControlNetModelMap } from ‘@/pages/AiTools/Draw/configs.ts’; import { useToolRouterStore } from ‘@/pages/AiTools/Router/store/createStore.ts’; import type { RequestDescribeRetouchingParams } from ‘@/services/aiTool’; import { requestDescribeRetouching } from ‘@/services/aiTool’; import { useStore } from ‘@/store/createStore.ts’; import type { AiToolProcessSuccessResponse, ControlNetEditorImageInfo } from ‘@/types/ai’; import type { CandidateItem, DrawSizeType, DrawStyleConfigItem, GeneratePriorityType, LoraItem, } from ‘@/types/draw.ts’; import type { ChartletCategoryItem, ChartletImageItem } from ‘@/types/image’; import { imageDataToBase64 } from ‘@/utils/image’; export type VersionValueType = ‘STANDARD’ | ‘EXTREME_SPEED’ | ‘ADVANCED’; // 请求之间的间隔时长 const REQUEST_DELAY_DURATION = 2000; // 默认重试次数 const DEFAULT_RETRY_COUNT = 2; type VersionMobileValue = any; export type VersionOptionItem = { label: ReactNode; value: VersionMobileValue; disabled?: boolean; }; export type CategoriesItem = { key: string; // 风格名称 label: string; // 风格展示图片 thumbUrl: string; // 提示词 promptsList: CandidateItem[]; // 大模型名称 modelName: string; // 小模型 loraParamses?: LoraItem[]; }; export type DecorationDesignStyleItem = { alias: string; key: string; categories: CategoriesItem[]; }; type PromptConfig = { key: string; list: CandidateItem[]; }; export const useAiDecorationDesign = () => { const location = useLocation(); const [searchParams, setSearchParams] = useSearchParams(); const addResultImage = useToolRouterStore((state) => state.addResultImage); // 产品类型 const productType = useStore((state) => state.productInfo.productType); // 不限制权益 const noLimitFrequency = userAuth([‘ORGANIZATION_VIP’]); // 描述词获取 const styleConfigTextAreaRef = useRef<StyleConfigTextAreaRef>(null); // 底图图库 const imageSelectorModalRef = useRef<ImageSelectorModalRef>(null); // 生成弹窗 const generateModalRef = useRef<AiToolGenerateModalRef<RequestJzxzDrawImageParams>>(null); const drawStyleCategoryRef = useRef<DrawStyleSecondCategoryGridRef>(null); const toolEquityCountWrapRef = useRef<ToolEquityCountWrapRef>(null); // 画板Ref const featureLeaferFrameLayoutRef = useRef<FeatureLeaferFrameLayoutRef>(null); const semanticsSegmentImageControlRef = useRef<SemanticsSegmentImageControlRef>(null); // 刷新次数 const refreshToolEquityUserUseInfo = useStore((state) => state.refreshToolEquityUserUseInfo); // 创作模式优先级 const [generatePriorityType, setGeneratePriorityType] = useState<GeneratePriorityType>(‘CONTROLNET’); // 会员权益弹窗 const DecorationDesignipLimitModalRef = useRef<AiVipEquityLimitModalRef>(null); // 相似度 const [weight, setWeight] = useState(1); // 最终生成图尺寸大小 const [sizeType, setSizeType] = useState<DrawSizeType>(‘LARGE’); // 极速版-生成失败的信息 const [errorMsg, setErrorMsg] = useState(‘’); // 创作弹框-极速版 const generateSpeedModalRef = useRef<AiToolGenerateModalRef>(null); // comfyUI 创作弹框-高级版 const comfyUiGenerateModalRef = useRef<AiToolGenerateModalRef<RequestDecorationDesignParams>>(null); // 风格获取-高级版 const drawComfyUiStyleCategoryRef = useRef<DrawStyleSecondCategoryGridRef>(null); // 风格列表 const [comfyUiStyleList, setComfyUiStyleList] = useState<DecorationDesignStyleItem[]>([]); // 高级版-风格参考图信息 const [styleReferComfyImageInfo, setStyleReferComfyImageInfo] = useState<UploadImageResponse | null>(null); // 风格参考图图库 const comfySourceImageSelectorModalRef = useRef<ImageSelectorModalRef>(null); // comfyUI-XL版本参考图风格符合度 const [comfyStyleReferWeight, setComfyStyleReferWeight] = useState(0.8); // 上传裁剪弹窗 const uploadCropperModalRef = useRef<UploadCropperModalRef>(null); // 裁剪弹窗open const [cropperModalOpen, setCropperModalOpen] = useState(false); // 选中风格索引 const [currentStyleIndex, setCurrentStyleIndex] = useState(0); // 高级版-一级类目状态获取 const [activeAlias, setActiveAlias] = useState<string>(comfyUiStyleList[0]?.alias || ‘’); // 高级版-获取当前激活的二级类目 const activeCategories = comfyUiStyleList.find((item) => item.alias === activeAlias)?.categories || []; // 版本切换状态 const [versionValue, setVersionValue] = useState<VersionValueType>(() => { return userAuth([‘USER’, ‘COMMON_VIP’, ‘TOURIST’]) ? ‘STANDARD’ : ‘EXTREME_SPEED’; }); // 标准版-该工具支持选择的类目组 const categoryRefsGroup: string[][] = useGetConfigValue({ productKeys: { JZXZ: DECORATION_DESIGN_CATEGORY_SELECT_CONFIG_KEY, JZDS: ‘jzds_decoration_design_style_categories’, }, }) || []; // 贴图类目列表 const chartletCategoryList = useGetConfigValue<ChartletCategoryItem[]>({ key: DECORATION_DESIGN_CHARTLET_CATEGORY_KEY }) || []; // 线稿库类目列表 const queryTagList = useGetConfigValue<QueryTagItem[]>({ key: ‘jzxz_decoration_design_base_Image_query_tag_config’, }) || []; // 高级版-该工具支持选择的类目组 const comfyUiCategoryRefsGroup: CategoriesItem[] = ( useGetConfigValue({ productKeys: { JZXZ: DECORATION_DESIGN_COMFY_UI_SELECT_CATEGORY_CONFIG_KEY, }, }) || [] ).filter( (category: CategoriesItem) => category.key === ‘indoorHomeDecoration’ || category.key === ‘indoorWorkWear’, ); // 高级版-描述词 const comfyUiPrompt: PromptConfig[] = ( useGetConfigValue({ productKeys: { JZXZ: ‘jzxz_advance_draw_descriptor_config’, }, }) || [] ).filter((p: PromptConfig) => p.key === ‘indoorHomeDecoration’ || p.key === ‘indoorWorkWear’); // 版本切换-mobile const handleVersionMobileChange = (value: VersionValueType) => { if (userAuth([‘TOURIST’])) { PubSub.publish(‘onLogin’, true); return; } if (value !== ‘STANDARD’ && userAuth([‘USER’, ‘COMMON_VIP’])) { console.log(‘value’, value); DecorationDesignipLimitModalRef.current?.setLimitType(‘FOREVER_VIP’); return; } // 否则允许切换 setVersionValue(value); // 描述词类型获取 const promptType = styleConfigTextAreaRef.current?.getPromptType(); // 描述词自定义-版本切换判断 if (promptType === 'USER' || promptType === 'INNOVATIVE') { if (value === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, false, ); return; } if (value === 'EXTREME_SPEED') { styleConfigTextAreaRef.current?.updateCandidateConfig( decorationDesignSpeedModePrompts, false, ); return; } if (value === 'STANDARD') { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, false); } } return; } if (value === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, true, ); return; } if (value === 'EXTREME_SPEED') { styleConfigTextAreaRef.current?.updateCandidateConfig(decorationDesignSpeedModePrompts, true); return; } if (value === 'STANDARD') { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, true); } } }; // 版本切换-pc const handleVersionPcChange: ThemeSegmentedProps[‘onChange’] = (item) => { const value = item.value; if (userAuth(['TOURIST'])) { PubSub.publish('onLogin', true); return; } if (value !== 'STANDARD' && userAuth(['USER', 'COMMON_VIP'])) { DecorationDesignipLimitModalRef.current?.setLimitType('FOREVER_VIP'); return; } // 否则允许切换 setVersionValue(value); // 描述词类型获取 const promptType = styleConfigTextAreaRef.current?.getPromptType(); // 描述词自定义-版本切换判断 if (promptType === 'USER' || promptType === 'INNOVATIVE') { if (value === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, false, ); return; } if (value === 'EXTREME_SPEED') { styleConfigTextAreaRef.current?.updateCandidateConfig( decorationDesignSpeedModePrompts, false, ); return; } if (value === 'STANDARD') { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, false); } } return; } if (value === 'ADVANCED') { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, true, ); return; } if (value === 'EXTREME_SPEED') { styleConfigTextAreaRef.current?.updateCandidateConfig(decorationDesignSpeedModePrompts, true); return; } if (value === 'STANDARD') { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, true); } } }; // 高级版-风格类目描述词对应方法 function mapPromptsToCategories(categoryGroups: any, prompts: any) { return categoryGroups.map((categoryGroup: any) => { const promptConfig = prompts.find((prompt: any) => prompt.key === categoryGroup.key); if (!promptConfig) { return null; } return { alias: categoryGroup.alias, key: categoryGroup.key, categories: categoryGroup.categories.map((cat: any) => ({ key: cat.key, label: cat.alias, thumbUrl: cat.coverUrl, promptsList: promptConfig.list.map((item: any) => ({ placeholder: item.placeholder || '', keyword: item.keyword || '', })), modelName: cat.modelName, lora: cat.loraParamses?.[0], })), }; }); } // 高级版-设计领域与风格切换 const onComfyUiStyleIndexChange = (index: number) => { setCurrentStyleIndex(index); const currentItem = activeCategories[index]; const isUniversalCategory = activeCategories[0]; // 随机描述词 let randomPrompts: CandidateItem[] = []; if (currentItem.promptsList?.length) { const randomIndex = Math.floor(Math.random() * currentItem.promptsList.length); randomPrompts = [currentItem.promptsList[randomIndex]]; } // 更新候选配置 styleConfigTextAreaRef.current?.updateCandidateConfig(randomPrompts, true); if (isUniversalCategory) { styleConfigTextAreaRef.current?.updateCandidateConfig(currentItem.promptsList, true); } }; // 获取语意分割组件和局部绘制组件内部参考图信息并转为ControlNet所需参数 const getControlsControlNetArgs = ({ localDrawImage, semanticsSegmentImage, }: { localDrawImage: ControlNetEditorImageInfo | null; semanticsSegmentImage: SemanticsSegmentImageType | null; }) => { const controlNetArgs: Pick< RequestTextToImageGenerateParams, ‘localDrawImage’ | ‘semanticsSegmentImage’ > = {}; if (localDrawImage?.enabled) { controlNetArgs.localDrawImage = { ...localDrawImage, ...commonDrawControlNetModelMap.inpaint, enable: true, // 开始迭代步数 guidanceStart: 0, guidanceEnd: 1, // 结束迭代步数 weight: 1, }; } if (semanticsSegmentImage?.enabled) { controlNetArgs.semanticsSegmentImage = { ...semanticsSegmentImage, ...commonDrawControlNetModelMap.semanticsSegment, enable: true, // 开始迭代步数 guidanceStart: 0, guidanceEnd: 1, // 结束迭代步数 weight: 1, }; } return controlNetArgs; }; // 获取画板图片 const getFrameImage = async (sizeInfo: Pick<UploadImageResponse, ‘width’ | ‘height’>) => { // 导出画布方法 const exportFrame = featureLeaferFrameLayoutRef.current?.exportFrame; if (!exportFrame) { return Promise.reject(); } // 导出内容 const result = await exportFrame('canvas', { size: { width: sizeInfo.width, height: sizeInfo.height, }, }); const leaferCanvas = result.data; const context = leaferCanvas.context; // 获取画板图片 const imageData = context.getImageData(0, 0, leaferCanvas.width, leaferCanvas.height); const base64 = imageDataToBase64(imageData); const file = blobToFile(base64ToBlob(base64)); const imageInfo = await microGetHuoshanUploadImageUrl(file); featureLeaferFrameLayoutRef.current?.setCompareImage(imageInfo); return imageInfo; }; // comfyUI-有风格参考图 const generateComfyStyleReferenceTask = async (uploadImage: UploadImageResponse) => { comfyUiGenerateModalRef.current?.setModalOpen(true); let imageInfo: UploadImageResponse = uploadImage; const prompt = styleConfigTextAreaRef.current?.prompts.placeholder || ''; // 当前尺寸的阈值 const thresholdNum = imgToImgSizeConfig[sizeType]; // 尺寸信息 const sizeParams = computeContentObjectFit({ containerWidth: thresholdNum, containerHeight: thresholdNum, contentWidth: uploadImage.width, contentHeight: uploadImage.height, type: 'contain', }); // 风格参考图获取 const baseImage = styleReferComfyImageInfo?.largeUrl; try { if (!featureLeaferFrameLayoutRef.current?.verifyFrameIsEmpty()) { imageInfo = await getFrameImage(uploadImage); } comfyUiGenerateModalRef.current?.generateTask({ imageUrl: imageInfo.largeUrl, seed: getRandomNum(), ...sizeParams, // 描述词 prompt, // 底图符合度 baseImageWeight: weight, // 参考图片链接 styleReferImageUrl: baseImage, // 风格参考度-权重 styleReferWeight: comfyStyleReferWeight, }); } catch (e) { comfyUiGenerateModalRef.current?.setRequestStatus('GENERATE_ERROR'); } }; // comfyUI-无风格参考图 const generateComfyTask = async (uploadImage: UploadImageResponse) => { comfyUiGenerateModalRef.current?.setModalOpen(true); let imageInfo: UploadImageResponse = uploadImage; const prompt = styleConfigTextAreaRef.current?.prompts.placeholder || ''; // 当前尺寸的阈值 const thresholdNum = imgToImgSizeConfig[sizeType]; // 尺寸信息 const sizeParams = computeContentObjectFit({ containerWidth: thresholdNum, containerHeight: thresholdNum, contentWidth: uploadImage.width, contentHeight: uploadImage.height, type: 'contain', }); try { if (!featureLeaferFrameLayoutRef.current?.verifyFrameIsEmpty()) { imageInfo = await getFrameImage(uploadImage); } comfyUiGenerateModalRef.current?.generateTask({ imageUrl: imageInfo.largeUrl, seed: getRandomNum(), ...sizeParams, // 描述词 prompt, // 底图符合度 baseImageWeight: weight, }); } catch (e) { comfyUiGenerateModalRef.current?.setRequestStatus('GENERATE_ERROR'); } }; // 标准版-开始创作 const onSdStart: FeatureLeaferFrameLayoutCommonProps[‘onStart’] = async ({ uploadImage, localDrawImage, semanticsSegmentImage, }) => { if (!noLimitFrequency && !toolEquityCountWrapRef.current?.verifyUseStatus()) { return; } if (!uploadImage) { message.warning(t(‘AiTools-LocalDrawImage-yES4’)); return; } let imageInfo: UploadImageResponse = uploadImage; generateModalRef.current?.setModalOpen(true); try { if (!featureLeaferFrameLayoutRef.current?.verifyFrameIsEmpty()) { imageInfo = await getFrameImage(uploadImage); } // 提取当前类目下下的参数 const { modelName = '', loraParamses = [] } = drawStyleCategoryRef.current?.getCategoryParams() || {}; let prompt = styleConfigTextAreaRef.current?.prompts.keyword || ''; // 启用了色块 获取色块描述 if (semanticsSegmentImage?.enabled) { // 去重描述词 const paintKeywords = [ ...new Set(semanticsSegmentImage.colorBlockList.map((item) => item.englishAlias)), ]; prompt = combineSentenceBySplitSymbol([...paintKeywords, prompt]); } // 参考图 const styleImage = styleReferComfyImageInfo; generateModalRef.current?.generateTask({ // 获取语意分割、局部重绘相关controlnet参数 ...getControlsControlNetArgs({ localDrawImage, semanticsSegmentImage }), prompt, modelName, loraParamses, negativePrompt: JZXZ_DRAW_INIT_NEGATIVE_PROMPTS, baseImage: { width: imageInfo.width, height: imageInfo.height, url: imageInfo.largeUrl || imageInfo.thumbUrl, // 开始迭代步数 guidanceStart: 0, // 结束迭代步数 guidanceEnd: 1, // 相似度 module: 'scribble_xdog', weight, model: 'control_v11p_sd15_scribble', }, ...(styleImage ? { styleReferImage: { guidanceEnd: 1, guidanceStart: 0, height: styleImage.height as number, model: 'ip-adapter_sd15_plus [32cd8f7f]', module: 'ip-adapter_clip_sd15', url: styleImage.largeUrl, weight: comfyStyleReferWeight, width: styleImage.width as number, }, } : {}), sizeType, generateMode: 'REPAINT', generatePriorityType, generateImageType: 'DECORATION_DESIGN', }); } catch (error) { message.error(t('AiTools-DecorationDesign-Operate-hook-trpaP')); } }; // comfy-高级版-开始创作 const onComfyStart: FeatureLeaferFrameLayoutCommonProps[‘onStart’] = async (params) => { if (!params?.uploadImage) { message.warn(t(‘AiTools-DetailEnhance-Operate-hook-PzPPn’)); return; } if (styleReferComfyImageInfo) { await generateComfyStyleReferenceTask(params.uploadImage); } else { await generateComfyTask(params.uploadImage); } }; // 极速版 const sendRequest = async ( // 请求参数 params: RequestDescribeRetouchingParams, // 重试次数 retryCount: number, // 请求头配置 用于减次数 config?: AxiosRequestConfig, ): Promise<UploadImageType> => { // 没有重试次数了 直接抛出异常 if (retryCount <= 0) { throw { errorCode: ‘MAX_RETRY_TIMES’, errorMsg: ‘生成图片异常’ }; } const response = await requestDescribeRetouching(params, config).catch((e) => { console.error('请求失败:', e); return null; }); // 本次生成没有结果 重试 if (!response) { await delayFun(REQUEST_DELAY_DURATION); return sendRequest(params, retryCount - 1, config); } // 拿到了结果 正常返回 if (response.imageList[0]) { return response.imageList[0]; } // 拿到了结果但是没有图片返回且有文案提示 if (!response.imageList.length && response.text) { setErrorMsg(response.text.replace(/\*/g, '')); // 特殊错误,不重试 throw { errorCode: 'NO_IMAGE', errorMsg: response.text }; } // 其他情况重试 await delayFun(REQUEST_DELAY_DURATION); return sendRequest(params, retryCount - 1, config); }; const batchRequest = async (params: RequestDescribeRetouchingParams) => { const result: UploadImageType[] = []; const data = await sendRequest(params, DEFAULT_RETRY_COUNT); result.push(data); return result; }; const onSpeedModeGenerateSuccess = (list: UploadImageType[]) => { const result = list[0].largeUrl; featureLeaferFrameLayoutRef.current?.setResult(result); // 添加进快捷使用 addResultImage({ ...list[0], url: list[0].largeUrl }); aiAddEventTrack(TRACK_DECORATION_DESIGN_TOOL_NAME, 'createSuccess'); refreshToolEquityUserUseInfo(decorationDesignToolKey); }; // 开始创作-极速版 const onSpeedModeStart: FeatureLeaferFrameLayoutCommonProps[‘onStart’] = async ({ uploadImage, }) => { if (!uploadImage) { message.warning({ content: ‘请先上传图片’, key: ‘emptyImage’ }); return; } setErrorMsg(‘’); generateSpeedModalRef.current?.setModalOpen(true); generateSpeedModalRef.current?.setRequestStatus(‘EXECUTING’); // 底图 const uploadImageInfo = uploadImage.largeUrl; // 参考图 const baseImage = styleReferComfyImageInfo?.largeUrl; const imageUrlList: string[] = []; imageUrlList.push(uploadImageInfo); if (baseImage) { imageUrlList.push(baseImage); } // 描述词 const prompts = styleConfigTextAreaRef.current?.prompts.placeholder || ''; // 拼接描述词-有参考图 const baseImageDescription = `图一参考图二,图片要求按${uploadImage.width}:${uploadImage.height}比例生成`; const newPrompts = `${prompts},${baseImageDescription}`; // 拼接描述词-没有参考图 const noBaseImageDescription = `图片要求按${uploadImage.width}:${uploadImage.height}比例生成`; const promptsText = `${prompts},${noBaseImageDescription}`; // 根据baseImage是否存在,设置description const description = baseImage ? newPrompts : promptsText; try { // 发起请求 const response = await batchRequest({ description: combineSentenceBySplitSymbol([description]), imageUrls: imageUrlList, }); onSpeedModeGenerateSuccess(response); generateSpeedModalRef.current?.setRequestStatus('PENDING'); generateSpeedModalRef.current?.setModalOpen(false); } catch (e) { aiAddEventTrack(TRACK_DECORATION_DESIGN_TOOL_NAME, 'createError'); generateSpeedModalRef.current?.setRequestStatus('POLLING_ERROR'); } }; // 生成结果回调 const onGenerateSuccess = async (response: AiToolProcessSuccessResponse) => { try { const result = response.imageList[0]; // 刷新次数 refreshToolEquityUserUseInfo(decorationDesignToolKey); // 设置显示 featureLeaferFrameLayoutRef.current?.setResult(result); const sizeInfo = await getImageSizeInfo(result); // 添加生成记录 addResultImage({ url: result, …sizeInfo, }); } catch (e) { console.error(e); } }; // 画板插入贴图 const onChartletItemClick = (imageInfo: ChartletImageItem) => { featureLeaferFrameLayoutRef.current?.insertPicture(imageInfo); }; // 画板插入本地上传图片 const onChartletUploadSuccess: CustomUploadProps[‘onUploadSuccess’] = async ({ url }) => { try { const size = await getImageSizeInfo(url); featureLeaferFrameLayoutRef.current?.insertPicture({ url, width: size.width, height: size.height, }); } catch (err) { console.error(err); message.error(t(‘AiTools-ImageEditorModal-hooks-mdQhK’)); } }; // 创作优先级改变 const onGeneratePriorityTypeChange = (type: GeneratePriorityType) => { setGeneratePriorityType(type); setWeight(type === ‘CONTROLNET’ ? 1 : 1.2); }; // 标准版-类目改变回调 const onSelectRefsChange: UseDrawStyleCategoryConfigProps[‘onSelectRefsChange’] = ( refs, categories, ) => { if (versionValue !== ‘STANDARD’) { return; } drawStyleCategoryRef.current?.setSelectRefs(refs); const ids = getStyleDataIndexByRefs(‘id’, categories, refs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids); } }; // 标准版 const onCategoryInit = (styleConfigItems: DrawStyleConfigItem[]) => { // 一级类目ref const firstCategoryRefs = categoryRefsGroup.map((item) => item[0]); // 过滤出相关的类目信息 const filterStyleConfigItems = styleConfigItems.filter((item) => firstCategoryRefs.includes(item.ref), ); drawStyleCategoryRef.current?.setStyleConfigList(filterStyleConfigItems); // 请求描述词 onSelectRefsChange(categoryRefsGroup[0], filterStyleConfigItems); }; // 当点击继续处理时 const onContinue = async (result: string) => { try { const sizeInfo = await getImageSizeInfo(result); featureLeaferFrameLayoutRef.current?.onUploadSuccess([ { …sizeInfo, largeUrl: result, thumbUrl: result }, ]); } catch (e) { message.error(t(‘AiTools-DecorationDesign-Operate-hook-6ipKH’)); } }; const onSelectorModalFinish: ImageSelectorModalProps[‘onFinish’] = async (item) => { featureLeaferFrameLayoutRef.current?.onUploadSuccess([item]); if (item.originFile) { aiAddEventTrack(TRACK_DECORATION_DESIGN_TOOL_NAME, isMobile() ? ‘mobileUpload’ : ‘pcUpload’); } }; // 风格底图选择弹窗回调 const onDecorationDesignReferImageModalFinish: ImageSelectorModalProps[‘onFinish’] = async ({ largeUrl, thumbUrl, width, height, originFile, }) => { setStyleReferComfyImageInfo({ width, height, largeUrl, thumbUrl, }); let base64; // 原图有originFile直接转base64 if (originFile) { try { base64 = await fileToBase64(originFile); } catch (e) { console.error(e); } } // base64转换成功 打开裁剪弹框 失败则默认为原图 setCropperModalOpen(true); uploadCropperModalRef.current?.setCropperImageUrl(base64 || largeUrl); }; // 风格底图裁剪完成图片弹框回调 const onStyleReferImageCropperSuccess: UploadCropperModalProps[‘onCropperSuccess’] = async ( obj, ) => { setStyleReferComfyImageInfo(obj); setCropperModalOpen(false); }; const onStyleReferImageCropperError = () => { setCropperModalOpen(false); message.error(t(‘AiTools-TransferSmudge-Operate-hook-MiGY’)); }; // 开始创作版本判断 const generateTask: FeatureLeaferFrameLayoutCommonProps[‘onStart’] = (params) => { if (userAuth([‘TOURIST’])) { PubSub.publish(‘onLogin’, true); return; } if (!noLimitFrequency && !toolEquityCountWrapRef.current?.verifyUseStatus()) { return; } if (versionValue === 'STANDARD') { onSdStart(params); return; } if (versionValue === 'EXTREME_SPEED') { onSpeedModeStart(params); return; } if (versionValue === 'ADVANCED') { onComfyStart(params); } }; useMount(() => { // 获取极速版描述词 styleConfigTextAreaRef.current?.updateCandidateConfig(decorationDesignSpeedModePrompts, true); // 获取高级版风格类目 const comfyUiStyle = mapPromptsToCategories(comfyUiCategoryRefsGroup, comfyUiPrompt); setComfyUiStyleList(comfyUiStyle); if (comfyUiStyle.length > 0) { setActiveAlias(comfyUiStyle[0].alias); setCurrentStyleIndex(0); } const image: UploadBtnImageResponse = location.state?.image; if (image) { featureLeaferFrameLayoutRef.current?.onUploadSuccess([image]); return; } // 地址栏参数传递 const url = decodeImageUrl(searchParams.get('url') || ''); const width = searchParams.get('width'); const height = searchParams.get('height'); if (url && width && height) { featureLeaferFrameLayoutRef.current?.onUploadSuccess([ { largeUrl: url, thumbUrl: url, width: Number(width), height: Number(height), }, ]); setSearchParams({}); } }); // 室内装修初始设置选中室内色块 useEffect(() => { semanticsSegmentImageControlRef.current?.setSelectCategoryIndex(1); }, []); return { noLimitFrequency, styleConfigTextAreaRef, imageSelectorModalRef, generateModalRef, featureLeaferFrameLayoutRef, drawStyleCategoryRef, toolEquityCountWrapRef, semanticsSegmentImageControlRef, generatePriorityType, categoryRefsGroup, weight, setWeight, sizeType, setSizeType, weightSliderMaxSize: generatePriorityType === ‘PROMPTS’ ? 2 : 1.3, chartletCategoryList, queryTagList, onChartletItemClick, onCategoryInit, onChartletUploadSuccess, onGenerateSuccess, onContinue, onGeneratePriorityTypeChange, onSelectRefsChange, onSelectorModalFinish, versionValue, handleVersionPcChange, productType, comfyUiStyleList, setActiveAlias, activeAlias, activeCategories, onComfyUiStyleIndexChange, currentStyleIndex, comfyUiGenerateModalRef, drawComfyUiStyleCategoryRef, styleReferComfyImageInfo, setStyleReferComfyImageInfo, comfySourceImageSelectorModalRef, comfyStyleReferWeight, setComfyStyleReferWeight, generateTask, onDecorationDesignReferImageModalFinish, uploadCropperModalRef, cropperModalOpen, setCropperModalOpen, onStyleReferImageCropperSuccess, onStyleReferImageCropperError, generateSpeedModalRef, errorMsg, DecorationDesignipLimitModalRef, handleVersionMobileChange, }; }; import ToolBenefitOperator from ‘@aiComponents/BenefitOperator’; import AiToolBlockItem from ‘@aiComponents/BlockItem’; import { AiToolImageCoverCard } from ‘@aiComponents/CardItems’; import ChartletUploadBtn from ‘@aiComponents/ChartletUploadBtn’; import DrawStyleSecondCategoryGrid from ‘@aiComponents/DrawStyleCategoryGrid/SecondGrid.tsx’; import FeatureLeaferFrameLayoutPc from ‘@aiComponents/FeatureLeaferFrameLayout/pc’; import ImageSticker from ‘@aiComponents/ImageSticker’; import { colorBlockCategoryList } from ‘@aiComponents/SemanticsSegmentEditor/config.tsx’; import DrawStyleConfigTextArea from ‘@aiComponents/StyleConfigTextArea’; import UploadCropperModal from ‘@aiComponents/UploadCropperModal’; import AiVipEquityLimitModal from ‘@aiComponents/VipEquityLimitModal’; import ProductAuth from ‘@components/ProductAuth’; import ThemeInputNumber from ‘@components/ThemeInputNumber’; import ThemeSegmented from ‘@components/ThemeSegmented’; import ThemeSlider from ‘@components/ThemeSlider’; import ToolEquityCountWrap from ‘@components/ToolEquityCountWrap’; import UploadImageBox from ‘@components/UploadImageCard/UploadImageBox.tsx’; import { requestDecorationDesign } from ‘@services/comfyUI.ts’; import type { RequestJzxzDrawImageParams } from ‘@services/draw.ts’; import { requestJzxzDrawImage } from ‘@services/draw.ts’; import { userAuth } from ‘@utils/auth.ts’; import classNames from ‘classnames/bind’; import type { CSSProperties } from ‘react’; import { type FC } from ‘react’; import { useTranslation } from ‘react-i18next’; import ImageSelectorModal from ‘@/components/ImageSelectorModal/index.tsx’; import ThemeButton from ‘@/components/ThemeButton’; import { enlargeDownloadOptions } from ‘@/config/aiTool.ts’; import { generatePriorityList } from ‘@/config/aiTool.ts’; import { advanceDrawSizeTypeList } from ‘@/pages/AiTools/AdvanceDraw/config.ts’; import DrawSizeControls from ‘@/pages/AiTools/components/DrawSizeControls’; import AiToolGenerateModal from ‘@/pages/AiTools/components/GenerateModal’; import AiToolGridList from ‘@/pages/AiTools/components/GridList/index.tsx’; import UploadImagePreviewCard from ‘@/pages/AiTools/components/PreviewCard/index.tsx’; import type { GeneratePriorityType } from ‘@/types/draw.ts’; import type { DrawSizeType } from ‘@/types/draw.ts’; import { DECORATION_DESIGN_TOOL_NAME_KEY, decorationBrushTypeList, decorationDesignSpeedupKey, decorationDesignToolKey, decorationDesignVideoUrl, decorationOperationTypeList, TRACK_DECORATION_DESIGN_TOOL_NAME, } from ‘…/…/config’; import { useAiDecorationDesign } from ‘…/hook.ts’; import styles from ‘./index.module.less’; const cx = classNames.bind(styles); // 室内设计 const DecorationDesignPc: FC = () => { const { t } = useTranslation(); const { noLimitFrequency, styleConfigTextAreaRef, imageSelectorModalRef, generateModalRef, featureLeaferFrameLayoutRef, drawStyleCategoryRef, toolEquityCountWrapRef, semanticsSegmentImageControlRef, generatePriorityType, categoryRefsGroup, weight, setWeight, sizeType, setSizeType, weightSliderMaxSize, chartletCategoryList, queryTagList, onChartletItemClick, onCategoryInit, onChartletUploadSuccess, onGenerateSuccess, onContinue, onGeneratePriorityTypeChange, onSelectRefsChange, onSelectorModalFinish, versionValue, handleVersionPcChange, productType, comfyUiStyleList, setActiveAlias, activeAlias, activeCategories, onComfyUiStyleIndexChange, currentStyleIndex, comfyUiGenerateModalRef, styleReferComfyImageInfo, setStyleReferComfyImageInfo, comfySourceImageSelectorModalRef, comfyStyleReferWeight, setComfyStyleReferWeight, onDecorationDesignReferImageModalFinish, uploadCropperModalRef, cropperModalOpen, setCropperModalOpen, onStyleReferImageCropperSuccess, onStyleReferImageCropperError, generateTask, generateSpeedModalRef, errorMsg, DecorationDesignipLimitModalRef, } = useAiDecorationDesign(); const contentBottomRender = ( <> <DrawStyleConfigTextArea ref={styleConfigTextAreaRef} textareaConfig={{ placeholder: t(‘AiTools-DecorationDesign-Operate-pc-PNiN’), }} blockItem={{ title: t(‘AiTools-Redraw-Operate-pc-3mzP’), size: ‘large’, iconType: null, }} /> <AiToolBlockItem title={t('AiTools-DecorationDesign-Operate-pc-3JRD')} size="large" hidden={versionValue !== 'STANDARD'} > <div className="feature_card_wrap"> <FeatureLeaferFrameLayoutPc.LocalDraw toolKey={decorationDesignToolKey} controlNetFeatureCard={{ style: { '--main-title-font-weight': 500 } as CSSProperties, }} /> <FeatureLeaferFrameLayoutPc.SemanticsSegment toolKey={decorationDesignToolKey} ref={semanticsSegmentImageControlRef} editorConfig={{ colorBlockCategoryList, }} controlNetFeatureCard={{ style: { '--main-title-font-weight': 500, marginBottom: 0, } as CSSProperties, }} /> </div> </AiToolBlockItem> <AiToolBlockItem title={t('AiTools-DecorationDesign-Operate-pc-twQz')} size="large" hidden={versionValue === 'EXTREME_SPEED'} > {!!categoryRefsGroup.length && ( <DrawStyleSecondCategoryGrid getScrollContainer={() => document.getElementById('rightOperatorContent')} scrollOffsetTop={200} defaultSelectRefs={categoryRefsGroup[0]} ref={drawStyleCategoryRef} onInit={onCategoryInit} hidden={versionValue !== 'STANDARD'} onSelectRefsChange={onSelectRefsChange} /> )} <div className={cx('tabs')} hidden={versionValue !== 'ADVANCED'}> {comfyUiStyleList.map((item) => ( <div key={item.key} className={cx('tab', { active: item.alias === activeAlias })} onClick={() => { setActiveAlias(item.alias); }} > <span className={cx({ active: item.alias === activeAlias })}>{item.alias}</span> </div> ))} </div> <AiToolGridList hidden={versionValue !== 'ADVANCED'} list={activeCategories || []} columns={4} renderItem={(item, index) => ( <AiToolImageCoverCard key={item.key} label={item.label} coverUrl={item.thumbUrl} active={currentStyleIndex === index} onClick={() => { onComfyUiStyleIndexChange(index); }} /> )} /> </AiToolBlockItem> {/* 风格参考图 */} <AiToolBlockItem title={t('AiTools-Draw-ControlNet-wn4Hz')} size="large"> {/* 未上传前展示上传按钮 */} <div hidden={!!styleReferComfyImageInfo} className={cx('upload_box', productType.toLowerCase())} > <ProductAuth productTypes={['JZXZ']}> <UploadImageBox imageStyle={{ width: 75 }} description="" customUploadBtn={() => ( <div className={cx('upload_btn', productType.toLowerCase())} onClick={() => { comfySourceImageSelectorModalRef.current?.setModalStatus('SELECT_MODAL'); }} > {t('AiTools-ControlNet-UploadBtn-5chr')} </div> )} /> </ProductAuth> </div> <div className={cx('preview_card', productType.toLowerCase())} hidden={!styleReferComfyImageInfo} > <UploadImagePreviewCard url={styleReferComfyImageInfo?.largeUrl} actions={[ { iconType: 'micro-icon-edit', onClick: () => { comfySourceImageSelectorModalRef.current?.setModalStatus('SELECT_MODAL'); }, }, { iconType: 'micro-icon-trash-can', onClick: () => { setStyleReferComfyImageInfo(null); }, }, ]} /> </div> </AiToolBlockItem> <AiToolBlockItem title={t('AiTools-MultiAnglesDraw-Operate-StyleControlNetImageCard-Card-CbB7')} size="large" hidden={!styleReferComfyImageInfo} extra={ <ThemeInputNumber value={comfyStyleReferWeight} min={0} step={0.1} max={1} className={cx('input_number')} onChange={(value) => { if (value) { setComfyStyleReferWeight(value); } }} /> } > <div style={{ padding: '0 8px' }}> <ThemeSlider className={cx('slider')} value={comfyStyleReferWeight} min={0} step={0.1} max={1} onChange={(value) => setComfyStyleReferWeight(value)} /> </div> </AiToolBlockItem> <AiToolBlockItem title={t('AiTools-DecorationDesign-Operate-pc-chDk')} size="large" hidden={versionValue === 'EXTREME_SPEED'} extra={ <ChartletUploadBtn onUploadSuccess={onChartletUploadSuccess}> {t('AiTools-ImageSelectorModal-b6NR')} </ChartletUploadBtn> } > {!!chartletCategoryList.length && ( <ImageSticker onItemClick={onChartletItemClick} cateList={chartletCategoryList} /> )} </AiToolBlockItem> <AiToolBlockItem title={t('AiTools-DecorationDesign-Operate-pc-C7bC')} size="large" hidden={versionValue === 'EXTREME_SPEED'} extra={ versionValue === 'STANDARD' && ( <ThemeSegmented size="small" value={generatePriorityType} options={generatePriorityList.map((item) => ({ label: t(item.labelKey), value: item.value, }))} onChange={(item) => onGeneratePriorityTypeChange(item.value as GeneratePriorityType)} /> ) } > <ThemeSlider className={cx('slider')} value={weight} dots={false} onChange={setWeight} step={0.1} min={0} max={weightSliderMaxSize} /> </AiToolBlockItem> <AiToolBlockItem hidden={versionValue === 'EXTREME_SPEED'} title={t('AiTools-DetailEnhance-Operate-pc-DwnF')} size="large" > <DrawSizeControls value={sizeType} list={advanceDrawSizeTypeList} onChange={(item: DrawSizeType) => { setSizeType(item); }} /> </AiToolBlockItem> </> ); const contentTopRender = ( <> <AiToolBlockItem title={t(‘AiTools-InspirationDraw-Operate-pc-babel’)} size=“large”> <ThemeSegmented size=“large” className={cx(‘version_grid’)} options={[ { label: ( <div> {t(‘AiTools-PlanColorizer-Operate-pc-Fess’)} <span className={cx(‘vip_text’)}>{t(‘Pro-vip-prefix’)}</span> </div> ), value: ‘ADVANCED’, }, { label: ( <div> {t(‘AiTools-PlanColorizer-Operate-pc-Jsonb’)} <span className={cx(‘vip_text’)}>{t(‘Pro-vip-prefix’)}</span> </div> ), value: ‘EXTREME_SPEED’, }, { label: t(‘AiTools-PlanColorizer-Operate-pc-bess’), value: ‘STANDARD’, }, ]} value={versionValue} onChange={handleVersionPcChange} /> </AiToolBlockItem> </> ); return ( <> <FeatureLeaferFrameLayoutPc ref={featureLeaferFrameLayoutRef} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} onStart={generateTask} download={{ enlargeDownloadOptions, }} rightHeader={{ header: { title: t(DECORATION_DESIGN_TOOL_NAME_KEY), videoUrl: decorationDesignVideoUrl, }, }} content={{ top: contentTopRender, bottom: contentBottomRender, }} imageAddLayer=“frame” uploadCard={{ customUploadBtn: (className) => ( <div className={className} onClick={() => { imageSelectorModalRef.current?.setModalStatus(‘SELECT_MODAL’); }} > {t(‘HuabanUploadBtn-rWrt’)} </div> ), }} customUploadBtn={ <ThemeButton size=“large” ghost className={cx(‘custom_upload_btn’)} onClick={() => { imageSelectorModalRef.current?.setModalStatus(‘SELECT_MODAL’); }} > {t(‘AiTools-LeaferFrameLayout-pc-bHEM’)} </ThemeButton> } editTool={ versionValue !== ‘EXTREME_SPEED’ && { title: t(‘AiTools-ImageEditorModal-pc-NP8N’), operationTypeList: decorationOperationTypeList, brushTypeList: decorationBrushTypeList, } } modifyTool={ versionValue !== ‘EXTREME_SPEED’ && { title: t(‘AiTools-ImageEditorModal-pc-Q4Zr’), enableSmearRepair: true, } } onContinue={onContinue} leaferFrame={{ defaultBrushType: ‘LINE’, defaultBrushSize: 3, defaultBrushColorRgb: [0, 0, 0], opacityConfig: { paintingWritGroup: 1, }, }} footerExtra={ <ToolEquityCountWrap ref={toolEquityCountWrapRef} toolKey={decorationDesignToolKey} showToolName={t(DECORATION_DESIGN_TOOL_NAME_KEY)} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} disabled={noLimitFrequency} > <ToolBenefitOperator style={{ marginTop: 6 }} hidden={noLimitFrequency || userAuth([‘TOURIST’])} toolKey={decorationDesignToolKey} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} showToolEquityInfo={() => { toolEquityCountWrapRef.current?.showToolEquityInfo(); }} showToolCountUsageIntro={() => { toolEquityCountWrapRef.current?.showToolCountUsageIntro(); }} /> </ToolEquityCountWrap> } /> {/* 旧房改造进度- 标准版 */} <AiToolGenerateModal<RequestJzxzDrawImageParams> customerRef={generateModalRef} toolKey={decorationDesignToolKey} speedupKey={decorationDesignSpeedupKey} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} pollingConfig={{ processingType: 'DECORATION_DESIGN', imageDetectMode: 'NONE', }} request={requestJzxzDrawImage} onGenerateSuccess={onGenerateSuccess} /> {/* 旧房改造进度-高级版 */} <AiToolGenerateModal customerRef={comfyUiGenerateModalRef} toolKey={decorationDesignToolKey} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} speedupKey={decorationDesignSpeedupKey} pollingConfig={{ processingType: 'COMFYUI_COMPOSE', imageDetectMode: 'NONE', }} request={requestDecorationDesign} onGenerateSuccess={onGenerateSuccess} renderStatusSection={{ EXECUTING: { title: '室内装修改造中...', }, }} /> {/* 极速模式 */} <AiToolGenerateModal customerRef={generateSpeedModalRef} toolKey={decorationDesignToolKey} trackToolName={TRACK_DECORATION_DESIGN_TOOL_NAME} customerRequest={() => {}} renderStatusSection={{ EXECUTING: { title: '室内装修改造中...', }, POLLING_ERROR: { title: t('AiTools-GenerateModal-YdY7'), extra: errorMsg && <span className={cx('error_msg')}>{errorMsg}</span>, }, }} /> {/* 底图图库 */} <ImageSelectorModal ref={imageSelectorModalRef} queryTagImages={{ modalTitle: t('AiTools-DecorationDesign-Operate-pc-xkdY'), introduce: t('AiTools-DecorationDesign-Operate-pc-f4eJ'), categoryList: queryTagList, iconClassName: cx('image_selector_cover'), }} onFinish={onSelectorModalFinish} /> {/* 上传风格参考图图库 */} <ImageSelectorModal ref={comfySourceImageSelectorModalRef} queryTagImages={{ introduce: t('AiTools-DecorationDesign-Operate-pc-xkdY'), modalTitle: t('AiTools-DecorationDesign-Operate-pc-f4eJ'), categoryList: queryTagList, iconClassName: cx('image_selector_cover'), }} onFinish={onDecorationDesignReferImageModalFinish} /> {/* 裁剪弹窗 */} <UploadCropperModal ref={uploadCropperModalRef} open={cropperModalOpen} modalTitle={ <> <div className={cx('cropper_title')}>{t('AiTools-ImageEditorModal-pc-EKx2')}</div> <div className={cx('cropper_description')}> {t('AiTools-TransferSmudge-Operate-pc-dZW2')} </div> </> } maskClosable onCropperSuccess={onStyleReferImageCropperSuccess} onCropperError={onStyleReferImageCropperError} onCancel={() => { setCropperModalOpen(false); }} /> {/* 会员权益限制弹窗 */} <AiVipEquityLimitModal ref={DecorationDesignipLimitModalRef} /> </> ); }; export default DecorationDesignPc; 怎么实现点击切换alias可以随机对应key值下activeCategories的内容,同时切换后点击随机,对应activeCategories依旧可以随机,并且保持自定义 if (promptType === ‘USER’ || promptType === ‘INNOVATIVE’) { if (value === ‘ADVANCED’) { styleConfigTextAreaRef.current?.updateCandidateConfig( activeCategories[currentStyleIndex].promptsList, false, ); return; } if (value === ‘EXTREME_SPEED’) { styleConfigTextAreaRef.current?.updateCandidateConfig( decorationDesignSpeedModePrompts, false, ); return; } if (value === ‘STANDARD’) { const defaultRefs = categoryRefsGroup[currentStyleIndex]; // 获取对应的类目配置数据 const categories = drawStyleCategoryRef.current?.styleConfigList || []; // 手动触发选择逻辑 const ids = getStyleDataIndexByRefs('id', categories, defaultRefs); if (ids.length) { styleConfigTextAreaRef.current?.updateCandidateConfigByIds(ids, false); } } return; }不变
时间: 2025-08-10 12:20:40 浏览: 3
<think>嗯,用户的问题是关于如何实现点击切换alias时随机显示对应key下的activeCategories内容,并且在切换后点击随机按钮时,activeCategories的内容依然可以随机,同时保持自定义部分的逻辑不变。我需要仔细分析现有的代码,找出需要修改的部分。
首先,用户提到的当前代码中,当alias切换时,activeCategories会更新,但需要实现随机显示对应的内容。可能需要在切换alias时,随机选择一个category作为当前激活的。例如,当用户点击不同的alias标签时,activeCategories会对应不同的列表,然后随机选中其中一个category。
然后,用户提到在切换后点击随机按钮,activeCategories的内容依旧可以随机。这意味着需要有一个方法,当用户点击随机按钮时,从当前的activeCategories中随机选择一个category。这可能需要一个状态来保存当前的activeCategories,或者每次随机时都根据当前的activeAlias获取对应的categories。接下来,保持自定义逻辑不变,即当用户自定义了prompt类型(USER或INNOVATIVE)时,切换版本时不会覆盖用户的输入。这部分代码已经存在,需要确保在实现随机功能时不影响这部分逻辑。
现有的代码中,当alias切换时,会调用setActiveAlias来更新activeAlias,进而activeCategories会根据comfyUiStyleList中对应的alias筛选出来。此时,currentStyleIndex可能还是之前的索引,需要重置为0或者随机一个索引。例如,在setActiveAlias的同时,将currentStyleIndex设置为0,或者随机生成一个新的索引。但用户希望的是切换alias时随机选择一个category,所以应该在切换alias时,随机生成currentStyleIndex的值。例如,当用户点击alias标签时,获取对应的categories数组的长度,然后生成一个随机索引,并更新currentStyleIndex为该随机值。此外,当用户点击随机按钮时,无论当前activeAlias是什么,都从activeCategories中随机选择一个category。这可能需要一个独立的函数来处理随机逻辑,并在点击按钮时调用该函数。
需要检查现有的onComfyUiStyleIndexChange函数,该函数目前是在用户点击某个category时设置currentStyleIndex。可能需要修改该函数,或者在alias切换时触发随机选择。
可能还需要在alias切换的回调函数中,添加随机生成currentStyleIndex的逻辑。例如,在setActiveAlias之后,计算新的activeCategories的长度,生成随机索引,并调用onComfyUiStyleIndexChange来更新currentStyleIndex。另外,需要确保在版本切换时,自定义逻辑不受影响。例如,当用户自定义了prompt,切换版本时不会覆盖其输入,这部分代码已经存在,需要确保在添加随机功能时不会破坏这部分逻辑。总结步骤:1.修改alias切换的处理函数,使其在切换alias时随机选择一个category。
2.创建一个处理随机选择的函数,供alias切换和随机按钮调用。3.确保在切换alias或点击随机按钮时,更新currentStyleIndex,并触发相关的配置更新。4.保持原有的自定义逻辑不变,即在用户自定义prompt类型时,切换版本不覆盖配置。
可能的代码修改点:-在setActiveAlias时,计算新的activeCategories,并生成随机索引,设置currentStyleIndex。-创建一个函数,如handleRandomCategory,用于从当前activeCategories中随机选择。-在UI中添加随机按钮,并绑定处理函数。</think>要实现点击切换alias时随机显示对应key下的activeCategories内容,并在切换后保持随机能力,同时保留原有自定义逻辑,可以按以下步骤修改:
```typescript
// 新增随机选择函数
const handleRandomCategory = () => {
if (activeCategories.length === 0) return;
const randomIndex = Math.floor(Math.random() * activeCategories.length);
setCurrentStyleIndex(randomIndex);
// 触发配置更新
const currentItem = activeCategories[randomIndex];
styleConfigTextAreaRef.current?.updateCandidateConfig(
currentItem.promptsList,
true
);
};
// 修改alias切换函数
const handleAliasChange = (alias: string) => {
setActiveAlias(alias);
// 获取新的categories列表
const newCategories = comfyUiStyleList.find(
item => item.alias === alias
)?.categories || [];
// 自动触发随机选择
if (newCategories.length > 0) {
const randomIndex = Math.floor(Math.random() * newCategories.length);
setCurrentStyleIndex(randomIndex);
// 更新候选配置
const currentItem = newCategories[randomIndex];
styleConfigTextAreaRef.current?.updateCandidateConfig(
currentItem.promptsList,
true
);
}
};
// 修改版本切换逻辑
const handleVersionChange = (value: VersionValueType) => {
// 保留原有自定义逻辑
if (promptType === 'USER' || promptType === 'INNOVATIVE') {
if (value === 'ADVANCED') {
// 从当前激活的categories中随机选择
const randomIndex = Math.floor(Math.random() * activeCategories.length);
styleConfigTextAreaRef.current?.updateCandidateConfig(
activeCategories[randomIndex].promptsList,
false
);
return;
}
// ...其他版本逻辑保持不变
}
// 新版本切换时触发随机
handleRandomCategory();
};
```
在UI层添加随机按钮:
```tsx
<AiToolBlockItem
title={t('AiTools-DecorationDesign-Operate-pc-twQz')}
size="large"
hidden={versionValue !== 'ADVANCED'}
extra={
<ThemeButton
size="small"
onClick={handleRandomCategory}
>
{t('Random')}
</ThemeButton>
}
>
{/* 原有内容保持不变 */}
</AiToolBlockItem>
```
关键修改说明:
1. 新增`handleRandomCategory`函数处理随机逻辑
2. 修改alias切换逻辑自动触发随机选择
3. 版本切换时保留自定义逻辑的同时触发随机
4. 添加UI按钮提供手动随机能力
**保持原有自定义逻辑的关键点:**
- 在版本切换条件判断中,当检测到用户自定义类型时:
- 从当前激活的categories中随机选择而非固定索引
- 保持`false`参数避免覆盖用户输入
- 在随机逻辑中保持`updateCandidateConfig`的第二个参数为`true`,仅在非自定义模式时覆盖配置
阅读全文
相关推荐





