活动介绍

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`,仅在非自定义模式时覆盖配置
阅读全文

相关推荐

zip
资源下载链接为: https://pan.quark.cn/s/1bfadf00ae14 “STC单片机电压测量”是一个以STC系列单片机为基础的电压检测应用案例,它涵盖了硬件电路设计、软件编程以及数据处理等核心知识点。STC单片机凭借其低功耗、高性价比和丰富的I/O接口,在电子工程领域得到了广泛应用。 STC是Specialized Technology Corporation的缩写,该公司的单片机基于8051内核,具备内部振荡器、高速运算能力、ISP(在系统编程)和IAP(在应用编程)功能,非常适合用于各种嵌入式控制系统。 在源代码方面,“浅雪”风格的代码通常简洁易懂,非常适合初学者学习。其中,“main.c”文件是程序的入口,包含了电压测量的核心逻辑;“STARTUP.A51”是启动代码,负责初始化单片机的硬件环境;“电压测量_uvopt.bak”和“电压测量_uvproj.bak”可能是Keil编译器的配置文件备份,用于设置编译选项和项目配置。 对于3S锂电池电压测量,3S锂电池由三节锂离子电池串联而成,标称电压为11.1V。测量时需要考虑电池的串联特性,通过分压电路将高电压转换为单片机可接受的范围,并实时监控,防止过充或过放,以确保电池的安全和寿命。 在电压测量电路设计中,“电压测量.lnp”文件可能包含电路布局信息,而“.hex”文件是编译后的机器码,用于烧录到单片机中。电路中通常会使用ADC(模拟数字转换器)将模拟电压信号转换为数字信号供单片机处理。 在软件编程方面,“StringData.h”文件可能包含程序中使用的字符串常量和数据结构定义。处理电压数据时,可能涉及浮点数运算,需要了解STC单片机对浮点数的支持情况,以及如何高效地存储和显示电压值。 用户界面方面,“电压测量.uvgui.kidd”可能是用户界面的配置文件,用于显示测量结果。在嵌入式系统中,用

大家在看

recommend-type

基于ADS的微带滤波器设计

微波滤波器是用来分离不同频率微波信号的一种器件。它的主要作用是抑制不需要的信号,使其不能通过滤波器,只让需要的信号通过。在微波电路系统中,滤波器的性能对电路的性能指标有很大的影响,因此如何设计出一个具有高性能的滤波器,对设计微波电路系统具有很重要的意义。
recommend-type

Pixhawk4飞控驱动.zip

已安装成功
recommend-type

ztecfg中兴配置加解密工具3.0版本.rar

中兴光猫配置文件加解密工具3.0 .\ztecfg.exe -d AESCBC -i .\(要解密的文件名)db_user_cfg.xml -o (解密后文件名)123.cfg
recommend-type

配置车辆-feedback systems_an introduction for scientists and engineers

5.2 道路场景 从界面右侧的道路场景列表中,双击载入所需的道路场景(如 Fld_FreeWay)。 PanoSim提供了 ADAS标准(ISO、Euro NCAP)典型场景库,如高速公路、乡村道路、 城镇、坡道、换道、停车场、高速出入口等。我们可根据用户需要定制丰富场景库。 PanoSim提供专门的道路场景设计工具,可通过常用工具栏\Tools\FieldBuilder 来创建自己的道路场景。 5.3 天气和光照 从右侧的实验环境列表栏中,通过双击载入所需的实验天气和光照。天气有多 云、雾天、雨天、雪天、晴天,光照有白天和夜晚,相关实验信息(如所选场景、天 气、车辆等),可在左侧实验信息栏中查看。 5.4 配置车辆 点击“Forward”,进入实验参数设置主界面(图 5-2)。
recommend-type

xilinx.com_user_IIC_AXI_1.0.zip

可以直接用在vivado 2017.4版本里。查看各个寄存器就知道用来干什么了,一号寄存器分频系数,二号的start、stop信号,三号寄存器8bit数据,四号寄存器只读,返回IIC状态和ACK信号,其中二号的一个bit可以用来不等待从机ACK,方便使用。

最新推荐

recommend-type

开发界面语义化:声控 + 画图协同生成代码.doc

开发界面语义化:声控 + 画图协同生成代码.doc
recommend-type

LABVIEW与三菱PLC通信:实现数据批量读写的高效库解决方案

如何通过LabVIEW与三菱PLC建立高效的通信桥梁,实现数据批量读写。首先概述了LabVIEW和三菱PLC的基本概念及其在工业自动化中的重要性。接着重点讲解了利用Modbus RTU协议构建通信连接的具体步骤和技术细节,包括初始化通信、发送读写请求、处理响应数据和关闭连接等功能。文中还提供了一个简化的代码示例,展示了如何在LabVIEW环境中实现这一过程。最后对这项技术进行了总结和展望,强调其在提高数据交互效率方面的潜力以及未来的广泛应用前景。 适合人群:从事工业自动化领域的工程师和技术人员,尤其是那些熟悉LabVIEW或三菱PLC的人士。 使用场景及目标:适用于需要频繁进行数据交互的工业控制系统,如生产线监控、设备状态监测等场合。主要目的是提升数据传输的速度和可靠性,从而优化整个系统的运行效率。 阅读建议:读者可以通过本文深入了解LabVIEW与三菱PLC通信的实现方法,掌握批量数据读写库的设计思路,并将其应用于实际工程项目中。建议边阅读边尝试动手实践相关代码,以便更好地理解和吸收所学知识。
recommend-type

欧姆龙PLC NJ系列模切机程序:高级伺服运动与张力控制的应用实例

欧姆龙PLC NJ系列模切机项目的编程细节及其关键技术。主要内容涵盖12轴EtherCAT总线伺服运动控制,包括回零、点动、定位和速度控制;张力控制采用PID算法并进行收放卷径计算;隔膜自动纠偏控制利用模拟量数据平均化处理;同步运动控制实现凸轮表追剪和裁切;以及结构化编程和ST语言功能块的使用。项目结构规范,注释详尽,有助于理解和维护代码。通过本项目的学习,可以掌握PLC高端复杂应用的实际操作技能。 适合人群:从事工业自动化领域的工程师和技术人员,特别是对PLC编程和伺服运动控制有浓厚兴趣的人群。 使用场景及目标:适用于需要深入了解PLC编程技巧和自动化控制系统原理的技术人员。目标是提升编程能力和对复杂自动化系统的工作机制的理解。 其他说明:本文不仅提供具体的编程指导,还强调了项目管理和代码规范的重要性,为读者提供了全面的学习体验。
recommend-type

大班主题性区域活动计划表.doc

大班主题性区域活动计划表.doc
recommend-type

高校教研室工作计划.doc

高校教研室工作计划.doc
recommend-type

Python程序TXLWizard生成TXL文件及转换工具介绍

### 知识点详细说明: #### 1. 图形旋转与TXL向导 图形旋转是图形学领域的一个基本操作,用于改变图形的方向。在本上下文中,TXL向导(TXLWizard)是由Esteban Marin编写的Python程序,它实现了特定的图形旋转功能,主要用于电子束光刻掩模的生成。光刻掩模是半导体制造过程中非常关键的一个环节,它确定了在硅片上沉积材料的精确位置。TXL向导通过生成特定格式的TXL文件来辅助这一过程。 #### 2. TXL文件格式与用途 TXL文件格式是一种基于文本的文件格式,它设计得易于使用,并且可以通过各种脚本语言如Python和Matlab生成。这种格式通常用于电子束光刻中,因为它的文本形式使得它可以通过编程快速创建复杂的掩模设计。TXL文件格式支持引用对象和复制对象数组(如SREF和AREF),这些特性可以用于优化电子束光刻设备的性能。 #### 3. TXLWizard的特性与优势 - **结构化的Python脚本:** TXLWizard 使用结构良好的脚本来创建遮罩,这有助于开发者创建清晰、易于维护的代码。 - **灵活的Python脚本:** 作为Python程序,TXLWizard 可以利用Python语言的灵活性和强大的库集合来编写复杂的掩模生成逻辑。 - **可读性和可重用性:** 生成的掩码代码易于阅读,开发者可以轻松地重用和修改以适应不同的需求。 - **自动标签生成:** TXLWizard 还包括自动为图形对象生成标签的功能,这在管理复杂图形时非常有用。 #### 4. TXL转换器的功能 - **查看.TXL文件:** TXL转换器(TXLConverter)允许用户将TXL文件转换成HTML或SVG格式,这样用户就可以使用任何现代浏览器或矢量图形应用程序来查看文件。 - **缩放和平移:** 转换后的文件支持缩放和平移功能,这使得用户在图形界面中更容易查看细节和整体结构。 - **快速转换:** TXL转换器还提供快速的文件转换功能,以实现有效的蒙版开发工作流程。 #### 5. 应用场景与技术参考 TXLWizard的应用场景主要集中在电子束光刻技术中,特别是用于设计和制作半导体器件时所需的掩模。TXLWizard作为一个向导,不仅提供了生成TXL文件的基础框架,还提供了一种方式来优化掩模设计,提高光刻过程的效率和精度。对于需要进行光刻掩模设计的工程师和研究人员来说,TXLWizard提供了一种有效的方法来实现他们的设计目标。 #### 6. 系统开源特性 标签“系统开源”表明TXLWizard遵循开放源代码的原则,这意味着源代码对所有人开放,允许用户自由地查看、修改和分发软件。开源项目通常拥有活跃的社区,社区成员可以合作改进软件,添加新功能,或帮助解决遇到的问题。这种开放性促进了技术创新,并允许用户根据自己的需求定制软件。 #### 7. 压缩包子文件的文件名称列表 文件名称列表中的“txlwizard-master”可能指的是TXLWizard项目的主版本库或主分支。这个名称表明了这是项目源代码的中心点,其他开发者会从这个主分支拉取代码进行合作开发或部署。以“-master”结尾通常是版本控制系统中表示主要开发线路的常见约定,例如Git中的master(现在更常被称为main)分支。 通过这些知识点的详细解释,我们可以看到TXLWizard不仅是一个用于生成TXL文件的工具,它还整合了一系列的功能,使得电子束光刻掩模的设计工作更为高效和直观。同时,作为一个开源项目,它能够借助社区的力量不断进步,为用户带来更多的便利和创新。
recommend-type

【创新图生成:扣子平台的技术前沿与创新思维】:引领图像生成技术的新潮流

# 1. 扣子平台的图像生成技术概述 在当今数字化时代,图像生成技术正成为推动技术创新和社会变革的关键力量。扣子平台正是这一技术革新的代表。本章将对扣子平台的图像生成技术进行一个全面概述,探究它如何将理论技术转化为实际应用,并带来显著的社会影响。 扣子平台运用了先进的图像生成技术,将现实世界和数字空间无缝融合,打造了沉浸式的视觉体验。其技术的核心是通过高度复杂的算法模型,快速生成高质
recommend-type

海康威视机器视觉工程师考核

<think>好的,我现在需要帮助用户了解海康威视机器视觉工程师的考核内容和要求。根据用户提供的引用资料,特别是引用[1]和[2],里面提到了考核素材包分为初级和中级,涵盖理论、算法、应用案例等。首先,我要整理这些信息,确保结构清晰,符合用户要求的格式。 接下来,我需要确认素材包的具体内容,比如初级和中级的不同点。引用[2]提到初级包含基础理论、算法实现和实际案例,中级则增加复杂算法和项目分析。这部分需要分点说明,方便用户理解层次。 另外,用户可能想知道如何准备考核,比如下载素材、学习顺序、模拟考核等,引用[2]中有使用说明和注意事项,这部分也要涵盖进去。同时要注意提醒用户考核窗口已关闭,
recommend-type

Linux环境下Docker Hub公共容器映像检测工具集

在给出的知识点中,我们需要详细解释有关Docker Hub、公共容器映像、容器编排器以及如何与这些工具交互的详细信息。同时,我们会涵盖Linux系统下的相关操作和工具使用,以及如何在ECS和Kubernetes等容器编排工具中运用这些检测工具。 ### Docker Hub 和公共容器映像 Docker Hub是Docker公司提供的一项服务,它允许用户存储、管理以及分享Docker镜像。Docker镜像可以视为应用程序或服务的“快照”,包含了运行特定软件所需的所有必要文件和配置。公共容器映像指的是那些被标记为公开可见的Docker镜像,任何用户都可以拉取并使用这些镜像。 ### 静态和动态标识工具 静态和动态标识工具在Docker Hub上用于识别和分析公共容器映像。静态标识通常指的是在不运行镜像的情况下分析镜像的元数据和内容,例如检查Dockerfile中的指令、环境变量、端口映射等。动态标识则需要在容器运行时对容器的行为和性能进行监控和分析,如资源使用率、网络通信等。 ### 容器编排器与Docker映像 容器编排器是用于自动化容器部署、管理和扩展的工具。在Docker环境中,容器编排器能够自动化地启动、停止以及管理容器的生命周期。常见的容器编排器包括ECS和Kubernetes。 - **ECS (Elastic Container Service)**:是由亚马逊提供的容器编排服务,支持Docker容器,并提供了一种简单的方式来运行、停止以及管理容器化应用程序。 - **Kubernetes**:是一个开源平台,用于自动化容器化应用程序的部署、扩展和操作。它已经成为容器编排领域的事实标准。 ### 如何使用静态和动态标识工具 要使用这些静态和动态标识工具,首先需要获取并安装它们。从给定信息中了解到,可以通过克隆仓库或下载压缩包并解压到本地系统中。之后,根据需要针对不同的容器编排环境(如Dockerfile、ECS、Kubernetes)编写配置,以集成和使用这些检测工具。 ### Dockerfile中的工具使用 在Dockerfile中使用工具意味着将检测工具的指令嵌入到构建过程中。这可能包括安装检测工具的命令、运行容器扫描的步骤,以及将扫描结果集成到镜像构建流程中,确保只有通过安全和合规检查的容器镜像才能被构建和部署。 ### ECS与Kubernetes中的工具集成 在ECS或Kubernetes环境中,工具的集成可能涉及到创建特定的配置文件、定义服务和部署策略,以及编写脚本或控制器来自动执行检测任务。这样可以在容器编排的过程中实现实时监控,确保容器编排器只使用符合预期的、安全的容器镜像。 ### Linux系统下的操作 在Linux系统下操作这些工具,用户可能需要具备一定的系统管理和配置能力。这包括使用Linux命令行工具、管理文件系统权限、配置网络以及安装和配置软件包等。 ### 总结 综上所述,Docker Hub上的静态和动态标识工具提供了一种方法来检测和分析公共容器映像,确保这些镜像的安全性和可靠性。这些工具在Linux开发环境中尤为重要,因为它们帮助开发人员和运维人员确保他们的容器映像满足安全要求。通过在Dockerfile、ECS和Kubernetes中正确使用这些工具,可以提高应用程序的安全性,减少由于使用不安全的容器镜像带来的风险。此外,掌握Linux系统下的操作技能,可以更好地管理和维护这些工具,确保它们能够有效地发挥作用。
recommend-type

【扣子平台图像艺术探究:理论与实践的完美结合】:深入学习图像生成的艺术

# 1. 图像艺术的理论基础 艺术领域的每一个流派和技巧都有其理论基础。在图像艺术中,理论基础不仅是对艺术表现形式的认知,也是掌握艺术创作内在逻辑的关键。深入理解图像艺术的理论基础,能够帮助艺术家们在创作过程中更加明确地表达自己的艺术意图,以及更好地与观众沟通。 图像艺术的理论