forked from GithubProxy/ChatGPT-Next-Web
refator: sd
This commit is contained in:
parent
e468fecf12
commit
9d55adbaf2
2
.gitignore
vendored
2
.gitignore
vendored
@ -44,3 +44,5 @@ dev
|
|||||||
|
|
||||||
*.key
|
*.key
|
||||||
*.key.pub
|
*.key.pub
|
||||||
|
|
||||||
|
masks.json
|
||||||
|
@ -59,7 +59,7 @@ const Sd = dynamic(async () => (await import("./sd")).Sd, {
|
|||||||
loading: () => <Loading noLogo />,
|
loading: () => <Loading noLogo />,
|
||||||
});
|
});
|
||||||
|
|
||||||
const SdPanel = dynamic(async () => (await import("./sd-panel")).SdPanel, {
|
const SdPanel = dynamic(async () => (await import("./sd")).SdPanel, {
|
||||||
loading: () => <Loading noLogo />,
|
loading: () => <Loading noLogo />,
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -130,12 +130,22 @@ const loadAsyncGoogleFont = () => {
|
|||||||
document.head.appendChild(linkEl);
|
document.head.appendChild(linkEl);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function WindowContent(props: { children: React.ReactNode }) {
|
||||||
|
return (
|
||||||
|
<div className={styles["window-content"]} id={SlotID.AppBody}>
|
||||||
|
{props?.children}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
function Screen() {
|
function Screen() {
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const isHome =
|
const isHome = location.pathname === Path.Home;
|
||||||
location.pathname === Path.Home || location.pathname === Path.SdPanel;
|
|
||||||
const isAuth = location.pathname === Path.Auth;
|
const isAuth = location.pathname === Path.Auth;
|
||||||
|
const isSd = location.pathname === Path.Sd;
|
||||||
|
const isSdPanel = location.pathname === Path.SdPanel;
|
||||||
|
|
||||||
const isMobileScreen = useMobileScreen();
|
const isMobileScreen = useMobileScreen();
|
||||||
const shouldTightBorder =
|
const shouldTightBorder =
|
||||||
getClientConfig()?.isApp || (config.tightBorder && !isMobileScreen);
|
getClientConfig()?.isApp || (config.tightBorder && !isMobileScreen);
|
||||||
@ -143,35 +153,36 @@ function Screen() {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
loadAsyncGoogleFont();
|
loadAsyncGoogleFont();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const renderContent = () => {
|
||||||
|
if (isAuth) return <AuthPage />;
|
||||||
|
if (isSd) return <Sd />;
|
||||||
|
if (isSdPanel) return <SdPanel />;
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SideBar className={isHome ? styles["sidebar-show"] : ""} />
|
||||||
|
<WindowContent>
|
||||||
|
<Routes>
|
||||||
|
<Route path={Path.Home} element={<Chat />} />
|
||||||
|
<Route path={Path.NewChat} element={<NewChat />} />
|
||||||
|
<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>
|
||||||
|
</WindowContent>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={
|
className={`${styles.container} ${
|
||||||
styles.container +
|
shouldTightBorder ? styles["tight-container"] : styles.container
|
||||||
` ${shouldTightBorder ? styles["tight-container"] : styles.container} ${
|
} ${getLang() === "ar" ? styles["rtl-screen"] : ""}`}
|
||||||
getLang() === "ar" ? styles["rtl-screen"] : ""
|
|
||||||
}`
|
|
||||||
}
|
|
||||||
>
|
>
|
||||||
{isAuth ? (
|
{renderContent()}
|
||||||
<>
|
|
||||||
<AuthPage />
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<SideBar className={isHome ? styles["sidebar-show"] : ""} />
|
|
||||||
<div className={styles["window-content"]} id={SlotID.AppBody}>
|
|
||||||
<Routes>
|
|
||||||
<Route path={Path.Home} element={<Chat />} />
|
|
||||||
<Route path={Path.NewChat} element={<NewChat />} />
|
|
||||||
<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>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,278 +0,0 @@
|
|||||||
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 } from "@/app/constant";
|
|
||||||
import React, { useEffect, useMemo, useRef, useState } from "react";
|
|
||||||
import {
|
|
||||||
copyToClipboard,
|
|
||||||
getMessageTextContent,
|
|
||||||
useMobileScreen,
|
|
||||||
} 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 { 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,
|
|
||||||
showImageModal,
|
|
||||||
showModal,
|
|
||||||
} from "@/app/components/ui-lib";
|
|
||||||
import { removeImage } from "@/app/utils/chat";
|
|
||||||
|
|
||||||
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
|
|
||||||
className="clickable"
|
|
||||||
onClick={() => {
|
|
||||||
showModal({
|
|
||||||
title: locales.Sd.Detail,
|
|
||||||
children: (
|
|
||||||
<div style={{ color: color, userSelect: "text" }}>
|
|
||||||
{item.error}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{" "}
|
|
||||||
- {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 sdStore = useSdStore();
|
|
||||||
const [sdImages, setSdImages] = useState(sdStore.draw);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSdImages(sdStore.draw);
|
|
||||||
}, [sdStore.currentId]);
|
|
||||||
|
|
||||||
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={item.img_data}
|
|
||||||
alt={item.id}
|
|
||||||
onClick={(e) =>
|
|
||||||
showImageModal(
|
|
||||||
item.img_data,
|
|
||||||
true,
|
|
||||||
isMobileScreen
|
|
||||||
? { width: "100%", height: "fit-content" }
|
|
||||||
: { maxWidth: "100%", maxHeight: "100%" },
|
|
||||||
isMobileScreen
|
|
||||||
? { width: "100%", height: "fit-content" }
|
|
||||||
: { width: "100%", height: "100%" },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
) : 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
|
|
||||||
className="clickable"
|
|
||||||
title={item.params.prompt}
|
|
||||||
onClick={() => {
|
|
||||||
showModal({
|
|
||||||
title: locales.Sd.Detail,
|
|
||||||
children: (
|
|
||||||
<div style={{ userSelect: "text" }}>
|
|
||||||
{item.params.prompt}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{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={() => {
|
|
||||||
showModal({
|
|
||||||
title: locales.Sd.GenerateParams,
|
|
||||||
children: (
|
|
||||||
<div style={{ userSelect: "text" }}>
|
|
||||||
{Object.keys(item.params).map((key) => (
|
|
||||||
<div key={key} style={{ margin: "10px" }}>
|
|
||||||
<strong>{key}: </strong>
|
|
||||||
{item.params[key]}
|
|
||||||
</div>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<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={() => {
|
|
||||||
const reqData = {
|
|
||||||
model: item.model,
|
|
||||||
model_name: item.model_name,
|
|
||||||
status: "wait",
|
|
||||||
params: { ...item.params },
|
|
||||||
created_at: new Date().toLocaleString(),
|
|
||||||
img_data: "",
|
|
||||||
};
|
|
||||||
sdStore.sendTask(reqData);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<ChatAction
|
|
||||||
text={Locale.Sd.Actions.Delete}
|
|
||||||
icon={<DeleteIcon />}
|
|
||||||
onClick={async () => {
|
|
||||||
if (await showConfirm(Locale.Sd.Danger.Delete)) {
|
|
||||||
// remove img_data + remove item in list
|
|
||||||
removeImage(item.img_data).finally(() => {
|
|
||||||
sdStore.draw = sdImages.filter(
|
|
||||||
(i: any) => i.id !== item.id,
|
|
||||||
);
|
|
||||||
sdStore.getNextId();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
})
|
|
||||||
) : (
|
|
||||||
<div>{locales.Sd.EmptyRecord}</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
2
app/components/sd/index.tsx
Normal file
2
app/components/sd/index.tsx
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export * from "./sd";
|
||||||
|
export * from "./sd-panel";
|
@ -10,7 +10,7 @@
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
|
||||||
.ctrl-param-item-title{
|
.ctrl-param-item-title {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
margin-bottom: 5px;
|
margin-bottom: 5px;
|
||||||
@ -22,12 +22,24 @@
|
|||||||
font-weight: normal;
|
font-weight: normal;
|
||||||
margin-top: 3px;
|
margin-top: 3px;
|
||||||
}
|
}
|
||||||
|
textarea {
|
||||||
|
appearance: none;
|
||||||
|
border-radius: 10px;
|
||||||
|
border: var(--border-in-light);
|
||||||
|
min-height: 36px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
background: var(--white);
|
||||||
|
color: var(--black);
|
||||||
|
padding: 0 10px;
|
||||||
|
max-width: 50%;
|
||||||
|
font-family: inherit;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.ai-models{
|
.ai-models {
|
||||||
button{
|
button {
|
||||||
margin-bottom: 10px;
|
margin-bottom: 10px;
|
||||||
padding:10px;
|
padding: 10px;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -2,7 +2,7 @@ import styles from "./sd-panel.module.scss";
|
|||||||
import React, { useState } from "react";
|
import React, { useState } from "react";
|
||||||
import { Select, showToast } 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 Locale from "@/app/locales";
|
||||||
import { nanoid } from "nanoid";
|
import { nanoid } from "nanoid";
|
||||||
import { StoreKey } from "@/app/constant";
|
import { StoreKey } from "@/app/constant";
|
||||||
import { useSdStore } from "@/app/store/sd";
|
import { useSdStore } from "@/app/store/sd";
|
||||||
@ -10,14 +10,14 @@ import { useSdStore } from "@/app/store/sd";
|
|||||||
const sdCommonParams = (model: string, data: any) => {
|
const sdCommonParams = (model: string, data: any) => {
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: locales.SdPanel.Prompt,
|
name: Locale.SdPanel.Prompt,
|
||||||
value: "prompt",
|
value: "prompt",
|
||||||
type: "textarea",
|
type: "textarea",
|
||||||
placeholder: locales.SdPanel.PleaseInput(locales.SdPanel.Prompt),
|
placeholder: Locale.SdPanel.PleaseInput(Locale.SdPanel.Prompt),
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: locales.SdPanel.ModelVersion,
|
name: Locale.SdPanel.ModelVersion,
|
||||||
value: "model",
|
value: "model",
|
||||||
type: "select",
|
type: "select",
|
||||||
default: "sd3-medium",
|
default: "sd3-medium",
|
||||||
@ -29,13 +29,13 @@ const sdCommonParams = (model: string, data: any) => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: locales.SdPanel.NegativePrompt,
|
name: Locale.SdPanel.NegativePrompt,
|
||||||
value: "negative_prompt",
|
value: "negative_prompt",
|
||||||
type: "textarea",
|
type: "textarea",
|
||||||
placeholder: locales.SdPanel.PleaseInput(locales.SdPanel.NegativePrompt),
|
placeholder: Locale.SdPanel.PleaseInput(Locale.SdPanel.NegativePrompt),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: locales.SdPanel.AspectRatio,
|
name: Locale.SdPanel.AspectRatio,
|
||||||
value: "aspect_ratio",
|
value: "aspect_ratio",
|
||||||
type: "select",
|
type: "select",
|
||||||
default: "1:1",
|
default: "1:1",
|
||||||
@ -52,32 +52,32 @@ const sdCommonParams = (model: string, data: any) => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: locales.SdPanel.ImageStyle,
|
name: Locale.SdPanel.ImageStyle,
|
||||||
value: "style",
|
value: "style",
|
||||||
type: "select",
|
type: "select",
|
||||||
default: "3d",
|
default: "3d",
|
||||||
support: ["core"],
|
support: ["core"],
|
||||||
options: [
|
options: [
|
||||||
{ name: locales.SdPanel.Styles.D3Model, value: "3d-model" },
|
{ name: Locale.SdPanel.Styles.D3Model, value: "3d-model" },
|
||||||
{ name: locales.SdPanel.Styles.AnalogFilm, value: "analog-film" },
|
{ name: Locale.SdPanel.Styles.AnalogFilm, value: "analog-film" },
|
||||||
{ name: locales.SdPanel.Styles.Anime, value: "anime" },
|
{ name: Locale.SdPanel.Styles.Anime, value: "anime" },
|
||||||
{ name: locales.SdPanel.Styles.Cinematic, value: "cinematic" },
|
{ name: Locale.SdPanel.Styles.Cinematic, value: "cinematic" },
|
||||||
{ name: locales.SdPanel.Styles.ComicBook, value: "comic-book" },
|
{ name: Locale.SdPanel.Styles.ComicBook, value: "comic-book" },
|
||||||
{ name: locales.SdPanel.Styles.DigitalArt, value: "digital-art" },
|
{ name: Locale.SdPanel.Styles.DigitalArt, value: "digital-art" },
|
||||||
{ name: locales.SdPanel.Styles.Enhance, value: "enhance" },
|
{ name: Locale.SdPanel.Styles.Enhance, value: "enhance" },
|
||||||
{ name: locales.SdPanel.Styles.FantasyArt, value: "fantasy-art" },
|
{ name: Locale.SdPanel.Styles.FantasyArt, value: "fantasy-art" },
|
||||||
{ name: locales.SdPanel.Styles.Isometric, value: "isometric" },
|
{ name: Locale.SdPanel.Styles.Isometric, value: "isometric" },
|
||||||
{ name: locales.SdPanel.Styles.LineArt, value: "line-art" },
|
{ name: Locale.SdPanel.Styles.LineArt, value: "line-art" },
|
||||||
{ name: locales.SdPanel.Styles.LowPoly, value: "low-poly" },
|
{ name: Locale.SdPanel.Styles.LowPoly, value: "low-poly" },
|
||||||
{
|
{
|
||||||
name: locales.SdPanel.Styles.ModelingCompound,
|
name: Locale.SdPanel.Styles.ModelingCompound,
|
||||||
value: "modeling-compound",
|
value: "modeling-compound",
|
||||||
},
|
},
|
||||||
{ name: locales.SdPanel.Styles.NeonPunk, value: "neon-punk" },
|
{ name: Locale.SdPanel.Styles.NeonPunk, value: "neon-punk" },
|
||||||
{ name: locales.SdPanel.Styles.Origami, value: "origami" },
|
{ name: Locale.SdPanel.Styles.Origami, value: "origami" },
|
||||||
{ name: locales.SdPanel.Styles.Photographic, value: "photographic" },
|
{ name: Locale.SdPanel.Styles.Photographic, value: "photographic" },
|
||||||
{ name: locales.SdPanel.Styles.PixelArt, value: "pixel-art" },
|
{ name: Locale.SdPanel.Styles.PixelArt, value: "pixel-art" },
|
||||||
{ name: locales.SdPanel.Styles.TileTexture, value: "tile-texture" },
|
{ name: Locale.SdPanel.Styles.TileTexture, value: "tile-texture" },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@ -89,7 +89,7 @@ const sdCommonParams = (model: string, data: any) => {
|
|||||||
max: 4294967294,
|
max: 4294967294,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: locales.SdPanel.OutFormat,
|
name: Locale.SdPanel.OutFormat,
|
||||||
value: "output_format",
|
value: "output_format",
|
||||||
type: "select",
|
type: "select",
|
||||||
default: "png",
|
default: "png",
|
||||||
@ -292,7 +292,7 @@ export function SdPanel() {
|
|||||||
reqParams[item.value] = params[item.value] ?? null;
|
reqParams[item.value] = params[item.value] ?? null;
|
||||||
if (item.required) {
|
if (item.required) {
|
||||||
if (!reqParams[item.value]) {
|
if (!reqParams[item.value]) {
|
||||||
showToast(locales.SdPanel.ParamIsRequired(item.name));
|
showToast(Locale.SdPanel.ParamIsRequired(item.name));
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -311,7 +311,7 @@ export function SdPanel() {
|
|||||||
};
|
};
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ControlParamItem title={locales.SdPanel.AIModel}>
|
<ControlParamItem title={Locale.SdPanel.AIModel}>
|
||||||
<div className={styles["ai-models"]}>
|
<div className={styles["ai-models"]}>
|
||||||
{models.map((item) => {
|
{models.map((item) => {
|
||||||
return (
|
return (
|
||||||
@ -332,7 +332,7 @@ export function SdPanel() {
|
|||||||
onChange={handleValueChange}
|
onChange={handleValueChange}
|
||||||
></ControlParam>
|
></ControlParam>
|
||||||
<IconButton
|
<IconButton
|
||||||
text={locales.SdPanel.Submit}
|
text={Locale.SdPanel.Submit}
|
||||||
type="primary"
|
type="primary"
|
||||||
style={{ marginTop: "20px" }}
|
style={{ marginTop: "20px" }}
|
||||||
shadow
|
shadow
|
65
app/components/sd/sd-sidebar.tsx
Normal file
65
app/components/sd/sd-sidebar.tsx
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import styles from "@/app/components/home.module.scss";
|
||||||
|
|
||||||
|
import { IconButton } from "@/app/components/button";
|
||||||
|
import GithubIcon from "@/app/icons/github.svg";
|
||||||
|
import SDIcon from "@/app/icons/sd.svg";
|
||||||
|
import ReturnIcon from "@/app/icons/return.svg";
|
||||||
|
import Locale from "@/app/locales";
|
||||||
|
|
||||||
|
import { Path, REPO_URL } from "@/app/constant";
|
||||||
|
|
||||||
|
import { useNavigate } from "react-router-dom";
|
||||||
|
import dynamic from "next/dynamic";
|
||||||
|
import {
|
||||||
|
SideBarContainer,
|
||||||
|
SideBarBody,
|
||||||
|
useDragSideBar,
|
||||||
|
useHotKey,
|
||||||
|
} from "@/app/components/sidebar";
|
||||||
|
|
||||||
|
const SdPanel = dynamic(
|
||||||
|
async () => (await import("@/app/components/sd/sd-panel")).SdPanel,
|
||||||
|
{
|
||||||
|
loading: () => null,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
export function SideBar(props: { className?: string }) {
|
||||||
|
useHotKey();
|
||||||
|
const { onDragStart, shouldNarrow } = useDragSideBar();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SideBarContainer
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
shouldNarrow={shouldNarrow}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<div className={styles["sidebar-header"]} data-tauri-drag-region>
|
||||||
|
<div className={styles["sidebar-title"]} data-tauri-drag-region>
|
||||||
|
<IconButton
|
||||||
|
icon={<ReturnIcon />}
|
||||||
|
bordered
|
||||||
|
title={Locale.Chat.Actions.ChatList}
|
||||||
|
onClick={() => navigate(Path.Chat)}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles["sidebar-logo"] + " no-dark"}>
|
||||||
|
<SDIcon width={38} height={38} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<SideBarBody>
|
||||||
|
<SdPanel />
|
||||||
|
</SideBarBody>
|
||||||
|
<div className={styles["sidebar-tail"]}>
|
||||||
|
<div className={styles["sidebar-actions"]}>
|
||||||
|
<div className={styles["sidebar-action"]}>
|
||||||
|
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
|
||||||
|
<IconButton icon={<GithubIcon />} shadow />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</SideBarContainer>
|
||||||
|
);
|
||||||
|
}
|
292
app/components/sd/sd.tsx
Normal file
292
app/components/sd/sd.tsx
Normal file
@ -0,0 +1,292 @@
|
|||||||
|
import chatStyles from "@/app/components/chat.module.scss";
|
||||||
|
import styles from "@/app/components/sd/sd.module.scss";
|
||||||
|
import { IconButton } from "@/app/components/button";
|
||||||
|
import ReturnIcon from "@/app/icons/return.svg";
|
||||||
|
import Locale from "@/app/locales";
|
||||||
|
import { Path } from "@/app/constant";
|
||||||
|
import React, { useEffect, useMemo, useRef, useState } from "react";
|
||||||
|
import {
|
||||||
|
copyToClipboard,
|
||||||
|
getMessageTextContent,
|
||||||
|
useMobileScreen,
|
||||||
|
} 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 { useSdStore } from "@/app/store/sd";
|
||||||
|
import locales from "@/app/locales";
|
||||||
|
import LoadingIcon from "@/app/icons/three-dots.svg";
|
||||||
|
import ErrorIcon from "@/app/icons/delete.svg";
|
||||||
|
import { Property } from "csstype";
|
||||||
|
import {
|
||||||
|
showConfirm,
|
||||||
|
showImageModal,
|
||||||
|
showModal,
|
||||||
|
} from "@/app/components/ui-lib";
|
||||||
|
import { removeImage } from "@/app/utils/chat";
|
||||||
|
import { SideBar } from "./sd-sidebar";
|
||||||
|
import { WindowContent } from "@/app/components/home";
|
||||||
|
|
||||||
|
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
|
||||||
|
className="clickable"
|
||||||
|
onClick={() => {
|
||||||
|
showModal({
|
||||||
|
title: locales.Sd.Detail,
|
||||||
|
children: (
|
||||||
|
<div style={{ color: color, userSelect: "text" }}>
|
||||||
|
{item.error}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{" "}
|
||||||
|
- {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 sdStore = useSdStore();
|
||||||
|
const [sdImages, setSdImages] = useState(sdStore.draw);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSdImages(sdStore.draw);
|
||||||
|
}, [sdStore.currentId]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<SideBar />
|
||||||
|
<WindowContent>
|
||||||
|
<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={item.img_data}
|
||||||
|
alt={item.id}
|
||||||
|
onClick={(e) =>
|
||||||
|
showImageModal(
|
||||||
|
item.img_data,
|
||||||
|
true,
|
||||||
|
isMobileScreen
|
||||||
|
? { width: "100%", height: "fit-content" }
|
||||||
|
: { maxWidth: "100%", maxHeight: "100%" },
|
||||||
|
isMobileScreen
|
||||||
|
? { width: "100%", height: "fit-content" }
|
||||||
|
: { width: "100%", height: "100%" },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
) : 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
|
||||||
|
className="clickable"
|
||||||
|
title={item.params.prompt}
|
||||||
|
onClick={() => {
|
||||||
|
showModal({
|
||||||
|
title: locales.Sd.Detail,
|
||||||
|
children: (
|
||||||
|
<div style={{ userSelect: "text" }}>
|
||||||
|
{item.params.prompt}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{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={() => {
|
||||||
|
showModal({
|
||||||
|
title: locales.Sd.GenerateParams,
|
||||||
|
children: (
|
||||||
|
<div style={{ userSelect: "text" }}>
|
||||||
|
{Object.keys(item.params).map((key) => (
|
||||||
|
<div
|
||||||
|
key={key}
|
||||||
|
style={{ margin: "10px" }}
|
||||||
|
>
|
||||||
|
<strong>{key}: </strong>
|
||||||
|
{item.params[key]}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<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={() => {
|
||||||
|
const reqData = {
|
||||||
|
model: item.model,
|
||||||
|
model_name: item.model_name,
|
||||||
|
status: "wait",
|
||||||
|
params: { ...item.params },
|
||||||
|
created_at: new Date().toLocaleString(),
|
||||||
|
img_data: "",
|
||||||
|
};
|
||||||
|
sdStore.sendTask(reqData);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<ChatAction
|
||||||
|
text={Locale.Sd.Actions.Delete}
|
||||||
|
icon={<DeleteIcon />}
|
||||||
|
onClick={async () => {
|
||||||
|
if (
|
||||||
|
await showConfirm(Locale.Sd.Danger.Delete)
|
||||||
|
) {
|
||||||
|
// remove img_data + remove item in list
|
||||||
|
removeImage(item.img_data).finally(() => {
|
||||||
|
sdStore.draw = sdImages.filter(
|
||||||
|
(i: any) => i.id !== item.id,
|
||||||
|
);
|
||||||
|
sdStore.getNextId();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
) : (
|
||||||
|
<div>{locales.Sd.EmptyRecord}</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</WindowContent>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import React, { useEffect, useRef, useMemo, useState } from "react";
|
import { useEffect, useRef, useMemo, useState } from "react";
|
||||||
|
|
||||||
import styles from "./home.module.scss";
|
import styles from "./home.module.scss";
|
||||||
|
|
||||||
@ -15,7 +15,7 @@ import DragIcon from "../icons/drag.svg";
|
|||||||
|
|
||||||
import Locale from "../locales";
|
import Locale from "../locales";
|
||||||
|
|
||||||
import { ModelType, useAppConfig, useChatStore } from "../store";
|
import { useAppConfig, useChatStore } from "../store";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
DEFAULT_SIDEBAR_WIDTH,
|
DEFAULT_SIDEBAR_WIDTH,
|
||||||
@ -27,20 +27,16 @@ import {
|
|||||||
REPO_URL,
|
REPO_URL,
|
||||||
} from "../constant";
|
} from "../constant";
|
||||||
|
|
||||||
import { Link, useLocation, useNavigate } from "react-router-dom";
|
import { Link, useNavigate } from "react-router-dom";
|
||||||
import { isIOS, useMobileScreen } from "../utils";
|
import { isIOS, useMobileScreen } from "../utils";
|
||||||
import dynamic from "next/dynamic";
|
import dynamic from "next/dynamic";
|
||||||
import { Selector, showConfirm, showToast } from "./ui-lib";
|
import { showConfirm, Selector } from "./ui-lib";
|
||||||
|
|
||||||
const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
|
const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
|
||||||
loading: () => null,
|
loading: () => null,
|
||||||
});
|
});
|
||||||
|
|
||||||
const SdPanel = dynamic(async () => (await import("./sd-panel")).SdPanel, {
|
export function useHotKey() {
|
||||||
loading: () => null,
|
|
||||||
});
|
|
||||||
|
|
||||||
function useHotKey() {
|
|
||||||
const chatStore = useChatStore();
|
const chatStore = useChatStore();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -59,7 +55,7 @@ function useHotKey() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function useDragSideBar() {
|
export function useDragSideBar() {
|
||||||
const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x);
|
const limit = (x: number) => Math.min(MAX_SIDEBAR_WIDTH, x);
|
||||||
|
|
||||||
const config = useAppConfig();
|
const config = useAppConfig();
|
||||||
@ -132,39 +128,21 @@ function useDragSideBar() {
|
|||||||
shouldNarrow,
|
shouldNarrow,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export function SideBarContainer(props: {
|
||||||
export function SideBar(props: { className?: string }) {
|
children: React.ReactNode;
|
||||||
const chatStore = useChatStore();
|
onDragStart: (e: MouseEvent) => void;
|
||||||
|
shouldNarrow: boolean;
|
||||||
// drag side bar
|
className?: string;
|
||||||
const { onDragStart, shouldNarrow } = useDragSideBar();
|
}) {
|
||||||
const navigate = useNavigate();
|
|
||||||
const config = useAppConfig();
|
|
||||||
const isMobileScreen = useMobileScreen();
|
const isMobileScreen = useMobileScreen();
|
||||||
const isIOSMobile = useMemo(
|
const isIOSMobile = useMemo(
|
||||||
() => isIOS() && isMobileScreen,
|
() => isIOS() && isMobileScreen,
|
||||||
[isMobileScreen],
|
[isMobileScreen],
|
||||||
);
|
);
|
||||||
const [showPluginSelector, setShowPluginSelector] = useState(false);
|
const { children, className, onDragStart, shouldNarrow } = props;
|
||||||
const location = useLocation();
|
|
||||||
|
|
||||||
useHotKey();
|
|
||||||
|
|
||||||
let bodyComponent: React.JSX.Element;
|
|
||||||
let isChat: boolean = false;
|
|
||||||
switch (location.pathname) {
|
|
||||||
case Path.Sd:
|
|
||||||
case Path.SdPanel:
|
|
||||||
bodyComponent = <SdPanel />;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
isChat = true;
|
|
||||||
bodyComponent = <ChatList narrow={shouldNarrow} />;
|
|
||||||
}
|
|
||||||
// @ts-ignore
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={`${styles.sidebar} ${props.className} ${
|
className={`${styles.sidebar} ${className} ${
|
||||||
shouldNarrow && styles["narrow-sidebar"]
|
shouldNarrow && styles["narrow-sidebar"]
|
||||||
}`}
|
}`}
|
||||||
style={{
|
style={{
|
||||||
@ -172,6 +150,25 @@ export function SideBar(props: { className?: string }) {
|
|||||||
transition: isMobileScreen && isIOSMobile ? "none" : undefined,
|
transition: isMobileScreen && isIOSMobile ? "none" : undefined,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
{children}
|
||||||
|
<div
|
||||||
|
className={styles["sidebar-drag"]}
|
||||||
|
onPointerDown={(e) => onDragStart(e as any)}
|
||||||
|
>
|
||||||
|
<DragIcon />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SideBarHeader(props: { shouldNarrow: boolean }) {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const config = useAppConfig();
|
||||||
|
const { shouldNarrow } = props;
|
||||||
|
const [showPluginSelector, setShowPluginSelector] = useState(false);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
<div className={styles["sidebar-header"]} data-tauri-drag-region>
|
<div className={styles["sidebar-header"]} data-tauri-drag-region>
|
||||||
<div className={styles["sidebar-title"]} data-tauri-drag-region>
|
<div className={styles["sidebar-title"]} data-tauri-drag-region>
|
||||||
NextChat
|
NextChat
|
||||||
@ -206,68 +203,6 @@ export function SideBar(props: { className?: string }) {
|
|||||||
shadow
|
shadow
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div
|
|
||||||
className={styles["sidebar-body"]}
|
|
||||||
onClick={(e) => {
|
|
||||||
if (isChat && e.target === e.currentTarget) {
|
|
||||||
navigate(Path.Home);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{bodyComponent}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={styles["sidebar-tail"]}>
|
|
||||||
<div className={styles["sidebar-actions"]}>
|
|
||||||
{isChat && (
|
|
||||||
<div className={styles["sidebar-action"] + " " + styles.mobile}>
|
|
||||||
<IconButton
|
|
||||||
icon={<DeleteIcon />}
|
|
||||||
onClick={async () => {
|
|
||||||
if (await showConfirm(Locale.Home.DeleteChat)) {
|
|
||||||
chatStore.deleteSession(chatStore.currentSessionIndex);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
<div className={styles["sidebar-action"]}>
|
|
||||||
<Link to={Path.Settings}>
|
|
||||||
<IconButton icon={<SettingsIcon />} shadow />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
<div className={styles["sidebar-action"]}>
|
|
||||||
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
|
|
||||||
<IconButton icon={<GithubIcon />} shadow />
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
{isChat && (
|
|
||||||
<div>
|
|
||||||
<IconButton
|
|
||||||
icon={<AddIcon />}
|
|
||||||
text={shouldNarrow ? undefined : Locale.Home.NewChat}
|
|
||||||
onClick={() => {
|
|
||||||
if (config.dontShowMaskSplashScreen) {
|
|
||||||
chatStore.newSession();
|
|
||||||
navigate(Path.Chat);
|
|
||||||
} else {
|
|
||||||
navigate(Path.NewChat);
|
|
||||||
}
|
|
||||||
}}
|
|
||||||
shadow
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className={styles["sidebar-drag"]}
|
|
||||||
onPointerDown={(e) => onDragStart(e as any)}
|
|
||||||
>
|
|
||||||
<DragIcon />
|
|
||||||
</div>
|
|
||||||
{showPluginSelector && (
|
{showPluginSelector && (
|
||||||
<Selector
|
<Selector
|
||||||
items={[
|
items={[
|
||||||
@ -289,6 +224,92 @@ export function SideBar(props: { className?: string }) {
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SideBarBody(props: {
|
||||||
|
children: React.ReactNode;
|
||||||
|
onClick?: (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => void;
|
||||||
|
}) {
|
||||||
|
const { onClick, children } = props;
|
||||||
|
return (
|
||||||
|
<div className={styles["sidebar-body"]} onClick={onClick}>
|
||||||
|
{children}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function SideBarTail(props: { shouldNarrow: boolean }) {
|
||||||
|
const { shouldNarrow } = props;
|
||||||
|
const chatStore = useChatStore();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const config = useAppConfig();
|
||||||
|
return (
|
||||||
|
<div className={styles["sidebar-tail"]}>
|
||||||
|
<div className={styles["sidebar-actions"]}>
|
||||||
|
<div className={styles["sidebar-action"] + " " + styles.mobile}>
|
||||||
|
<IconButton
|
||||||
|
icon={<DeleteIcon />}
|
||||||
|
onClick={async () => {
|
||||||
|
if (await showConfirm(Locale.Home.DeleteChat)) {
|
||||||
|
chatStore.deleteSession(chatStore.currentSessionIndex);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className={styles["sidebar-action"]}>
|
||||||
|
<Link to={Path.Settings}>
|
||||||
|
<IconButton icon={<SettingsIcon />} shadow />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
<div className={styles["sidebar-action"]}>
|
||||||
|
<a href={REPO_URL} target="_blank" rel="noopener noreferrer">
|
||||||
|
<IconButton icon={<GithubIcon />} shadow />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<IconButton
|
||||||
|
icon={<AddIcon />}
|
||||||
|
text={shouldNarrow ? undefined : Locale.Home.NewChat}
|
||||||
|
onClick={() => {
|
||||||
|
if (config.dontShowMaskSplashScreen) {
|
||||||
|
chatStore.newSession();
|
||||||
|
navigate(Path.Chat);
|
||||||
|
} else {
|
||||||
|
navigate(Path.NewChat);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
shadow
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function SideBar(props: { className?: string }) {
|
||||||
|
useHotKey();
|
||||||
|
const { onDragStart, shouldNarrow } = useDragSideBar();
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<SideBarContainer
|
||||||
|
onDragStart={onDragStart}
|
||||||
|
shouldNarrow={shouldNarrow}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<SideBarHeader shouldNarrow={shouldNarrow} />
|
||||||
|
<SideBarBody
|
||||||
|
onClick={(e) => {
|
||||||
|
if (e.target === e.currentTarget) {
|
||||||
|
navigate(Path.Home);
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<ChatList narrow={shouldNarrow} />
|
||||||
|
</SideBarBody>
|
||||||
|
<SideBarTail shouldNarrow={shouldNarrow} />
|
||||||
|
</SideBarContainer>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
12
app/icons/sd.svg
Normal file
12
app/icons/sd.svg
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="1.21em" height="1em" viewBox="0 0 256 213">
|
||||||
|
<defs>
|
||||||
|
<linearGradient id="logosStabilityAiIcon0" x1="50%" x2="50%" y1="0%" y2="100%">
|
||||||
|
<stop offset="0%" stop-color="#9d39ff" />
|
||||||
|
<stop offset="100%" stop-color="#a380ff" />
|
||||||
|
</linearGradient>
|
||||||
|
</defs>
|
||||||
|
<path fill="url(#logosStabilityAiIcon0)"
|
||||||
|
d="M72.418 212.45c49.478 0 81.658-26.205 81.658-65.626c0-30.572-19.572-49.998-54.569-58.043l-22.469-6.74c-19.71-4.424-31.215-9.738-28.505-23.312c2.255-11.292 9.002-17.667 24.69-17.667c49.872 0 68.35 17.667 68.35 17.667V16.237S123.583 0 73.223 0C25.757 0 0 24.424 0 62.236c0 30.571 17.85 48.35 54.052 56.798q3.802.95 3.885.976q8.26 2.556 22.293 6.755c18.504 4.425 23.262 9.121 23.262 23.2c0 12.872-13.374 20.19-31.074 20.19C21.432 170.154 0 144.36 0 144.36v47.078s13.402 21.01 72.418 21.01" />
|
||||||
|
<path fill="#e80000"
|
||||||
|
d="M225.442 209.266c17.515 0 30.558-12.67 30.558-29.812c0-17.515-12.67-29.813-30.558-29.813c-17.515 0-30.185 12.298-30.185 29.813s12.67 29.812 30.185 29.812" />
|
||||||
|
</svg>
|
After Width: | Height: | Size: 1.1 KiB |
@ -226,8 +226,7 @@ input[type="range"]::-ms-thumb:hover {
|
|||||||
|
|
||||||
input[type="number"],
|
input[type="number"],
|
||||||
input[type="text"],
|
input[type="text"],
|
||||||
input[type="password"],
|
input[type="password"] {
|
||||||
textarea {
|
|
||||||
appearance: none;
|
appearance: none;
|
||||||
border-radius: 10px;
|
border-radius: 10px;
|
||||||
border: var(--border-in-light);
|
border: var(--border-in-light);
|
||||||
|
Loading…
Reference in New Issue
Block a user