You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
	
	
		
			322 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			TypeScript
		
	
		
		
			
		
	
	
			322 lines
		
	
	
		
			9.2 KiB
		
	
	
	
		
			TypeScript
		
	
| 
											9 months ago
										 | import styles from "./sd-panel.module.scss"; | ||
|  | import React from "react"; | ||
|  | import { Select } from "@/app/components/ui-lib"; | ||
|  | import { IconButton } from "@/app/components/button"; | ||
|  | import Locale from "@/app/locales"; | ||
|  | import { useSdStore } from "@/app/store/sd"; | ||
|  | import clsx from "clsx"; | ||
|  | 
 | ||
|  | export const params = [ | ||
|  |   { | ||
|  |     name: Locale.SdPanel.Prompt, | ||
|  |     value: "prompt", | ||
|  |     type: "textarea", | ||
|  |     placeholder: Locale.SdPanel.PleaseInput(Locale.SdPanel.Prompt), | ||
|  |     required: true, | ||
|  |   }, | ||
|  |   { | ||
|  |     name: Locale.SdPanel.ModelVersion, | ||
|  |     value: "model", | ||
|  |     type: "select", | ||
|  |     default: "sd3-medium", | ||
|  |     support: ["sd3"], | ||
|  |     options: [ | ||
|  |       { name: "SD3 Medium", value: "sd3-medium" }, | ||
|  |       { name: "SD3 Large", value: "sd3-large" }, | ||
|  |       { name: "SD3 Large Turbo", value: "sd3-large-turbo" }, | ||
|  |     ], | ||
|  |   }, | ||
|  |   { | ||
|  |     name: Locale.SdPanel.NegativePrompt, | ||
|  |     value: "negative_prompt", | ||
|  |     type: "textarea", | ||
|  |     placeholder: Locale.SdPanel.PleaseInput(Locale.SdPanel.NegativePrompt), | ||
|  |   }, | ||
|  |   { | ||
|  |     name: Locale.SdPanel.AspectRatio, | ||
|  |     value: "aspect_ratio", | ||
|  |     type: "select", | ||
|  |     default: "1:1", | ||
|  |     options: [ | ||
|  |       { name: "1:1", value: "1:1" }, | ||
|  |       { name: "16:9", value: "16:9" }, | ||
|  |       { name: "21:9", value: "21:9" }, | ||
|  |       { name: "2:3", value: "2:3" }, | ||
|  |       { name: "3:2", value: "3:2" }, | ||
|  |       { name: "4:5", value: "4:5" }, | ||
|  |       { name: "5:4", value: "5:4" }, | ||
|  |       { name: "9:16", value: "9:16" }, | ||
|  |       { name: "9:21", value: "9:21" }, | ||
|  |     ], | ||
|  |   }, | ||
|  |   { | ||
|  |     name: Locale.SdPanel.ImageStyle, | ||
|  |     value: "style", | ||
|  |     type: "select", | ||
|  |     default: "3d-model", | ||
|  |     support: ["core"], | ||
|  |     options: [ | ||
|  |       { name: Locale.SdPanel.Styles.D3Model, value: "3d-model" }, | ||
|  |       { name: Locale.SdPanel.Styles.AnalogFilm, value: "analog-film" }, | ||
|  |       { name: Locale.SdPanel.Styles.Anime, value: "anime" }, | ||
|  |       { name: Locale.SdPanel.Styles.Cinematic, value: "cinematic" }, | ||
|  |       { name: Locale.SdPanel.Styles.ComicBook, value: "comic-book" }, | ||
|  |       { name: Locale.SdPanel.Styles.DigitalArt, value: "digital-art" }, | ||
|  |       { name: Locale.SdPanel.Styles.Enhance, value: "enhance" }, | ||
|  |       { name: Locale.SdPanel.Styles.FantasyArt, value: "fantasy-art" }, | ||
|  |       { name: Locale.SdPanel.Styles.Isometric, value: "isometric" }, | ||
|  |       { name: Locale.SdPanel.Styles.LineArt, value: "line-art" }, | ||
|  |       { name: Locale.SdPanel.Styles.LowPoly, value: "low-poly" }, | ||
|  |       { | ||
|  |         name: Locale.SdPanel.Styles.ModelingCompound, | ||
|  |         value: "modeling-compound", | ||
|  |       }, | ||
|  |       { name: Locale.SdPanel.Styles.NeonPunk, value: "neon-punk" }, | ||
|  |       { name: Locale.SdPanel.Styles.Origami, value: "origami" }, | ||
|  |       { name: Locale.SdPanel.Styles.Photographic, value: "photographic" }, | ||
|  |       { name: Locale.SdPanel.Styles.PixelArt, value: "pixel-art" }, | ||
|  |       { name: Locale.SdPanel.Styles.TileTexture, value: "tile-texture" }, | ||
|  |     ], | ||
|  |   }, | ||
|  |   { | ||
|  |     name: "Seed", | ||
|  |     value: "seed", | ||
|  |     type: "number", | ||
|  |     default: 0, | ||
|  |     min: 0, | ||
|  |     max: 4294967294, | ||
|  |   }, | ||
|  |   { | ||
|  |     name: Locale.SdPanel.OutFormat, | ||
|  |     value: "output_format", | ||
|  |     type: "select", | ||
|  |     default: "png", | ||
|  |     options: [ | ||
|  |       { name: "PNG", value: "png" }, | ||
|  |       { name: "JPEG", value: "jpeg" }, | ||
|  |       { name: "WebP", value: "webp" }, | ||
|  |     ], | ||
|  |   }, | ||
|  | ]; | ||
|  | 
 | ||
|  | const sdCommonParams = (model: string, data: any) => { | ||
|  |   return params.filter((item) => { | ||
|  |     return !(item.support && !item.support.includes(model)); | ||
|  |   }); | ||
|  | }; | ||
|  | 
 | ||
|  | export const models = [ | ||
|  |   { | ||
|  |     name: "Stable Image Ultra", | ||
|  |     value: "ultra", | ||
|  |     params: (data: any) => sdCommonParams("ultra", data), | ||
|  |   }, | ||
|  |   { | ||
|  |     name: "Stable Image Core", | ||
|  |     value: "core", | ||
|  |     params: (data: any) => sdCommonParams("core", data), | ||
|  |   }, | ||
|  |   { | ||
|  |     name: "Stable Diffusion 3", | ||
|  |     value: "sd3", | ||
|  |     params: (data: any) => { | ||
|  |       return sdCommonParams("sd3", data).filter((item) => { | ||
|  |         return !( | ||
|  |           data.model === "sd3-large-turbo" && item.value == "negative_prompt" | ||
|  |         ); | ||
|  |       }); | ||
|  |     }, | ||
|  |   }, | ||
|  | ]; | ||
|  | 
 | ||
|  | export function ControlParamItem(props: { | ||
|  |   title: string; | ||
|  |   subTitle?: string; | ||
|  |   required?: boolean; | ||
|  |   children?: JSX.Element | JSX.Element[]; | ||
|  |   className?: string; | ||
|  | }) { | ||
|  |   return ( | ||
|  |     <div className={clsx(styles["ctrl-param-item"], props.className)}> | ||
|  |       <div className={styles["ctrl-param-item-header"]}> | ||
|  |         <div className={styles["ctrl-param-item-title"]}> | ||
|  |           <div> | ||
|  |             {props.title} | ||
|  |             {props.required && <span style={{ color: "red" }}>*</span>} | ||
|  |           </div> | ||
|  |         </div> | ||
|  |       </div> | ||
|  |       {props.children} | ||
|  |       {props.subTitle && ( | ||
|  |         <div className={styles["ctrl-param-item-sub-title"]}> | ||
|  |           {props.subTitle} | ||
|  |         </div> | ||
|  |       )} | ||
|  |     </div> | ||
|  |   ); | ||
|  | } | ||
|  | 
 | ||
|  | export function ControlParam(props: { | ||
|  |   columns: any[]; | ||
|  |   data: any; | ||
|  |   onChange: (field: string, val: any) => void; | ||
|  | }) { | ||
|  |   return ( | ||
|  |     <> | ||
|  |       {props.columns?.map((item) => { | ||
|  |         let element: null | JSX.Element; | ||
|  |         switch (item.type) { | ||
|  |           case "textarea": | ||
|  |             element = ( | ||
|  |               <ControlParamItem | ||
|  |                 title={item.name} | ||
|  |                 subTitle={item.sub} | ||
|  |                 required={item.required} | ||
|  |               > | ||
|  |                 <textarea | ||
|  |                   rows={item.rows || 3} | ||
|  |                   style={{ maxWidth: "100%", width: "100%", padding: "10px" }} | ||
|  |                   placeholder={item.placeholder} | ||
|  |                   onChange={(e) => { | ||
|  |                     props.onChange(item.value, e.currentTarget.value); | ||
|  |                   }} | ||
|  |                   value={props.data[item.value]} | ||
|  |                 ></textarea> | ||
|  |               </ControlParamItem> | ||
|  |             ); | ||
|  |             break; | ||
|  |           case "select": | ||
|  |             element = ( | ||
|  |               <ControlParamItem | ||
|  |                 title={item.name} | ||
|  |                 subTitle={item.sub} | ||
|  |                 required={item.required} | ||
|  |               > | ||
|  |                 <Select | ||
|  |                   aria-label={item.name} | ||
|  |                   value={props.data[item.value]} | ||
|  |                   onChange={(e) => { | ||
|  |                     props.onChange(item.value, e.currentTarget.value); | ||
|  |                   }} | ||
|  |                 > | ||
|  |                   {item.options.map((opt: any) => { | ||
|  |                     return ( | ||
|  |                       <option value={opt.value} key={opt.value}> | ||
|  |                         {opt.name} | ||
|  |                       </option> | ||
|  |                     ); | ||
|  |                   })} | ||
|  |                 </Select> | ||
|  |               </ControlParamItem> | ||
|  |             ); | ||
|  |             break; | ||
|  |           case "number": | ||
|  |             element = ( | ||
|  |               <ControlParamItem | ||
|  |                 title={item.name} | ||
|  |                 subTitle={item.sub} | ||
|  |                 required={item.required} | ||
|  |               > | ||
|  |                 <input | ||
|  |                   aria-label={item.name} | ||
|  |                   type="number" | ||
|  |                   min={item.min} | ||
|  |                   max={item.max} | ||
|  |                   value={props.data[item.value] || 0} | ||
|  |                   onChange={(e) => { | ||
|  |                     props.onChange(item.value, parseInt(e.currentTarget.value)); | ||
|  |                   }} | ||
|  |                 /> | ||
|  |               </ControlParamItem> | ||
|  |             ); | ||
|  |             break; | ||
|  |           default: | ||
|  |             element = ( | ||
|  |               <ControlParamItem | ||
|  |                 title={item.name} | ||
|  |                 subTitle={item.sub} | ||
|  |                 required={item.required} | ||
|  |               > | ||
|  |                 <input | ||
|  |                   aria-label={item.name} | ||
|  |                   type="text" | ||
|  |                   value={props.data[item.value]} | ||
|  |                   style={{ maxWidth: "100%", width: "100%" }} | ||
|  |                   onChange={(e) => { | ||
|  |                     props.onChange(item.value, e.currentTarget.value); | ||
|  |                   }} | ||
|  |                 /> | ||
|  |               </ControlParamItem> | ||
|  |             ); | ||
|  |         } | ||
|  |         return <div key={item.value}>{element}</div>; | ||
|  |       })} | ||
|  |     </> | ||
|  |   ); | ||
|  | } | ||
|  | 
 | ||
|  | export const getModelParamBasicData = ( | ||
|  |   columns: any[], | ||
|  |   data: any, | ||
|  |   clearText?: boolean, | ||
|  | ) => { | ||
|  |   const newParams: any = {}; | ||
|  |   columns.forEach((item: any) => { | ||
|  |     if (clearText && ["text", "textarea", "number"].includes(item.type)) { | ||
|  |       newParams[item.value] = item.default || ""; | ||
|  |     } else { | ||
|  |       // @ts-ignore
 | ||
|  |       newParams[item.value] = data[item.value] || item.default || ""; | ||
|  |     } | ||
|  |   }); | ||
|  |   return newParams; | ||
|  | }; | ||
|  | 
 | ||
|  | export const getParams = (model: any, params: any) => { | ||
|  |   return models.find((m) => m.value === model.value)?.params(params) || []; | ||
|  | }; | ||
|  | 
 | ||
|  | export function SdPanel() { | ||
|  |   const sdStore = useSdStore(); | ||
|  |   const currentModel = sdStore.currentModel; | ||
|  |   const setCurrentModel = sdStore.setCurrentModel; | ||
|  |   const params = sdStore.currentParams; | ||
|  |   const setParams = sdStore.setCurrentParams; | ||
|  | 
 | ||
|  |   const handleValueChange = (field: string, val: any) => { | ||
|  |     setParams({ | ||
|  |       ...params, | ||
|  |       [field]: val, | ||
|  |     }); | ||
|  |   }; | ||
|  |   const handleModelChange = (model: any) => { | ||
|  |     setCurrentModel(model); | ||
|  |     setParams(getModelParamBasicData(model.params({}), params)); | ||
|  |   }; | ||
|  | 
 | ||
|  |   return ( | ||
|  |     <> | ||
|  |       <ControlParamItem title={Locale.SdPanel.AIModel}> | ||
|  |         <div className={styles["ai-models"]}> | ||
|  |           {models.map((item) => { | ||
|  |             return ( | ||
|  |               <IconButton | ||
|  |                 text={item.name} | ||
|  |                 key={item.value} | ||
|  |                 type={currentModel.value == item.value ? "primary" : null} | ||
|  |                 shadow | ||
|  |                 onClick={() => handleModelChange(item)} | ||
|  |               /> | ||
|  |             ); | ||
|  |           })} | ||
|  |         </div> | ||
|  |       </ControlParamItem> | ||
|  |       <ControlParam | ||
|  |         columns={getParams?.(currentModel, params) as any[]} | ||
|  |         data={params} | ||
|  |         onChange={handleValueChange} | ||
|  |       ></ControlParam> | ||
|  |     </> | ||
|  |   ); | ||
|  | } |