sd-panel.tsx 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. import styles from "./sd-panel.module.scss";
  2. import React, { useState } from "react";
  3. import { Select } from "@/app/components/ui-lib";
  4. import { IconButton } from "@/app/components/button";
  5. import locales from "@/app/locales";
  6. const sdCommonParams = (model: string, data: any) => {
  7. return [
  8. {
  9. name: locales.SdPanel.Prompt,
  10. value: "prompt",
  11. type: "textarea",
  12. placeholder: locales.SdPanel.PleaseInput(locales.SdPanel.Prompt),
  13. required: true,
  14. },
  15. {
  16. name: locales.SdPanel.ModelVersion,
  17. value: "model",
  18. type: "select",
  19. default: 0,
  20. support: ["sd3"],
  21. options: [
  22. { name: "SD3 Medium", value: "sd3-medium" },
  23. { name: "SD3 Large", value: "sd3-large" },
  24. { name: "SD3 Large Turbo", value: "sd3-large-turbo" },
  25. ],
  26. },
  27. {
  28. name: locales.SdPanel.NegativePrompt,
  29. value: "negative_prompt",
  30. type: "textarea",
  31. placeholder: locales.SdPanel.PleaseInput(locales.SdPanel.NegativePrompt),
  32. },
  33. {
  34. name: locales.SdPanel.AspectRatio,
  35. value: "aspect_ratio",
  36. type: "select",
  37. default: "1:1",
  38. options: [
  39. { name: "1:1", value: "1:1" },
  40. { name: "16:9", value: "16:9" },
  41. { name: "21:9", value: "21:9" },
  42. { name: "2:3", value: "2:3" },
  43. { name: "3:2", value: "3:2" },
  44. { name: "4:5", value: "4:5" },
  45. { name: "5:4", value: "5:4" },
  46. { name: "9:16", value: "9:16" },
  47. { name: "9:21", value: "9:21" },
  48. ],
  49. },
  50. {
  51. name: locales.SdPanel.ImageStyle,
  52. value: "style",
  53. type: "select",
  54. default: "3d",
  55. support: ["core"],
  56. options: [
  57. { name: locales.SdPanel.Styles.D3Model, value: "3d-model" },
  58. { name: locales.SdPanel.Styles.AnalogFilm, value: "analog-film" },
  59. { name: locales.SdPanel.Styles.Anime, value: "anime" },
  60. { name: locales.SdPanel.Styles.Cinematic, value: "cinematic" },
  61. { name: locales.SdPanel.Styles.ComicBook, value: "comic-book" },
  62. { name: locales.SdPanel.Styles.DigitalArt, value: "digital-art" },
  63. { name: locales.SdPanel.Styles.Enhance, value: "enhance" },
  64. { name: locales.SdPanel.Styles.FantasyArt, value: "fantasy-art" },
  65. { name: locales.SdPanel.Styles.Isometric, value: "isometric" },
  66. { name: locales.SdPanel.Styles.LineArt, value: "line-art" },
  67. { name: locales.SdPanel.Styles.LowPoly, value: "low-poly" },
  68. {
  69. name: locales.SdPanel.Styles.ModelingCompound,
  70. value: "modeling-compound",
  71. },
  72. { name: locales.SdPanel.Styles.NeonPunk, value: "neon-punk" },
  73. { name: locales.SdPanel.Styles.Origami, value: "origami" },
  74. { name: locales.SdPanel.Styles.Photographic, value: "photographic" },
  75. { name: locales.SdPanel.Styles.PixelArt, value: "pixel-art" },
  76. { name: locales.SdPanel.Styles.TileTexture, value: "tile-texture" },
  77. ],
  78. },
  79. {
  80. name: "Seed",
  81. value: "seed",
  82. type: "number",
  83. default: 0,
  84. min: 0,
  85. max: 4294967294,
  86. },
  87. {
  88. name: locales.SdPanel.OutFormat,
  89. value: "output_format",
  90. type: "select",
  91. default: 0,
  92. options: [
  93. { name: "PNG", value: "png" },
  94. { name: "JPEG", value: "jpeg" },
  95. { name: "WebP", value: "webp" },
  96. ],
  97. },
  98. ].filter((item) => {
  99. return !(item.support && !item.support.includes(model));
  100. });
  101. };
  102. const models = [
  103. {
  104. name: "Stable Image Ultra",
  105. value: "ultra",
  106. params: (data: any) => sdCommonParams("ultra", data),
  107. },
  108. {
  109. name: "Stable Image Core",
  110. value: "core",
  111. params: (data: any) => sdCommonParams("core", data),
  112. },
  113. {
  114. name: "Stable Diffusion 3",
  115. value: "sd3",
  116. params: (data: any) => {
  117. return sdCommonParams("sd3", data).filter((item) => {
  118. return !(
  119. data.model === "sd3-large-turbo" && item.value == "negative_prompt"
  120. );
  121. });
  122. },
  123. },
  124. ];
  125. export function ControlParamItem(props: {
  126. title: string;
  127. subTitle?: string;
  128. children?: JSX.Element | JSX.Element[];
  129. className?: string;
  130. }) {
  131. return (
  132. <div className={styles["ctrl-param-item"] + ` ${props.className || ""}`}>
  133. <div className={styles["ctrl-param-item-header"]}>
  134. <div className={styles["ctrl-param-item-title"]}>
  135. <div>{props.title}</div>
  136. </div>
  137. </div>
  138. {props.children}
  139. {props.subTitle && (
  140. <div className={styles["ctrl-param-item-sub-title"]}>
  141. {props.subTitle}
  142. </div>
  143. )}
  144. </div>
  145. );
  146. }
  147. export function ControlParam(props: {
  148. columns: any[];
  149. data: any;
  150. onChange: (field: string, val: any) => void;
  151. }) {
  152. return (
  153. <>
  154. {props.columns.map((item) => {
  155. let element: null | JSX.Element;
  156. switch (item.type) {
  157. case "textarea":
  158. element = (
  159. <ControlParamItem title={item.name} subTitle={item.sub}>
  160. <textarea
  161. rows={item.rows || 3}
  162. style={{ maxWidth: "100%", width: "100%", padding: "10px" }}
  163. placeholder={item.placeholder}
  164. onChange={(e) => {
  165. props.onChange(item.value, e.currentTarget.value);
  166. }}
  167. value={props.data[item.value]}
  168. ></textarea>
  169. </ControlParamItem>
  170. );
  171. break;
  172. case "select":
  173. element = (
  174. <ControlParamItem title={item.name} subTitle={item.sub}>
  175. <Select
  176. value={props.data[item.value]}
  177. onChange={(e) => {
  178. props.onChange(item.value, e.currentTarget.value);
  179. }}
  180. >
  181. {item.options.map((opt: any) => {
  182. return (
  183. <option value={opt.value} key={opt.value}>
  184. {opt.name}
  185. </option>
  186. );
  187. })}
  188. </Select>
  189. </ControlParamItem>
  190. );
  191. break;
  192. case "number":
  193. element = (
  194. <ControlParamItem title={item.name} subTitle={item.sub}>
  195. <input
  196. type="number"
  197. min={item.min}
  198. max={item.max}
  199. value={props.data[item.value] || 0}
  200. onChange={(e) => {
  201. props.onChange(item.value, parseInt(e.currentTarget.value));
  202. }}
  203. />
  204. </ControlParamItem>
  205. );
  206. break;
  207. default:
  208. element = (
  209. <ControlParamItem title={item.name} subTitle={item.sub}>
  210. <input
  211. type="text"
  212. value={props.data[item.value]}
  213. style={{ maxWidth: "100%", width: "100%" }}
  214. onChange={(e) => {
  215. props.onChange(item.value, e.currentTarget.value);
  216. }}
  217. />
  218. </ControlParamItem>
  219. );
  220. }
  221. return <div key={item.value}>{element}</div>;
  222. })}
  223. </>
  224. );
  225. }
  226. const getModelParamBasicData = (
  227. columns: any[],
  228. data: any,
  229. clearText?: boolean,
  230. ) => {
  231. const newParams: any = {};
  232. columns.forEach((item: any) => {
  233. if (clearText && ["text", "textarea", "number"].includes(item.type)) {
  234. newParams[item.value] = item.default || "";
  235. } else {
  236. // @ts-ignore
  237. newParams[item.value] = data[item.value] || item.default || "";
  238. }
  239. });
  240. return newParams;
  241. };
  242. export function SdPanel() {
  243. const [currentModel, setCurrentModel] = useState(models[0]);
  244. const [params, setParams] = useState(
  245. getModelParamBasicData(currentModel.params({}), {}),
  246. );
  247. const handleValueChange = (field: string, val: any) => {
  248. setParams((prevParams: any) => ({
  249. ...prevParams,
  250. [field]: val,
  251. }));
  252. };
  253. const handleModelChange = (model: any) => {
  254. setCurrentModel(model);
  255. setParams(getModelParamBasicData(model.params({}), params));
  256. };
  257. const handleSubmit = () => {
  258. const columns = currentModel.params(params);
  259. const reqData: any = {};
  260. columns.forEach((item: any) => {
  261. reqData[item.value] = params[item.value] ?? null;
  262. });
  263. console.log(JSON.stringify(reqData, null, 4));
  264. setParams(getModelParamBasicData(columns, params, true));
  265. };
  266. return (
  267. <>
  268. <ControlParamItem title={locales.SdPanel.AIModel}>
  269. <div className={styles["ai-models"]}>
  270. {models.map((item) => {
  271. return (
  272. <IconButton
  273. text={item.name}
  274. key={item.value}
  275. type={currentModel.value == item.value ? "primary" : null}
  276. shadow
  277. onClick={() => handleModelChange(item)}
  278. />
  279. );
  280. })}
  281. </div>
  282. </ControlParamItem>
  283. <ControlParam
  284. columns={currentModel.params(params) as any[]}
  285. data={params}
  286. onChange={handleValueChange}
  287. ></ControlParam>
  288. <IconButton
  289. text={locales.SdPanel.Submit}
  290. type="primary"
  291. style={{ marginTop: "20px" }}
  292. shadow
  293. onClick={handleSubmit}
  294. ></IconButton>
  295. </>
  296. );
  297. }