sd.tsx 9.9 KB

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