mirror of
				https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
				synced 2025-11-04 16:23:41 +08:00 
			
		
		
		
	feat: Improve SD list data and API integration
This commit is contained in:
		@@ -337,7 +337,7 @@ function ClearContextDivider() {
 | 
				
			|||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function ChatAction(props: {
 | 
					export function ChatAction(props: {
 | 
				
			||||||
  text: string;
 | 
					  text: string;
 | 
				
			||||||
  icon: JSX.Element;
 | 
					  icon: JSX.Element;
 | 
				
			||||||
  onClick: () => void;
 | 
					  onClick: () => void;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,5 @@
 | 
				
			|||||||
"use client";
 | 
					"use client";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { Sd } from "@/app/components/sd";
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
require("../polyfill");
 | 
					require("../polyfill");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { useState, useEffect } from "react";
 | 
					import { useState, useEffect } from "react";
 | 
				
			||||||
@@ -32,6 +30,7 @@ import { getClientConfig } from "../config/client";
 | 
				
			|||||||
import { ClientApi } from "../client/api";
 | 
					import { ClientApi } from "../client/api";
 | 
				
			||||||
import { useAccessStore } from "../store";
 | 
					import { useAccessStore } from "../store";
 | 
				
			||||||
import { identifyDefaultClaudeModel } from "../utils/checkers";
 | 
					import { identifyDefaultClaudeModel } from "../utils/checkers";
 | 
				
			||||||
 | 
					import { initDB } from "react-indexed-db-hook";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function Loading(props: { noLogo?: boolean }) {
 | 
					export function Loading(props: { noLogo?: boolean }) {
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
@@ -58,6 +57,14 @@ const MaskPage = dynamic(async () => (await import("./mask")).MaskPage, {
 | 
				
			|||||||
  loading: () => <Loading noLogo />,
 | 
					  loading: () => <Loading noLogo />,
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const Sd = dynamic(async () => (await import("./sd")).Sd, {
 | 
				
			||||||
 | 
					  loading: () => <Loading noLogo />,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SdPanel = dynamic(async () => (await import("./sd-panel")).SdPanel, {
 | 
				
			||||||
 | 
					  loading: () => <Loading noLogo />,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function useSwitchTheme() {
 | 
					export function useSwitchTheme() {
 | 
				
			||||||
  const config = useAppConfig();
 | 
					  const config = useAppConfig();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -128,7 +135,8 @@ const loadAsyncGoogleFont = () => {
 | 
				
			|||||||
function Screen() {
 | 
					function Screen() {
 | 
				
			||||||
  const config = useAppConfig();
 | 
					  const config = useAppConfig();
 | 
				
			||||||
  const location = useLocation();
 | 
					  const location = useLocation();
 | 
				
			||||||
  const isHome = location.pathname === Path.Home;
 | 
					  const isHome =
 | 
				
			||||||
 | 
					    location.pathname === Path.Home || location.pathname === Path.SdPanel;
 | 
				
			||||||
  const isAuth = location.pathname === Path.Auth;
 | 
					  const isAuth = location.pathname === Path.Auth;
 | 
				
			||||||
  const isMobileScreen = useMobileScreen();
 | 
					  const isMobileScreen = useMobileScreen();
 | 
				
			||||||
  const shouldTightBorder =
 | 
					  const shouldTightBorder =
 | 
				
			||||||
@@ -137,7 +145,6 @@ function Screen() {
 | 
				
			|||||||
  useEffect(() => {
 | 
					  useEffect(() => {
 | 
				
			||||||
    loadAsyncGoogleFont();
 | 
					    loadAsyncGoogleFont();
 | 
				
			||||||
  }, []);
 | 
					  }, []);
 | 
				
			||||||
 | 
					 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <div
 | 
					    <div
 | 
				
			||||||
      className={
 | 
					      className={
 | 
				
			||||||
@@ -154,7 +161,6 @@ function Screen() {
 | 
				
			|||||||
      ) : (
 | 
					      ) : (
 | 
				
			||||||
        <>
 | 
					        <>
 | 
				
			||||||
          <SideBar className={isHome ? styles["sidebar-show"] : ""} />
 | 
					          <SideBar className={isHome ? styles["sidebar-show"] : ""} />
 | 
				
			||||||
 | 
					 | 
				
			||||||
          <div className={styles["window-content"]} id={SlotID.AppBody}>
 | 
					          <div className={styles["window-content"]} id={SlotID.AppBody}>
 | 
				
			||||||
            <Routes>
 | 
					            <Routes>
 | 
				
			||||||
              <Route path={Path.Home} element={<Chat />} />
 | 
					              <Route path={Path.Home} element={<Chat />} />
 | 
				
			||||||
@@ -162,6 +168,7 @@ function Screen() {
 | 
				
			|||||||
              <Route path={Path.Masks} element={<MaskPage />} />
 | 
					              <Route path={Path.Masks} element={<MaskPage />} />
 | 
				
			||||||
              <Route path={Path.Chat} element={<Chat />} />
 | 
					              <Route path={Path.Chat} element={<Chat />} />
 | 
				
			||||||
              <Route path={Path.Sd} element={<Sd />} />
 | 
					              <Route path={Path.Sd} element={<Sd />} />
 | 
				
			||||||
 | 
					              <Route path={Path.SdPanel} element={<Sd />} />
 | 
				
			||||||
              <Route path={Path.Settings} element={<Settings />} />
 | 
					              <Route path={Path.Settings} element={<Settings />} />
 | 
				
			||||||
            </Routes>
 | 
					            </Routes>
 | 
				
			||||||
          </div>
 | 
					          </div>
 | 
				
			||||||
@@ -173,7 +180,6 @@ function Screen() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function useLoadData() {
 | 
					export function useLoadData() {
 | 
				
			||||||
  const config = useAppConfig();
 | 
					  const config = useAppConfig();
 | 
				
			||||||
 | 
					 | 
				
			||||||
  var api: ClientApi;
 | 
					  var api: ClientApi;
 | 
				
			||||||
  if (config.modelConfig.model.startsWith("gemini")) {
 | 
					  if (config.modelConfig.model.startsWith("gemini")) {
 | 
				
			||||||
    api = new ClientApi(ModelProvider.GeminiPro);
 | 
					    api = new ClientApi(ModelProvider.GeminiPro);
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,14 @@
 | 
				
			|||||||
import styles from "./sd-panel.module.scss";
 | 
					import styles from "./sd-panel.module.scss";
 | 
				
			||||||
import React, { useState } from "react";
 | 
					import React, { useState } from "react";
 | 
				
			||||||
import { Select } from "@/app/components/ui-lib";
 | 
					import { Select, showToast } from "@/app/components/ui-lib";
 | 
				
			||||||
import { IconButton } from "@/app/components/button";
 | 
					import { IconButton } from "@/app/components/button";
 | 
				
			||||||
import locales from "@/app/locales";
 | 
					import locales from "@/app/locales";
 | 
				
			||||||
 | 
					import { nanoid } from "nanoid";
 | 
				
			||||||
 | 
					import { useIndexedDB } from "react-indexed-db-hook";
 | 
				
			||||||
 | 
					import { StoreKey } from "@/app/constant";
 | 
				
			||||||
 | 
					import { SdDbInit, sendSdTask, useSdStore } from "@/app/store/sd";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					SdDbInit();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const sdCommonParams = (model: string, data: any) => {
 | 
					const sdCommonParams = (model: string, data: any) => {
 | 
				
			||||||
  return [
 | 
					  return [
 | 
				
			||||||
@@ -89,7 +95,7 @@ const sdCommonParams = (model: string, data: any) => {
 | 
				
			|||||||
      name: locales.SdPanel.OutFormat,
 | 
					      name: locales.SdPanel.OutFormat,
 | 
				
			||||||
      value: "output_format",
 | 
					      value: "output_format",
 | 
				
			||||||
      type: "select",
 | 
					      type: "select",
 | 
				
			||||||
      default: 0,
 | 
					      default: "png",
 | 
				
			||||||
      options: [
 | 
					      options: [
 | 
				
			||||||
        { name: "PNG", value: "png" },
 | 
					        { name: "PNG", value: "png" },
 | 
				
			||||||
        { name: "JPEG", value: "jpeg" },
 | 
					        { name: "JPEG", value: "jpeg" },
 | 
				
			||||||
@@ -128,6 +134,7 @@ const models = [
 | 
				
			|||||||
export function ControlParamItem(props: {
 | 
					export function ControlParamItem(props: {
 | 
				
			||||||
  title: string;
 | 
					  title: string;
 | 
				
			||||||
  subTitle?: string;
 | 
					  subTitle?: string;
 | 
				
			||||||
 | 
					  required?: boolean;
 | 
				
			||||||
  children?: JSX.Element | JSX.Element[];
 | 
					  children?: JSX.Element | JSX.Element[];
 | 
				
			||||||
  className?: string;
 | 
					  className?: string;
 | 
				
			||||||
}) {
 | 
					}) {
 | 
				
			||||||
@@ -135,7 +142,10 @@ export function ControlParamItem(props: {
 | 
				
			|||||||
    <div className={styles["ctrl-param-item"] + ` ${props.className || ""}`}>
 | 
					    <div className={styles["ctrl-param-item"] + ` ${props.className || ""}`}>
 | 
				
			||||||
      <div className={styles["ctrl-param-item-header"]}>
 | 
					      <div className={styles["ctrl-param-item-header"]}>
 | 
				
			||||||
        <div className={styles["ctrl-param-item-title"]}>
 | 
					        <div className={styles["ctrl-param-item-title"]}>
 | 
				
			||||||
          <div>{props.title}</div>
 | 
					          <div>
 | 
				
			||||||
 | 
					            {props.title}
 | 
				
			||||||
 | 
					            {props.required && <span style={{ color: "red" }}>*</span>}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
      </div>
 | 
					      </div>
 | 
				
			||||||
      {props.children}
 | 
					      {props.children}
 | 
				
			||||||
@@ -160,7 +170,11 @@ export function ControlParam(props: {
 | 
				
			|||||||
        switch (item.type) {
 | 
					        switch (item.type) {
 | 
				
			||||||
          case "textarea":
 | 
					          case "textarea":
 | 
				
			||||||
            element = (
 | 
					            element = (
 | 
				
			||||||
              <ControlParamItem title={item.name} subTitle={item.sub}>
 | 
					              <ControlParamItem
 | 
				
			||||||
 | 
					                title={item.name}
 | 
				
			||||||
 | 
					                subTitle={item.sub}
 | 
				
			||||||
 | 
					                required={item.required}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
                <textarea
 | 
					                <textarea
 | 
				
			||||||
                  rows={item.rows || 3}
 | 
					                  rows={item.rows || 3}
 | 
				
			||||||
                  style={{ maxWidth: "100%", width: "100%", padding: "10px" }}
 | 
					                  style={{ maxWidth: "100%", width: "100%", padding: "10px" }}
 | 
				
			||||||
@@ -175,7 +189,11 @@ export function ControlParam(props: {
 | 
				
			|||||||
            break;
 | 
					            break;
 | 
				
			||||||
          case "select":
 | 
					          case "select":
 | 
				
			||||||
            element = (
 | 
					            element = (
 | 
				
			||||||
              <ControlParamItem title={item.name} subTitle={item.sub}>
 | 
					              <ControlParamItem
 | 
				
			||||||
 | 
					                title={item.name}
 | 
				
			||||||
 | 
					                subTitle={item.sub}
 | 
				
			||||||
 | 
					                required={item.required}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
                <Select
 | 
					                <Select
 | 
				
			||||||
                  value={props.data[item.value]}
 | 
					                  value={props.data[item.value]}
 | 
				
			||||||
                  onChange={(e) => {
 | 
					                  onChange={(e) => {
 | 
				
			||||||
@@ -195,7 +213,11 @@ export function ControlParam(props: {
 | 
				
			|||||||
            break;
 | 
					            break;
 | 
				
			||||||
          case "number":
 | 
					          case "number":
 | 
				
			||||||
            element = (
 | 
					            element = (
 | 
				
			||||||
              <ControlParamItem title={item.name} subTitle={item.sub}>
 | 
					              <ControlParamItem
 | 
				
			||||||
 | 
					                title={item.name}
 | 
				
			||||||
 | 
					                subTitle={item.sub}
 | 
				
			||||||
 | 
					                required={item.required}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
                <input
 | 
					                <input
 | 
				
			||||||
                  type="number"
 | 
					                  type="number"
 | 
				
			||||||
                  min={item.min}
 | 
					                  min={item.min}
 | 
				
			||||||
@@ -210,7 +232,11 @@ export function ControlParam(props: {
 | 
				
			|||||||
            break;
 | 
					            break;
 | 
				
			||||||
          default:
 | 
					          default:
 | 
				
			||||||
            element = (
 | 
					            element = (
 | 
				
			||||||
              <ControlParamItem title={item.name} subTitle={item.sub}>
 | 
					              <ControlParamItem
 | 
				
			||||||
 | 
					                title={item.name}
 | 
				
			||||||
 | 
					                subTitle={item.sub}
 | 
				
			||||||
 | 
					                required={item.required}
 | 
				
			||||||
 | 
					              >
 | 
				
			||||||
                <input
 | 
					                <input
 | 
				
			||||||
                  type="text"
 | 
					                  type="text"
 | 
				
			||||||
                  value={props.data[item.value]}
 | 
					                  value={props.data[item.value]}
 | 
				
			||||||
@@ -260,14 +286,43 @@ export function SdPanel() {
 | 
				
			|||||||
    setCurrentModel(model);
 | 
					    setCurrentModel(model);
 | 
				
			||||||
    setParams(getModelParamBasicData(model.params({}), params));
 | 
					    setParams(getModelParamBasicData(model.params({}), params));
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
 | 
					  const sdListDb = useIndexedDB(StoreKey.SdList);
 | 
				
			||||||
 | 
					  const { execCountInc } = useSdStore();
 | 
				
			||||||
  const handleSubmit = () => {
 | 
					  const handleSubmit = () => {
 | 
				
			||||||
    const columns = currentModel.params(params);
 | 
					    const columns = currentModel.params(params);
 | 
				
			||||||
    const reqData: any = {};
 | 
					    const reqParams: any = {};
 | 
				
			||||||
    columns.forEach((item: any) => {
 | 
					    for (let i = 0; i < columns.length; i++) {
 | 
				
			||||||
      reqData[item.value] = params[item.value] ?? null;
 | 
					      const item = columns[i];
 | 
				
			||||||
    });
 | 
					      reqParams[item.value] = params[item.value] ?? null;
 | 
				
			||||||
    console.log(JSON.stringify(reqData, null, 4));
 | 
					      if (item.required) {
 | 
				
			||||||
    setParams(getModelParamBasicData(columns, params, true));
 | 
					        if (!reqParams[item.value]) {
 | 
				
			||||||
 | 
					          showToast(locales.SdPanel.ParamIsRequired(item.name));
 | 
				
			||||||
 | 
					          return;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    // console.log(JSON.stringify(reqParams, null, 4));
 | 
				
			||||||
 | 
					    let data: any = {
 | 
				
			||||||
 | 
					      model: currentModel.value,
 | 
				
			||||||
 | 
					      model_name: currentModel.name,
 | 
				
			||||||
 | 
					      status: "wait",
 | 
				
			||||||
 | 
					      params: reqParams,
 | 
				
			||||||
 | 
					      created_at: new Date().toISOString(),
 | 
				
			||||||
 | 
					      img_data: "",
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					    sdListDb.add(data).then(
 | 
				
			||||||
 | 
					      (id) => {
 | 
				
			||||||
 | 
					        data = { ...data, id, status: "running" };
 | 
				
			||||||
 | 
					        sdListDb.update(data);
 | 
				
			||||||
 | 
					        execCountInc();
 | 
				
			||||||
 | 
					        sendSdTask(data, sdListDb, execCountInc);
 | 
				
			||||||
 | 
					        setParams(getModelParamBasicData(columns, params, true));
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      (error) => {
 | 
				
			||||||
 | 
					        console.error(error);
 | 
				
			||||||
 | 
					        showToast(`error: ` + error.message);
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <>
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,53 @@
 | 
				
			|||||||
 | 
					.sd-img-list{
 | 
				
			||||||
 | 
					  display: flex;
 | 
				
			||||||
 | 
					  flex-wrap: wrap;
 | 
				
			||||||
 | 
					  justify-content: space-between;
 | 
				
			||||||
 | 
					  .sd-img-item{
 | 
				
			||||||
 | 
					    width: 48%;
 | 
				
			||||||
 | 
					    .sd-img-item-info{
 | 
				
			||||||
 | 
					      flex:1;
 | 
				
			||||||
 | 
					      width: 100%;
 | 
				
			||||||
 | 
					      overflow: hidden;
 | 
				
			||||||
 | 
					      user-select: text;
 | 
				
			||||||
 | 
					      p{
 | 
				
			||||||
 | 
					        margin: 6px;
 | 
				
			||||||
 | 
					        font-size: 12px;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      .line-1{
 | 
				
			||||||
 | 
					        overflow: hidden;
 | 
				
			||||||
 | 
					        white-space: nowrap;
 | 
				
			||||||
 | 
					        text-overflow: ellipsis;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .pre-img{
 | 
				
			||||||
 | 
					      display: flex;
 | 
				
			||||||
 | 
					      width: 130px;
 | 
				
			||||||
 | 
					      justify-content: center;
 | 
				
			||||||
 | 
					      align-items: center;
 | 
				
			||||||
 | 
					      background-color: var(--second);
 | 
				
			||||||
 | 
					      border-radius: 10px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    .img{
 | 
				
			||||||
 | 
					      width: 130px;
 | 
				
			||||||
 | 
					      height: 130px;
 | 
				
			||||||
 | 
					      border-radius: 10px;
 | 
				
			||||||
 | 
					      overflow: hidden;
 | 
				
			||||||
 | 
					      cursor: pointer;
 | 
				
			||||||
 | 
					      transition: all .3s;
 | 
				
			||||||
 | 
					      &:hover{
 | 
				
			||||||
 | 
					        opacity: .7;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    &:not(:last-child){
 | 
				
			||||||
 | 
					      margin-bottom: 20px;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@media only screen and (max-width: 600px) {
 | 
				
			||||||
 | 
					  .sd-img-list{
 | 
				
			||||||
 | 
					    .sd-img-item{
 | 
				
			||||||
 | 
					      width: 100%;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,3 +1,232 @@
 | 
				
			|||||||
export function Sd() {
 | 
					import chatStyles from "@/app/components/chat.module.scss";
 | 
				
			||||||
  return <div>sd</div>;
 | 
					import styles from "@/app/components/sd.module.scss";
 | 
				
			||||||
 | 
					import { IconButton } from "@/app/components/button";
 | 
				
			||||||
 | 
					import ReturnIcon from "@/app/icons/return.svg";
 | 
				
			||||||
 | 
					import Locale from "@/app/locales";
 | 
				
			||||||
 | 
					import { Path, StoreKey } from "@/app/constant";
 | 
				
			||||||
 | 
					import React, { useEffect, useMemo, useRef, useState } from "react";
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  copyToClipboard,
 | 
				
			||||||
 | 
					  getMessageTextContent,
 | 
				
			||||||
 | 
					  useMobileScreen,
 | 
				
			||||||
 | 
					  useWindowSize,
 | 
				
			||||||
 | 
					} from "@/app/utils";
 | 
				
			||||||
 | 
					import { useNavigate } from "react-router-dom";
 | 
				
			||||||
 | 
					import { useAppConfig } from "@/app/store";
 | 
				
			||||||
 | 
					import MinIcon from "@/app/icons/min.svg";
 | 
				
			||||||
 | 
					import MaxIcon from "@/app/icons/max.svg";
 | 
				
			||||||
 | 
					import { getClientConfig } from "@/app/config/client";
 | 
				
			||||||
 | 
					import { ChatAction } from "@/app/components/chat";
 | 
				
			||||||
 | 
					import DeleteIcon from "@/app/icons/clear.svg";
 | 
				
			||||||
 | 
					import CopyIcon from "@/app/icons/copy.svg";
 | 
				
			||||||
 | 
					import PromptIcon from "@/app/icons/prompt.svg";
 | 
				
			||||||
 | 
					import ResetIcon from "@/app/icons/reload.svg";
 | 
				
			||||||
 | 
					import { useIndexedDB } from "react-indexed-db-hook";
 | 
				
			||||||
 | 
					import { useSdStore } from "@/app/store/sd";
 | 
				
			||||||
 | 
					import locales from "@/app/locales";
 | 
				
			||||||
 | 
					import LoadingIcon from "../icons/three-dots.svg";
 | 
				
			||||||
 | 
					import ErrorIcon from "../icons/delete.svg";
 | 
				
			||||||
 | 
					import { Property } from "csstype";
 | 
				
			||||||
 | 
					import { showConfirm } from "@/app/components/ui-lib";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function openBase64ImgUrl(base64Data: string, contentType: string) {
 | 
				
			||||||
 | 
					  const byteCharacters = atob(base64Data);
 | 
				
			||||||
 | 
					  const byteNumbers = new Array(byteCharacters.length);
 | 
				
			||||||
 | 
					  for (let i = 0; i < byteCharacters.length; i++) {
 | 
				
			||||||
 | 
					    byteNumbers[i] = byteCharacters.charCodeAt(i);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  const byteArray = new Uint8Array(byteNumbers);
 | 
				
			||||||
 | 
					  const blob = new Blob([byteArray], { type: contentType });
 | 
				
			||||||
 | 
					  const blobUrl = URL.createObjectURL(blob);
 | 
				
			||||||
 | 
					  window.open(blobUrl);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getSdTaskStatus(item: any) {
 | 
				
			||||||
 | 
					  let s: string;
 | 
				
			||||||
 | 
					  let color: Property.Color | undefined = undefined;
 | 
				
			||||||
 | 
					  switch (item.status) {
 | 
				
			||||||
 | 
					    case "success":
 | 
				
			||||||
 | 
					      s = Locale.Sd.Status.Success;
 | 
				
			||||||
 | 
					      color = "green";
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case "error":
 | 
				
			||||||
 | 
					      s = Locale.Sd.Status.Error;
 | 
				
			||||||
 | 
					      color = "red";
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case "wait":
 | 
				
			||||||
 | 
					      s = Locale.Sd.Status.Wait;
 | 
				
			||||||
 | 
					      color = "yellow";
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    case "running":
 | 
				
			||||||
 | 
					      s = Locale.Sd.Status.Running;
 | 
				
			||||||
 | 
					      color = "blue";
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      s = item.status.toUpperCase();
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <p className={styles["line-1"]} title={item.error} style={{ color: color }}>
 | 
				
			||||||
 | 
					      <span>
 | 
				
			||||||
 | 
					        {locales.Sd.Status.Name}: {s}
 | 
				
			||||||
 | 
					      </span>
 | 
				
			||||||
 | 
					      {item.status === "error" && <span> - {item.error}</span>}
 | 
				
			||||||
 | 
					    </p>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function Sd() {
 | 
				
			||||||
 | 
					  const isMobileScreen = useMobileScreen();
 | 
				
			||||||
 | 
					  const navigate = useNavigate();
 | 
				
			||||||
 | 
					  const clientConfig = useMemo(() => getClientConfig(), []);
 | 
				
			||||||
 | 
					  const showMaxIcon = !isMobileScreen && !clientConfig?.isApp;
 | 
				
			||||||
 | 
					  const config = useAppConfig();
 | 
				
			||||||
 | 
					  const scrollRef = useRef<HTMLDivElement>(null);
 | 
				
			||||||
 | 
					  const sdListDb = useIndexedDB(StoreKey.SdList);
 | 
				
			||||||
 | 
					  const [sdImages, setSdImages] = useState([]);
 | 
				
			||||||
 | 
					  const { execCount } = useSdStore();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  useEffect(() => {
 | 
				
			||||||
 | 
					    sdListDb.getAll().then((data) => {
 | 
				
			||||||
 | 
					      setSdImages(((data as never[]) || []).reverse());
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }, [execCount]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <div className={chatStyles.chat} key={"1"}>
 | 
				
			||||||
 | 
					      <div className="window-header" data-tauri-drag-region>
 | 
				
			||||||
 | 
					        {isMobileScreen && (
 | 
				
			||||||
 | 
					          <div className="window-actions">
 | 
				
			||||||
 | 
					            <div className={"window-action-button"}>
 | 
				
			||||||
 | 
					              <IconButton
 | 
				
			||||||
 | 
					                icon={<ReturnIcon />}
 | 
				
			||||||
 | 
					                bordered
 | 
				
			||||||
 | 
					                title={Locale.Chat.Actions.ChatList}
 | 
				
			||||||
 | 
					                onClick={() => navigate(Path.SdPanel)}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        )}
 | 
				
			||||||
 | 
					        <div className={`window-header-title ${chatStyles["chat-body-title"]}`}>
 | 
				
			||||||
 | 
					          <div className={`window-header-main-title`}>Stability AI</div>
 | 
				
			||||||
 | 
					          <div className="window-header-sub-title">
 | 
				
			||||||
 | 
					            {Locale.Sd.SubTitle(sdImages.length || 0)}
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div className="window-actions">
 | 
				
			||||||
 | 
					          {showMaxIcon && (
 | 
				
			||||||
 | 
					            <div className="window-action-button">
 | 
				
			||||||
 | 
					              <IconButton
 | 
				
			||||||
 | 
					                icon={config.tightBorder ? <MinIcon /> : <MaxIcon />}
 | 
				
			||||||
 | 
					                bordered
 | 
				
			||||||
 | 
					                onClick={() => {
 | 
				
			||||||
 | 
					                  config.update(
 | 
				
			||||||
 | 
					                    (config) => (config.tightBorder = !config.tightBorder),
 | 
				
			||||||
 | 
					                  );
 | 
				
			||||||
 | 
					                }}
 | 
				
			||||||
 | 
					              />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					      <div className={chatStyles["chat-body"]} ref={scrollRef}>
 | 
				
			||||||
 | 
					        <div className={styles["sd-img-list"]}>
 | 
				
			||||||
 | 
					          {sdImages.length > 0 ? (
 | 
				
			||||||
 | 
					            sdImages.map((item: any) => {
 | 
				
			||||||
 | 
					              return (
 | 
				
			||||||
 | 
					                <div
 | 
				
			||||||
 | 
					                  key={item.id}
 | 
				
			||||||
 | 
					                  style={{ display: "flex" }}
 | 
				
			||||||
 | 
					                  className={styles["sd-img-item"]}
 | 
				
			||||||
 | 
					                >
 | 
				
			||||||
 | 
					                  {item.status === "success" ? (
 | 
				
			||||||
 | 
					                    <img
 | 
				
			||||||
 | 
					                      className={styles["img"]}
 | 
				
			||||||
 | 
					                      src={`data:image/png;base64,${item.img_data}`}
 | 
				
			||||||
 | 
					                      alt={`${item.id}`}
 | 
				
			||||||
 | 
					                      onClick={(e) => {
 | 
				
			||||||
 | 
					                        openBase64ImgUrl(item.img_data, "image/png");
 | 
				
			||||||
 | 
					                      }}
 | 
				
			||||||
 | 
					                    />
 | 
				
			||||||
 | 
					                  ) : item.status === "error" ? (
 | 
				
			||||||
 | 
					                    <div className={styles["pre-img"]}>
 | 
				
			||||||
 | 
					                      <ErrorIcon />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                  ) : (
 | 
				
			||||||
 | 
					                    <div className={styles["pre-img"]}>
 | 
				
			||||||
 | 
					                      <LoadingIcon />
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                  )}
 | 
				
			||||||
 | 
					                  <div
 | 
				
			||||||
 | 
					                    style={{ marginLeft: "10px" }}
 | 
				
			||||||
 | 
					                    className={styles["sd-img-item-info"]}
 | 
				
			||||||
 | 
					                  >
 | 
				
			||||||
 | 
					                    <p className={styles["line-1"]}>
 | 
				
			||||||
 | 
					                      {locales.SdPanel.Prompt}:{" "}
 | 
				
			||||||
 | 
					                      <span title={item.params.prompt}>
 | 
				
			||||||
 | 
					                        {item.params.prompt}
 | 
				
			||||||
 | 
					                      </span>
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
 | 
					                    <p>
 | 
				
			||||||
 | 
					                      {locales.SdPanel.AIModel}: {item.model_name}
 | 
				
			||||||
 | 
					                    </p>
 | 
				
			||||||
 | 
					                    {getSdTaskStatus(item)}
 | 
				
			||||||
 | 
					                    <p>{item.created_at}</p>
 | 
				
			||||||
 | 
					                    <div className={chatStyles["chat-message-actions"]}>
 | 
				
			||||||
 | 
					                      <div className={chatStyles["chat-input-actions"]}>
 | 
				
			||||||
 | 
					                        <ChatAction
 | 
				
			||||||
 | 
					                          text={Locale.Sd.Actions.Params}
 | 
				
			||||||
 | 
					                          icon={<PromptIcon />}
 | 
				
			||||||
 | 
					                          onClick={() => console.log(1)}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                        <ChatAction
 | 
				
			||||||
 | 
					                          text={Locale.Sd.Actions.Copy}
 | 
				
			||||||
 | 
					                          icon={<CopyIcon />}
 | 
				
			||||||
 | 
					                          onClick={() =>
 | 
				
			||||||
 | 
					                            copyToClipboard(
 | 
				
			||||||
 | 
					                              getMessageTextContent({
 | 
				
			||||||
 | 
					                                role: "user",
 | 
				
			||||||
 | 
					                                content: item.params.prompt,
 | 
				
			||||||
 | 
					                              }),
 | 
				
			||||||
 | 
					                            )
 | 
				
			||||||
 | 
					                          }
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                        <ChatAction
 | 
				
			||||||
 | 
					                          text={Locale.Sd.Actions.Retry}
 | 
				
			||||||
 | 
					                          icon={<ResetIcon />}
 | 
				
			||||||
 | 
					                          onClick={() => console.log(1)}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                        <ChatAction
 | 
				
			||||||
 | 
					                          text={Locale.Sd.Actions.Delete}
 | 
				
			||||||
 | 
					                          icon={<DeleteIcon />}
 | 
				
			||||||
 | 
					                          onClick={async () => {
 | 
				
			||||||
 | 
					                            if (await showConfirm(Locale.Sd.Danger.Delete)) {
 | 
				
			||||||
 | 
					                              sdListDb.deleteRecord(item.id).then(
 | 
				
			||||||
 | 
					                                () => {
 | 
				
			||||||
 | 
					                                  setSdImages(
 | 
				
			||||||
 | 
					                                    sdImages.filter(
 | 
				
			||||||
 | 
					                                      (i: any) => i.id !== item.id,
 | 
				
			||||||
 | 
					                                    ),
 | 
				
			||||||
 | 
					                                  );
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                                (error) => {
 | 
				
			||||||
 | 
					                                  console.error(error);
 | 
				
			||||||
 | 
					                                },
 | 
				
			||||||
 | 
					                              );
 | 
				
			||||||
 | 
					                            }
 | 
				
			||||||
 | 
					                          }}
 | 
				
			||||||
 | 
					                        />
 | 
				
			||||||
 | 
					                      </div>
 | 
				
			||||||
 | 
					                    </div>
 | 
				
			||||||
 | 
					                  </div>
 | 
				
			||||||
 | 
					                </div>
 | 
				
			||||||
 | 
					              );
 | 
				
			||||||
 | 
					            })
 | 
				
			||||||
 | 
					          ) : (
 | 
				
			||||||
 | 
					            <div>{locales.Sd.EmptyRecord}</div>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					      </div>
 | 
				
			||||||
 | 
					    </div>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -155,6 +155,7 @@ export function SideBar(props: { className?: string }) {
 | 
				
			|||||||
  let isChat: boolean = false;
 | 
					  let isChat: boolean = false;
 | 
				
			||||||
  switch (location.pathname) {
 | 
					  switch (location.pathname) {
 | 
				
			||||||
    case Path.Sd:
 | 
					    case Path.Sd:
 | 
				
			||||||
 | 
					    case Path.SdPanel:
 | 
				
			||||||
      bodyComponent = <SdPanel />;
 | 
					      bodyComponent = <SdPanel />;
 | 
				
			||||||
      break;
 | 
					      break;
 | 
				
			||||||
    default:
 | 
					    default:
 | 
				
			||||||
@@ -220,16 +221,18 @@ export function SideBar(props: { className?: string }) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      <div className={styles["sidebar-tail"]}>
 | 
					      <div className={styles["sidebar-tail"]}>
 | 
				
			||||||
        <div className={styles["sidebar-actions"]}>
 | 
					        <div className={styles["sidebar-actions"]}>
 | 
				
			||||||
          <div className={styles["sidebar-action"] + " " + styles.mobile}>
 | 
					          {isChat && (
 | 
				
			||||||
            <IconButton
 | 
					            <div className={styles["sidebar-action"] + " " + styles.mobile}>
 | 
				
			||||||
              icon={<DeleteIcon />}
 | 
					              <IconButton
 | 
				
			||||||
              onClick={async () => {
 | 
					                icon={<DeleteIcon />}
 | 
				
			||||||
                if (await showConfirm(Locale.Home.DeleteChat)) {
 | 
					                onClick={async () => {
 | 
				
			||||||
                  chatStore.deleteSession(chatStore.currentSessionIndex);
 | 
					                  if (await showConfirm(Locale.Home.DeleteChat)) {
 | 
				
			||||||
                }
 | 
					                    chatStore.deleteSession(chatStore.currentSessionIndex);
 | 
				
			||||||
              }}
 | 
					                  }
 | 
				
			||||||
            />
 | 
					                }}
 | 
				
			||||||
          </div>
 | 
					              />
 | 
				
			||||||
 | 
					            </div>
 | 
				
			||||||
 | 
					          )}
 | 
				
			||||||
          <div className={styles["sidebar-action"]}>
 | 
					          <div className={styles["sidebar-action"]}>
 | 
				
			||||||
            <Link to={Path.Settings}>
 | 
					            <Link to={Path.Settings}>
 | 
				
			||||||
              <IconButton icon={<SettingsIcon />} shadow />
 | 
					              <IconButton icon={<SettingsIcon />} shadow />
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,7 +3,7 @@ import { BuildConfig, getBuildConfig } from "./build";
 | 
				
			|||||||
export function getClientConfig() {
 | 
					export function getClientConfig() {
 | 
				
			||||||
  if (typeof document !== "undefined") {
 | 
					  if (typeof document !== "undefined") {
 | 
				
			||||||
    // client side
 | 
					    // client side
 | 
				
			||||||
    return JSON.parse(queryMeta("config")) as BuildConfig;
 | 
					    return JSON.parse(queryMeta("config") || "{}") as BuildConfig;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (typeof process !== "undefined") {
 | 
					  if (typeof process !== "undefined") {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -22,6 +22,7 @@ export enum Path {
 | 
				
			|||||||
  Masks = "/masks",
 | 
					  Masks = "/masks",
 | 
				
			||||||
  Auth = "/auth",
 | 
					  Auth = "/auth",
 | 
				
			||||||
  Sd = "/sd",
 | 
					  Sd = "/sd",
 | 
				
			||||||
 | 
					  SdPanel = "/sd-panel",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export enum ApiPath {
 | 
					export enum ApiPath {
 | 
				
			||||||
@@ -48,6 +49,7 @@ export enum StoreKey {
 | 
				
			|||||||
  Prompt = "prompt-store",
 | 
					  Prompt = "prompt-store",
 | 
				
			||||||
  Update = "chat-update",
 | 
					  Update = "chat-update",
 | 
				
			||||||
  Sync = "sync",
 | 
					  Sync = "sync",
 | 
				
			||||||
 | 
					  SdList = "sd-list",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const DEFAULT_SIDEBAR_WIDTH = 300;
 | 
					export const DEFAULT_SIDEBAR_WIDTH = 300;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -494,6 +494,7 @@ const cn = {
 | 
				
			|||||||
    AIModel: "AI模型",
 | 
					    AIModel: "AI模型",
 | 
				
			||||||
    ModelVersion: "模型版本",
 | 
					    ModelVersion: "模型版本",
 | 
				
			||||||
    Submit: "提交生成",
 | 
					    Submit: "提交生成",
 | 
				
			||||||
 | 
					    ParamIsRequired: (name: string) => `${name}不能为空`,
 | 
				
			||||||
    Styles: {
 | 
					    Styles: {
 | 
				
			||||||
      D3Model: "3D模型",
 | 
					      D3Model: "3D模型",
 | 
				
			||||||
      AnalogFilm: "模拟电影",
 | 
					      AnalogFilm: "模拟电影",
 | 
				
			||||||
@@ -514,6 +515,26 @@ const cn = {
 | 
				
			|||||||
      TileTexture: "贴图",
 | 
					      TileTexture: "贴图",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  Sd: {
 | 
				
			||||||
 | 
					    SubTitle: (count: number) => `共 ${count} 条绘画`,
 | 
				
			||||||
 | 
					    Actions: {
 | 
				
			||||||
 | 
					      Params: "查看参数",
 | 
				
			||||||
 | 
					      Copy: "复制提示词",
 | 
				
			||||||
 | 
					      Delete: "删除",
 | 
				
			||||||
 | 
					      Retry: "重试",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    EmptyRecord: "暂无绘画记录",
 | 
				
			||||||
 | 
					    Status: {
 | 
				
			||||||
 | 
					      Name: "状态",
 | 
				
			||||||
 | 
					      Success: "成功",
 | 
				
			||||||
 | 
					      Error: "失败",
 | 
				
			||||||
 | 
					      Wait: "等待中",
 | 
				
			||||||
 | 
					      Running: "运行中",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Danger: {
 | 
				
			||||||
 | 
					      Delete: "确认删除?",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type DeepPartial<T> = T extends object
 | 
					type DeepPartial<T> = T extends object
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -500,6 +500,7 @@ const en: LocaleType = {
 | 
				
			|||||||
    AIModel: "AI Model",
 | 
					    AIModel: "AI Model",
 | 
				
			||||||
    ModelVersion: "Model Version",
 | 
					    ModelVersion: "Model Version",
 | 
				
			||||||
    Submit: "Submit",
 | 
					    Submit: "Submit",
 | 
				
			||||||
 | 
					    ParamIsRequired: (name: string) => `${name} is required`,
 | 
				
			||||||
    Styles: {
 | 
					    Styles: {
 | 
				
			||||||
      D3Model: "3d-model",
 | 
					      D3Model: "3d-model",
 | 
				
			||||||
      AnalogFilm: "analog-film",
 | 
					      AnalogFilm: "analog-film",
 | 
				
			||||||
@@ -520,6 +521,26 @@ const en: LocaleType = {
 | 
				
			|||||||
      TileTexture: "tile-texture",
 | 
					      TileTexture: "tile-texture",
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 | 
					  Sd: {
 | 
				
			||||||
 | 
					    SubTitle: (count: number) => `${count} images`,
 | 
				
			||||||
 | 
					    Actions: {
 | 
				
			||||||
 | 
					      Params: "See Params",
 | 
				
			||||||
 | 
					      Copy: "Copy Prompt",
 | 
				
			||||||
 | 
					      Delete: "Delete",
 | 
				
			||||||
 | 
					      Retry: "Retry",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    EmptyRecord: "No images yet",
 | 
				
			||||||
 | 
					    Status: {
 | 
				
			||||||
 | 
					      Name: "Status",
 | 
				
			||||||
 | 
					      Success: "Success",
 | 
				
			||||||
 | 
					      Error: "Error",
 | 
				
			||||||
 | 
					      Wait: "Waiting",
 | 
				
			||||||
 | 
					      Running: "Running",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    Danger: {
 | 
				
			||||||
 | 
					      Delete: "Confirm to delete?",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export default en;
 | 
					export default en;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ import { Analytics } from "@vercel/analytics/react";
 | 
				
			|||||||
import { Home } from "./components/home";
 | 
					import { Home } from "./components/home";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import { getServerSideConfig } from "./config/server";
 | 
					import { getServerSideConfig } from "./config/server";
 | 
				
			||||||
 | 
					import { SdDbInit } from "@/app/store/sd";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const serverConfig = getServerSideConfig();
 | 
					const serverConfig = getServerSideConfig();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										78
									
								
								app/store/sd.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								app/store/sd.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
				
			|||||||
 | 
					import { initDB, useIndexedDB } from "react-indexed-db-hook";
 | 
				
			||||||
 | 
					import { StoreKey } from "@/app/constant";
 | 
				
			||||||
 | 
					import { create, StoreApi } from "zustand";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const SdDbConfig = {
 | 
				
			||||||
 | 
					  name: "@chatgpt-next-web/sd",
 | 
				
			||||||
 | 
					  version: 1,
 | 
				
			||||||
 | 
					  objectStoresMeta: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      store: StoreKey.SdList,
 | 
				
			||||||
 | 
					      storeConfig: { keyPath: "id", autoIncrement: true },
 | 
				
			||||||
 | 
					      storeSchema: [
 | 
				
			||||||
 | 
					        { name: "model", keypath: "model", options: { unique: false } },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          name: "model_name",
 | 
				
			||||||
 | 
					          keypath: "model_name",
 | 
				
			||||||
 | 
					          options: { unique: false },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        { name: "status", keypath: "status", options: { unique: false } },
 | 
				
			||||||
 | 
					        { name: "params", keypath: "params", options: { unique: false } },
 | 
				
			||||||
 | 
					        { name: "img_data", keypath: "img_data", options: { unique: false } },
 | 
				
			||||||
 | 
					        { name: "error", keypath: "error", options: { unique: false } },
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          name: "created_at",
 | 
				
			||||||
 | 
					          keypath: "created_at",
 | 
				
			||||||
 | 
					          options: { unique: false },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function SdDbInit() {
 | 
				
			||||||
 | 
					  initDB(SdDbConfig);
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type SdStore = {
 | 
				
			||||||
 | 
					  execCount: number;
 | 
				
			||||||
 | 
					  execCountInc: () => void;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const useSdStore = create<SdStore>()((set) => ({
 | 
				
			||||||
 | 
					  execCount: 1,
 | 
				
			||||||
 | 
					  execCountInc: () => set((state) => ({ execCount: state.execCount + 1 })),
 | 
				
			||||||
 | 
					}));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function sendSdTask(data: any, db: any, inc: any) {
 | 
				
			||||||
 | 
					  const formData = new FormData();
 | 
				
			||||||
 | 
					  for (let paramsKey in data.params) {
 | 
				
			||||||
 | 
					    formData.append(paramsKey, data.params[paramsKey]);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  fetch("https://api.stability.ai/v2beta/stable-image/generate/" + data.model, {
 | 
				
			||||||
 | 
					    method: "POST",
 | 
				
			||||||
 | 
					    headers: {
 | 
				
			||||||
 | 
					      Accept: "application/json",
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    body: formData,
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					    .then((response) => response.json())
 | 
				
			||||||
 | 
					    .then((resData) => {
 | 
				
			||||||
 | 
					      if (resData.errors && resData.errors.length > 0) {
 | 
				
			||||||
 | 
					        db.update({ ...data, status: "error", error: resData.errors[0] });
 | 
				
			||||||
 | 
					        inc();
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      if (resData.finish_reason === "SUCCESS") {
 | 
				
			||||||
 | 
					        db.update({ ...data, status: "success", img_data: resData.image });
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        db.update({ ...data, status: "error", error: JSON.stringify(resData) });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      inc();
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch((error) => {
 | 
				
			||||||
 | 
					      db.update({ ...data, status: "error", error: error.message });
 | 
				
			||||||
 | 
					      console.error("Error:", error);
 | 
				
			||||||
 | 
					      inc();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -32,6 +32,7 @@
 | 
				
			|||||||
    "node-fetch": "^3.3.1",
 | 
					    "node-fetch": "^3.3.1",
 | 
				
			||||||
    "react": "^18.2.0",
 | 
					    "react": "^18.2.0",
 | 
				
			||||||
    "react-dom": "^18.2.0",
 | 
					    "react-dom": "^18.2.0",
 | 
				
			||||||
 | 
					    "react-indexed-db-hook": "^1.0.14",
 | 
				
			||||||
    "react-markdown": "^8.0.7",
 | 
					    "react-markdown": "^8.0.7",
 | 
				
			||||||
    "react-router-dom": "^6.15.0",
 | 
					    "react-router-dom": "^6.15.0",
 | 
				
			||||||
    "rehype-highlight": "^6.0.0",
 | 
					    "rehype-highlight": "^6.0.0",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5110,6 +5110,11 @@ react-dom@^18.2.0:
 | 
				
			|||||||
    loose-envify "^1.1.0"
 | 
					    loose-envify "^1.1.0"
 | 
				
			||||||
    scheduler "^0.23.0"
 | 
					    scheduler "^0.23.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					react-indexed-db-hook@^1.0.14:
 | 
				
			||||||
 | 
					  version "1.0.14"
 | 
				
			||||||
 | 
					  resolved "https://registry.npmmirror.com/react-indexed-db-hook/-/react-indexed-db-hook-1.0.14.tgz#a29cd732d592735b6a68dfc94316b7a4a091e6be"
 | 
				
			||||||
 | 
					  integrity sha512-tQ6rWofgXUCBhZp9pRpWzthzPbjqcll5uXMo07lbQTKl47VyL9nw9wfVswRxxzS5yj5Sq/VHUkNUjamWbA/M/w==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
react-is@^16.13.1, react-is@^16.7.0:
 | 
					react-is@^16.13.1, react-is@^16.7.0:
 | 
				
			||||||
  version "16.13.1"
 | 
					  version "16.13.1"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
 | 
					  resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user