feat: Improve SD list data and API integration
This commit is contained in:
parent
54401162bd
commit
a16725ac17
|
@ -337,7 +337,7 @@ function ClearContextDivider() {
|
|||
);
|
||||
}
|
||||
|
||||
function ChatAction(props: {
|
||||
export function ChatAction(props: {
|
||||
text: string;
|
||||
icon: JSX.Element;
|
||||
onClick: () => void;
|
||||
|
|
|
@ -1,7 +1,5 @@
|
|||
"use client";
|
||||
|
||||
import { Sd } from "@/app/components/sd";
|
||||
|
||||
require("../polyfill");
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
|
@ -32,6 +30,7 @@ import { getClientConfig } from "../config/client";
|
|||
import { ClientApi } from "../client/api";
|
||||
import { useAccessStore } from "../store";
|
||||
import { identifyDefaultClaudeModel } from "../utils/checkers";
|
||||
import { initDB } from "react-indexed-db-hook";
|
||||
|
||||
export function Loading(props: { noLogo?: boolean }) {
|
||||
return (
|
||||
|
@ -58,6 +57,14 @@ const MaskPage = dynamic(async () => (await import("./mask")).MaskPage, {
|
|||
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() {
|
||||
const config = useAppConfig();
|
||||
|
||||
|
@ -128,7 +135,8 @@ const loadAsyncGoogleFont = () => {
|
|||
function Screen() {
|
||||
const config = useAppConfig();
|
||||
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 isMobileScreen = useMobileScreen();
|
||||
const shouldTightBorder =
|
||||
|
@ -137,7 +145,6 @@ function Screen() {
|
|||
useEffect(() => {
|
||||
loadAsyncGoogleFont();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div
|
||||
className={
|
||||
|
@ -154,7 +161,6 @@ function Screen() {
|
|||
) : (
|
||||
<>
|
||||
<SideBar className={isHome ? styles["sidebar-show"] : ""} />
|
||||
|
||||
<div className={styles["window-content"]} id={SlotID.AppBody}>
|
||||
<Routes>
|
||||
<Route path={Path.Home} element={<Chat />} />
|
||||
|
@ -162,6 +168,7 @@ function Screen() {
|
|||
<Route path={Path.Masks} element={<MaskPage />} />
|
||||
<Route path={Path.Chat} element={<Chat />} />
|
||||
<Route path={Path.Sd} element={<Sd />} />
|
||||
<Route path={Path.SdPanel} element={<Sd />} />
|
||||
<Route path={Path.Settings} element={<Settings />} />
|
||||
</Routes>
|
||||
</div>
|
||||
|
@ -173,7 +180,6 @@ function Screen() {
|
|||
|
||||
export function useLoadData() {
|
||||
const config = useAppConfig();
|
||||
|
||||
var api: ClientApi;
|
||||
if (config.modelConfig.model.startsWith("gemini")) {
|
||||
api = new ClientApi(ModelProvider.GeminiPro);
|
||||
|
|
|
@ -1,8 +1,14 @@
|
|||
import styles from "./sd-panel.module.scss";
|
||||
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 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) => {
|
||||
return [
|
||||
|
@ -89,7 +95,7 @@ const sdCommonParams = (model: string, data: any) => {
|
|||
name: locales.SdPanel.OutFormat,
|
||||
value: "output_format",
|
||||
type: "select",
|
||||
default: 0,
|
||||
default: "png",
|
||||
options: [
|
||||
{ name: "PNG", value: "png" },
|
||||
{ name: "JPEG", value: "jpeg" },
|
||||
|
@ -128,6 +134,7 @@ const models = [
|
|||
export function ControlParamItem(props: {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
required?: boolean;
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
className?: string;
|
||||
}) {
|
||||
|
@ -135,7 +142,10 @@ export function ControlParamItem(props: {
|
|||
<div className={styles["ctrl-param-item"] + ` ${props.className || ""}`}>
|
||||
<div className={styles["ctrl-param-item-header"]}>
|
||||
<div className={styles["ctrl-param-item-title"]}>
|
||||
<div>{props.title}</div>
|
||||
<div>
|
||||
{props.title}
|
||||
{props.required && <span style={{ color: "red" }}>*</span>}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{props.children}
|
||||
|
@ -160,7 +170,11 @@ export function ControlParam(props: {
|
|||
switch (item.type) {
|
||||
case "textarea":
|
||||
element = (
|
||||
<ControlParamItem title={item.name} subTitle={item.sub}>
|
||||
<ControlParamItem
|
||||
title={item.name}
|
||||
subTitle={item.sub}
|
||||
required={item.required}
|
||||
>
|
||||
<textarea
|
||||
rows={item.rows || 3}
|
||||
style={{ maxWidth: "100%", width: "100%", padding: "10px" }}
|
||||
|
@ -175,7 +189,11 @@ export function ControlParam(props: {
|
|||
break;
|
||||
case "select":
|
||||
element = (
|
||||
<ControlParamItem title={item.name} subTitle={item.sub}>
|
||||
<ControlParamItem
|
||||
title={item.name}
|
||||
subTitle={item.sub}
|
||||
required={item.required}
|
||||
>
|
||||
<Select
|
||||
value={props.data[item.value]}
|
||||
onChange={(e) => {
|
||||
|
@ -195,7 +213,11 @@ export function ControlParam(props: {
|
|||
break;
|
||||
case "number":
|
||||
element = (
|
||||
<ControlParamItem title={item.name} subTitle={item.sub}>
|
||||
<ControlParamItem
|
||||
title={item.name}
|
||||
subTitle={item.sub}
|
||||
required={item.required}
|
||||
>
|
||||
<input
|
||||
type="number"
|
||||
min={item.min}
|
||||
|
@ -210,7 +232,11 @@ export function ControlParam(props: {
|
|||
break;
|
||||
default:
|
||||
element = (
|
||||
<ControlParamItem title={item.name} subTitle={item.sub}>
|
||||
<ControlParamItem
|
||||
title={item.name}
|
||||
subTitle={item.sub}
|
||||
required={item.required}
|
||||
>
|
||||
<input
|
||||
type="text"
|
||||
value={props.data[item.value]}
|
||||
|
@ -260,14 +286,43 @@ export function SdPanel() {
|
|||
setCurrentModel(model);
|
||||
setParams(getModelParamBasicData(model.params({}), params));
|
||||
};
|
||||
const sdListDb = useIndexedDB(StoreKey.SdList);
|
||||
const { execCountInc } = useSdStore();
|
||||
const handleSubmit = () => {
|
||||
const columns = currentModel.params(params);
|
||||
const reqData: any = {};
|
||||
columns.forEach((item: any) => {
|
||||
reqData[item.value] = params[item.value] ?? null;
|
||||
});
|
||||
console.log(JSON.stringify(reqData, null, 4));
|
||||
const reqParams: any = {};
|
||||
for (let i = 0; i < columns.length; i++) {
|
||||
const item = columns[i];
|
||||
reqParams[item.value] = params[item.value] ?? null;
|
||||
if (item.required) {
|
||||
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 (
|
||||
<>
|
||||
|
|
|
@ -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() {
|
||||
return <div>sd</div>;
|
||||
import chatStyles from "@/app/components/chat.module.scss";
|
||||
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;
|
||||
switch (location.pathname) {
|
||||
case Path.Sd:
|
||||
case Path.SdPanel:
|
||||
bodyComponent = <SdPanel />;
|
||||
break;
|
||||
default:
|
||||
|
@ -220,6 +221,7 @@ export function SideBar(props: { className?: string }) {
|
|||
|
||||
<div className={styles["sidebar-tail"]}>
|
||||
<div className={styles["sidebar-actions"]}>
|
||||
{isChat && (
|
||||
<div className={styles["sidebar-action"] + " " + styles.mobile}>
|
||||
<IconButton
|
||||
icon={<DeleteIcon />}
|
||||
|
@ -230,6 +232,7 @@ export function SideBar(props: { className?: string }) {
|
|||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<div className={styles["sidebar-action"]}>
|
||||
<Link to={Path.Settings}>
|
||||
<IconButton icon={<SettingsIcon />} shadow />
|
||||
|
|
|
@ -3,7 +3,7 @@ import { BuildConfig, getBuildConfig } from "./build";
|
|||
export function getClientConfig() {
|
||||
if (typeof document !== "undefined") {
|
||||
// client side
|
||||
return JSON.parse(queryMeta("config")) as BuildConfig;
|
||||
return JSON.parse(queryMeta("config") || "{}") as BuildConfig;
|
||||
}
|
||||
|
||||
if (typeof process !== "undefined") {
|
||||
|
|
|
@ -22,6 +22,7 @@ export enum Path {
|
|||
Masks = "/masks",
|
||||
Auth = "/auth",
|
||||
Sd = "/sd",
|
||||
SdPanel = "/sd-panel",
|
||||
}
|
||||
|
||||
export enum ApiPath {
|
||||
|
@ -48,6 +49,7 @@ export enum StoreKey {
|
|||
Prompt = "prompt-store",
|
||||
Update = "chat-update",
|
||||
Sync = "sync",
|
||||
SdList = "sd-list",
|
||||
}
|
||||
|
||||
export const DEFAULT_SIDEBAR_WIDTH = 300;
|
||||
|
|
|
@ -494,6 +494,7 @@ const cn = {
|
|||
AIModel: "AI模型",
|
||||
ModelVersion: "模型版本",
|
||||
Submit: "提交生成",
|
||||
ParamIsRequired: (name: string) => `${name}不能为空`,
|
||||
Styles: {
|
||||
D3Model: "3D模型",
|
||||
AnalogFilm: "模拟电影",
|
||||
|
@ -514,6 +515,26 @@ const cn = {
|
|||
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
|
||||
|
|
|
@ -500,6 +500,7 @@ const en: LocaleType = {
|
|||
AIModel: "AI Model",
|
||||
ModelVersion: "Model Version",
|
||||
Submit: "Submit",
|
||||
ParamIsRequired: (name: string) => `${name} is required`,
|
||||
Styles: {
|
||||
D3Model: "3d-model",
|
||||
AnalogFilm: "analog-film",
|
||||
|
@ -520,6 +521,26 @@ const en: LocaleType = {
|
|||
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;
|
||||
|
|
|
@ -3,6 +3,7 @@ import { Analytics } from "@vercel/analytics/react";
|
|||
import { Home } from "./components/home";
|
||||
|
||||
import { getServerSideConfig } from "./config/server";
|
||||
import { SdDbInit } from "@/app/store/sd";
|
||||
|
||||
const serverConfig = getServerSideConfig();
|
||||
|
||||
|
|
|
@ -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",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"react-indexed-db-hook": "^1.0.14",
|
||||
"react-markdown": "^8.0.7",
|
||||
"react-router-dom": "^6.15.0",
|
||||
"rehype-highlight": "^6.0.0",
|
||||
|
|
|
@ -5110,6 +5110,11 @@ react-dom@^18.2.0:
|
|||
loose-envify "^1.1.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:
|
||||
version "16.13.1"
|
||||
resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"
|
||||
|
|
Loading…
Reference in New Issue