mirror of
https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git
synced 2025-05-19 04:00:16 +09:00
Improve the Stability parameter control panel
This commit is contained in:
parent
34034be0e3
commit
bbbf59c74a
@ -1,6 +1,7 @@
|
||||
import * as React from "react";
|
||||
|
||||
import styles from "./button.module.scss";
|
||||
import { CSSProperties } from "react";
|
||||
|
||||
export type ButtonType = "primary" | "danger" | null;
|
||||
|
||||
@ -16,6 +17,7 @@ export function IconButton(props: {
|
||||
disabled?: boolean;
|
||||
tabIndex?: number;
|
||||
autoFocus?: boolean;
|
||||
style?: CSSProperties;
|
||||
}) {
|
||||
return (
|
||||
<button
|
||||
@ -31,6 +33,7 @@ export function IconButton(props: {
|
||||
role="button"
|
||||
tabIndex={props.tabIndex}
|
||||
autoFocus={props.autoFocus}
|
||||
style={props.style}
|
||||
>
|
||||
{props.icon && (
|
||||
<div
|
||||
|
@ -1,3 +0,0 @@
|
||||
export function SdList() {
|
||||
return <div>sd-list</div>;
|
||||
}
|
33
app/components/sd-panel.module.scss
Normal file
33
app/components/sd-panel.module.scss
Normal file
@ -0,0 +1,33 @@
|
||||
.ctrl-param-item {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
min-height: 40px;
|
||||
padding: 10px 0;
|
||||
animation: slide-in ease 0.6s;
|
||||
flex-direction: column;
|
||||
|
||||
.ctrl-param-item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.ctrl-param-item-title{
|
||||
font-size: 14px;
|
||||
font-weight: bolder;
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.ctrl-param-item-sub-title {
|
||||
font-size: 12px;
|
||||
font-weight: normal;
|
||||
margin-top: 3px;
|
||||
}
|
||||
}
|
||||
|
||||
.ai-models{
|
||||
button{
|
||||
margin-bottom: 10px;
|
||||
padding:10px;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
220
app/components/sd-panel.tsx
Normal file
220
app/components/sd-panel.tsx
Normal file
@ -0,0 +1,220 @@
|
||||
import styles from "./sd-panel.module.scss";
|
||||
import React, { useState } from "react";
|
||||
import { Select } from "@/app/components/ui-lib";
|
||||
import { IconButton } from "@/app/components/button";
|
||||
import locales from "@/app/locales";
|
||||
|
||||
const sdCommonParams = (model: string, data: any) => {
|
||||
return [
|
||||
{
|
||||
name: locales.SdPanel.Prompt,
|
||||
value: "prompt",
|
||||
type: "textarea",
|
||||
placeholder: locales.SdPanel.PleaseInput(locales.SdPanel.Prompt),
|
||||
required: true,
|
||||
},
|
||||
{
|
||||
name: locales.SdPanel.NegativePrompt,
|
||||
value: "negative_prompt",
|
||||
type: "textarea",
|
||||
placeholder: locales.SdPanel.PleaseInput(locales.SdPanel.NegativePrompt),
|
||||
},
|
||||
{
|
||||
name: locales.SdPanel.AspectRatio,
|
||||
value: "aspect_ratio",
|
||||
type: "select",
|
||||
default: "1:1",
|
||||
options: [
|
||||
{ name: "1:1", value: "1:1" },
|
||||
{ name: "2:2", value: "2:2" },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: locales.SdPanel.ImageStyle,
|
||||
value: "style",
|
||||
type: "select",
|
||||
default: "3d",
|
||||
support: ["core"],
|
||||
options: [{ name: "3D", value: "3d" }],
|
||||
},
|
||||
{ name: "Seed", value: "seed", type: "number", default: 0 },
|
||||
{
|
||||
name: locales.SdPanel.OutFormat,
|
||||
value: "output_format",
|
||||
type: "select",
|
||||
default: 0,
|
||||
options: [
|
||||
{ name: "PNG", value: "png" },
|
||||
{ name: "JPEG", value: "jpeg" },
|
||||
{ name: "WebP", value: "webp" },
|
||||
],
|
||||
},
|
||||
].filter((item) => {
|
||||
return !(item.support && !item.support.includes(model));
|
||||
});
|
||||
};
|
||||
|
||||
const models = [
|
||||
{
|
||||
name: "Stable Image Ultra",
|
||||
value: "ultra",
|
||||
params: (data: any) => sdCommonParams("ultra", data),
|
||||
},
|
||||
{
|
||||
name: "Stable Image Core",
|
||||
value: "core",
|
||||
params: (data: any) => sdCommonParams("core", data),
|
||||
},
|
||||
{
|
||||
name: "Stable Diffusion 3",
|
||||
value: "sd3",
|
||||
params: (data: any) => {
|
||||
return sdCommonParams("sd3", data);
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
export function ControlParamItem(props: {
|
||||
title: string;
|
||||
subTitle?: string;
|
||||
children?: JSX.Element | JSX.Element[];
|
||||
className?: string;
|
||||
}) {
|
||||
return (
|
||||
<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>
|
||||
</div>
|
||||
{props.children}
|
||||
{props.subTitle && (
|
||||
<div className={styles["ctrl-param-item-sub-title"]}>
|
||||
{props.subTitle}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function ControlParam(props: {
|
||||
columns: any[];
|
||||
data: any;
|
||||
set: React.Dispatch<React.SetStateAction<{}>>;
|
||||
}) {
|
||||
const handleValueChange = (field: string, val: any) => {
|
||||
props.set((prevParams) => ({
|
||||
...prevParams,
|
||||
[field]: val,
|
||||
}));
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
{props.columns.map((item) => {
|
||||
let element: null | JSX.Element;
|
||||
switch (item.type) {
|
||||
case "textarea":
|
||||
element = (
|
||||
<ControlParamItem title={item.name} subTitle={item.sub}>
|
||||
<textarea
|
||||
rows={item.rows || 3}
|
||||
style={{ maxWidth: "100%", width: "100%", padding: "10px" }}
|
||||
placeholder={item.placeholder}
|
||||
onChange={(e) => {
|
||||
handleValueChange(item.value, e.currentTarget.value);
|
||||
}}
|
||||
value={props.data[item.value]}
|
||||
></textarea>
|
||||
</ControlParamItem>
|
||||
);
|
||||
break;
|
||||
case "select":
|
||||
element = (
|
||||
<ControlParamItem title={item.name} subTitle={item.sub}>
|
||||
<Select
|
||||
value={props.data[item.value]}
|
||||
onChange={(e) => {
|
||||
handleValueChange(item.value, e.currentTarget.value);
|
||||
}}
|
||||
>
|
||||
{item.options.map((opt: any) => {
|
||||
return (
|
||||
<option value={opt.value} key={opt.value}>
|
||||
{opt.name}
|
||||
</option>
|
||||
);
|
||||
})}
|
||||
</Select>
|
||||
</ControlParamItem>
|
||||
);
|
||||
break;
|
||||
case "number":
|
||||
element = (
|
||||
<ControlParamItem title={item.name} subTitle={item.sub}>
|
||||
<input
|
||||
type="number"
|
||||
value={props.data[item.value]}
|
||||
onChange={(e) => {
|
||||
handleValueChange(item.value, e.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
</ControlParamItem>
|
||||
);
|
||||
break;
|
||||
default:
|
||||
element = (
|
||||
<ControlParamItem title={item.name} subTitle={item.sub}>
|
||||
<input
|
||||
type="text"
|
||||
value={props.data[item.value]}
|
||||
style={{ maxWidth: "100%", width: "100%" }}
|
||||
onChange={(e) => {
|
||||
handleValueChange(item.value, e.currentTarget.value);
|
||||
}}
|
||||
/>
|
||||
</ControlParamItem>
|
||||
);
|
||||
}
|
||||
return <div key={item.value}>{element}</div>;
|
||||
})}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export function SdPanel() {
|
||||
const [currentModel, setCurrentModel] = useState(models[0]);
|
||||
const [params, setParams] = useState({});
|
||||
return (
|
||||
<>
|
||||
<ControlParamItem title={locales.SdPanel.AIModel}>
|
||||
<div className={styles["ai-models"]}>
|
||||
{models.map((item) => {
|
||||
return (
|
||||
<IconButton
|
||||
text={item.name}
|
||||
key={item.value}
|
||||
type={currentModel.value == item.value ? "primary" : null}
|
||||
shadow
|
||||
onClick={() => {
|
||||
setCurrentModel(item);
|
||||
}}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</ControlParamItem>
|
||||
<ControlParam
|
||||
columns={currentModel.params(params) as any[]}
|
||||
set={setParams}
|
||||
data={params}
|
||||
></ControlParam>
|
||||
<IconButton
|
||||
text={locales.SdPanel.Submit}
|
||||
type="primary"
|
||||
style={{ marginTop: "20px" }}
|
||||
shadow
|
||||
></IconButton>
|
||||
</>
|
||||
);
|
||||
}
|
@ -37,7 +37,7 @@ const ChatList = dynamic(async () => (await import("./chat-list")).ChatList, {
|
||||
loading: () => null,
|
||||
});
|
||||
|
||||
const SdList = dynamic(async () => (await import("./sd-list")).SdList, {
|
||||
const SdPanel = dynamic(async () => (await import("./sd-panel")).SdPanel, {
|
||||
loading: () => null,
|
||||
});
|
||||
|
||||
@ -155,7 +155,7 @@ export function SideBar(props: { className?: string }) {
|
||||
let isChat: boolean = false;
|
||||
switch (location.pathname) {
|
||||
case Path.Sd:
|
||||
bodyComponent = <SdList />;
|
||||
bodyComponent = <SdPanel />;
|
||||
break;
|
||||
default:
|
||||
isChat = true;
|
||||
|
@ -61,6 +61,19 @@
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
&.vertical{
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
.list-header{
|
||||
.list-item-title{
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
.list-item-sub-title{
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.list {
|
||||
|
@ -48,10 +48,15 @@ export function ListItem(props: {
|
||||
icon?: JSX.Element;
|
||||
className?: string;
|
||||
onClick?: (event: MouseEvent) => void;
|
||||
vertical?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div
|
||||
className={styles["list-item"] + ` ${props.className || ""}`}
|
||||
className={
|
||||
styles["list-item"] +
|
||||
` ${props.vertical ? styles["vertical"] : ""} ` +
|
||||
` ${props.className || ""}`
|
||||
}
|
||||
onClick={props.onClick}
|
||||
>
|
||||
<div className={styles["list-header"]}>
|
||||
|
@ -484,6 +484,16 @@ const cn = {
|
||||
Topic: "主题",
|
||||
Time: "时间",
|
||||
},
|
||||
SdPanel: {
|
||||
Prompt: "画面提示",
|
||||
NegativePrompt: "否定提示",
|
||||
PleaseInput: (name: string) => `请输入${name}`,
|
||||
AspectRatio: "横纵比",
|
||||
ImageStyle: "图像风格",
|
||||
OutFormat: "输出格式",
|
||||
AIModel: "AI模型",
|
||||
Submit: "提交生成",
|
||||
},
|
||||
};
|
||||
|
||||
type DeepPartial<T> = T extends object
|
||||
|
@ -486,11 +486,20 @@ const en: LocaleType = {
|
||||
Topic: "Topic",
|
||||
Time: "Time",
|
||||
},
|
||||
|
||||
URLCommand: {
|
||||
Code: "Detected access code from url, confirm to apply? ",
|
||||
Settings: "Detected settings from url, confirm to apply?",
|
||||
},
|
||||
SdPanel: {
|
||||
Prompt: "Prompt",
|
||||
NegativePrompt: "Negative Prompt",
|
||||
PleaseInput: (name: string) => `Please input ${name}`,
|
||||
AspectRatio: "Aspect Ratio",
|
||||
ImageStyle: "Image Style",
|
||||
OutFormat: "Output Format",
|
||||
AIModel: "AI Model",
|
||||
Submit: "Submit",
|
||||
},
|
||||
};
|
||||
|
||||
export default en;
|
||||
|
@ -226,7 +226,8 @@ input[type="range"]::-ms-thumb:hover {
|
||||
|
||||
input[type="number"],
|
||||
input[type="text"],
|
||||
input[type="password"] {
|
||||
input[type="password"],
|
||||
textarea {
|
||||
appearance: none;
|
||||
border-radius: 10px;
|
||||
border: var(--border-in-light);
|
||||
|
Loading…
Reference in New Issue
Block a user