sd.tsx 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287
  1. import chatStyles from "@/app/components/chat.module.scss";
  2. import styles from "@/app/components/sd.module.scss";
  3. import { IconButton } from "@/app/components/button";
  4. import ReturnIcon from "@/app/icons/return.svg";
  5. import Locale from "@/app/locales";
  6. import { Path } from "@/app/constant";
  7. import React, { useEffect, useMemo, useRef, useState } from "react";
  8. import {
  9. copyToClipboard,
  10. getMessageTextContent,
  11. useMobileScreen,
  12. } from "@/app/utils";
  13. import { useNavigate } from "react-router-dom";
  14. import { useAppConfig } from "@/app/store";
  15. import MinIcon from "@/app/icons/min.svg";
  16. import MaxIcon from "@/app/icons/max.svg";
  17. import { getClientConfig } from "@/app/config/client";
  18. import { ChatAction } from "@/app/components/chat";
  19. import DeleteIcon from "@/app/icons/clear.svg";
  20. import CopyIcon from "@/app/icons/copy.svg";
  21. import PromptIcon from "@/app/icons/prompt.svg";
  22. import ResetIcon from "@/app/icons/reload.svg";
  23. import { useSdStore } from "@/app/store/sd";
  24. import locales from "@/app/locales";
  25. import LoadingIcon from "../icons/three-dots.svg";
  26. import ErrorIcon from "../icons/delete.svg";
  27. import { Property } from "csstype";
  28. import {
  29. showConfirm,
  30. showImageModal,
  31. showModal,
  32. } from "@/app/components/ui-lib";
  33. import { func } from "prop-types";
  34. import { useFileDB, IndexDBImage } from "@/app/utils/file";
  35. function getSdTaskStatus(item: any) {
  36. let s: string;
  37. let color: Property.Color | undefined = undefined;
  38. switch (item.status) {
  39. case "success":
  40. s = Locale.Sd.Status.Success;
  41. color = "green";
  42. break;
  43. case "error":
  44. s = Locale.Sd.Status.Error;
  45. color = "red";
  46. break;
  47. case "wait":
  48. s = Locale.Sd.Status.Wait;
  49. color = "yellow";
  50. break;
  51. case "running":
  52. s = Locale.Sd.Status.Running;
  53. color = "blue";
  54. break;
  55. default:
  56. s = item.status.toUpperCase();
  57. }
  58. return (
  59. <p className={styles["line-1"]} title={item.error} style={{ color: color }}>
  60. <span>
  61. {locales.Sd.Status.Name}: {s}
  62. </span>
  63. {item.status === "error" && (
  64. <span
  65. className="clickable"
  66. onClick={() => {
  67. showModal({
  68. title: locales.Sd.Detail,
  69. children: (
  70. <div style={{ color: color, userSelect: "text" }}>
  71. {item.error}
  72. </div>
  73. ),
  74. });
  75. }}
  76. >
  77. {" "}
  78. - {item.error}
  79. </span>
  80. )}
  81. </p>
  82. );
  83. }
  84. export function Sd() {
  85. const isMobileScreen = useMobileScreen();
  86. const navigate = useNavigate();
  87. const clientConfig = useMemo(() => getClientConfig(), []);
  88. const showMaxIcon = !isMobileScreen && !clientConfig?.isApp;
  89. const config = useAppConfig();
  90. const scrollRef = useRef<HTMLDivElement>(null);
  91. const fileDb = useFileDB();
  92. const sdStore = useSdStore();
  93. const [sdImages, setSdImages] = useState(sdStore.draw);
  94. useEffect(() => {
  95. setSdImages(sdStore.draw);
  96. }, [sdStore.currentId]);
  97. return (
  98. <div className={chatStyles.chat} key={"1"}>
  99. <div className="window-header" data-tauri-drag-region>
  100. {isMobileScreen && (
  101. <div className="window-actions">
  102. <div className={"window-action-button"}>
  103. <IconButton
  104. icon={<ReturnIcon />}
  105. bordered
  106. title={Locale.Chat.Actions.ChatList}
  107. onClick={() => navigate(Path.SdPanel)}
  108. />
  109. </div>
  110. </div>
  111. )}
  112. <div className={`window-header-title ${chatStyles["chat-body-title"]}`}>
  113. <div className={`window-header-main-title`}>Stability AI</div>
  114. <div className="window-header-sub-title">
  115. {Locale.Sd.SubTitle(sdImages.length || 0)}
  116. </div>
  117. </div>
  118. <div className="window-actions">
  119. {showMaxIcon && (
  120. <div className="window-action-button">
  121. <IconButton
  122. icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}
  123. bordered
  124. onClick={() => {
  125. config.update(
  126. (config) => (config.tightBorder = !config.tightBorder),
  127. );
  128. }}
  129. />
  130. </div>
  131. )}
  132. </div>
  133. </div>
  134. <div className={chatStyles["chat-body"]} ref={scrollRef}>
  135. <div className={styles["sd-img-list"]}>
  136. {sdImages.length > 0 ? (
  137. sdImages.map((item: any) => {
  138. return (
  139. <div
  140. key={item.id}
  141. style={{ display: "flex" }}
  142. className={styles["sd-img-item"]}
  143. >
  144. {item.status === "success" ? (
  145. <IndexDBImage
  146. className={styles["img"]}
  147. db={fileDb}
  148. src={item.img_data}
  149. alt={item.id}
  150. onClick={(data, e) => {
  151. showImageModal(
  152. data,
  153. true,
  154. isMobileScreen
  155. ? { width: "100%", height: "fit-content" }
  156. : { maxWidth: "100%", maxHeight: "100%" },
  157. isMobileScreen
  158. ? { width: "100%", height: "fit-content" }
  159. : { width: "100%", height: "100%" },
  160. );
  161. }}
  162. isMobileScreen={isMobileScreen}
  163. />
  164. ) : item.status === "error" ? (
  165. <div className={styles["pre-img"]}>
  166. <ErrorIcon />
  167. </div>
  168. ) : (
  169. <div className={styles["pre-img"]}>
  170. <LoadingIcon />
  171. </div>
  172. )}
  173. <div
  174. style={{ marginLeft: "10px" }}
  175. className={styles["sd-img-item-info"]}
  176. >
  177. <p className={styles["line-1"]}>
  178. {locales.SdPanel.Prompt}:{" "}
  179. <span
  180. className="clickable"
  181. title={item.params.prompt}
  182. onClick={() => {
  183. showModal({
  184. title: locales.Sd.Detail,
  185. children: (
  186. <div style={{ userSelect: "text" }}>
  187. {item.params.prompt}
  188. </div>
  189. ),
  190. });
  191. }}
  192. >
  193. {item.params.prompt}
  194. </span>
  195. </p>
  196. <p>
  197. {locales.SdPanel.AIModel}: {item.model_name}
  198. </p>
  199. {getSdTaskStatus(item)}
  200. <p>{item.created_at}</p>
  201. <div className={chatStyles["chat-message-actions"]}>
  202. <div className={chatStyles["chat-input-actions"]}>
  203. <ChatAction
  204. text={Locale.Sd.Actions.Params}
  205. icon={<PromptIcon />}
  206. onClick={() => {
  207. showModal({
  208. title: locales.Sd.GenerateParams,
  209. children: (
  210. <div style={{ userSelect: "text" }}>
  211. {Object.keys(item.params).map((key) => (
  212. <div key={key} style={{ margin: "10px" }}>
  213. <strong>{key}: </strong>
  214. {item.params[key]}
  215. </div>
  216. ))}
  217. </div>
  218. ),
  219. });
  220. }}
  221. />
  222. <ChatAction
  223. text={Locale.Sd.Actions.Copy}
  224. icon={<CopyIcon />}
  225. onClick={() =>
  226. copyToClipboard(
  227. getMessageTextContent({
  228. role: "user",
  229. content: item.params.prompt,
  230. }),
  231. )
  232. }
  233. />
  234. <ChatAction
  235. text={Locale.Sd.Actions.Retry}
  236. icon={<ResetIcon />}
  237. onClick={() => {
  238. const reqData = {
  239. model: item.model,
  240. model_name: item.model_name,
  241. status: "wait",
  242. params: { ...item.params },
  243. created_at: new Date().toLocaleString(),
  244. img_data: "",
  245. };
  246. sdStore.sendTask(reqData, fileDb);
  247. }}
  248. />
  249. <ChatAction
  250. text={Locale.Sd.Actions.Delete}
  251. icon={<DeleteIcon />}
  252. onClick={async () => {
  253. if (await showConfirm(Locale.Sd.Danger.Delete)) {
  254. // remove img_data + remove item in list
  255. fileDb.deleteRecord(item.id).then(
  256. () => {
  257. sdStore.draw = sdImages.filter(
  258. (i: any) => i.id !== item.id,
  259. );
  260. sdStore.getNextId();
  261. },
  262. (error) => {
  263. console.error(error);
  264. },
  265. );
  266. }
  267. }}
  268. />
  269. </div>
  270. </div>
  271. </div>
  272. </div>
  273. );
  274. })
  275. ) : (
  276. <div>{locales.Sd.EmptyRecord}</div>
  277. )}
  278. </div>
  279. </div>
  280. </div>
  281. );
  282. }