Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature] Stable Diffusion #4983

Merged
merged 41 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
a51fb24
fix ts error
Licoy Jun 27, 2024
fa6ebad
feat: add plugin entry selection
Licoy Jun 27, 2024
d214811
feat: add SD page switching
Licoy Jun 27, 2024
34034be
hide new chat button on sd page
Licoy Jun 27, 2024
bbbf59c
Improve the Stability parameter control panel
Licoy Jul 2, 2024
7fde932
feat: Improve the data input and submission acquisition of SD paramet…
Licoy Jul 2, 2024
5440116
fix: model version field name
Licoy Jul 2, 2024
a16725a
feat: Improve SD list data and API integration
Licoy Jul 3, 2024
2b01538
feat: Add Stability API server relay sending
Licoy Jul 9, 2024
498d0f0
merge main
lloydzhou Jul 11, 2024
17cc928
add config in readme
lloydzhou Jul 11, 2024
01ea690
remove code
lloydzhou Jul 11, 2024
74b915a
fix: sd3 model default select
Licoy Jul 12, 2024
a7ceb61
pref: remove console
Licoy Jul 12, 2024
d61cb98
Merge remote-tracking branch 'origin/dev-sd' into dev-sd
Licoy Jul 12, 2024
dd10301
fix: sd image preview modal size
Licoy Jul 12, 2024
3767b2c
test
Licoy Jul 15, 2024
e2f0206
update using indexdb read sd image data
lloydzhou Jul 15, 2024
33450ce
move code to utils/file
lloydzhou Jul 15, 2024
5df09d5
move code to utils/file
lloydzhou Jul 15, 2024
6ece818
remove no need code
lloydzhou Jul 15, 2024
a1117cd
save blob to indexeddb instead of base64 image string
lloydzhou Jul 15, 2024
b3a324b
fixed typescript error
lloydzhou Jul 15, 2024
bab3e0b
using CacheStorage to store image #5013
lloydzhou Jul 15, 2024
94bc880
fixed typescript error
lloydzhou Jul 15, 2024
5267ad4
add header for service worker upload api
lloydzhou Jul 16, 2024
4b84fb3
Merge pull request #2 from OpenAI-Next/dev-sd-test
lloydzhou Jul 16, 2024
eb7c7cd
更新serviceworker逻辑
lloydzhou Jul 16, 2024
49151da
hotfix
lloydzhou Jul 16, 2024
fc31d8e
merge origin/main
lloydzhou Jul 20, 2024
e468fec
update
lloydzhou Jul 20, 2024
9d55adb
refator: sd
Dogtiti Jul 22, 2024
6b98b14
fix: sd mobile
Dogtiti Jul 23, 2024
82e6fd7
feat: move sd config to store
Dogtiti Jul 23, 2024
908ee00
chore: remove sd new
Dogtiti Jul 23, 2024
3935c72
feat: sd setting
Dogtiti Jul 23, 2024
2a1c05a
fix: bugs
Dogtiti Jul 24, 2024
fd441d9
feat: discovery icon
Dogtiti Jul 24, 2024
8f6e5d7
hotfix: can send sd task in client
lloydzhou Jul 25, 2024
8f14de5
hotfix: ts check
lloydzhou Jul 25, 2024
6cc0a5a
remove code
lloydzhou Jul 25, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -44,3 +44,5 @@ dev

*.key
*.key.pub

masks.json
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -326,6 +326,14 @@ You can use this option if you want to increase the number of webdav service add

Customize the default template used to initialize the User Input Preprocessing configuration item in Settings.

### `STABILITY_API_KEY` (optional)

Stability API key.

### `STABILITY_URL` (optional)

Customize Stability API url.

## Requirements

NodeJS >= 18, Docker >= 20
Expand Down
9 changes: 9 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -218,6 +218,15 @@ ByteDance Api Url.

自定义默认的 template,用于初始化『设置』中的『用户输入预处理』配置项

### `STABILITY_API_KEY` (optional)

Stability API密钥

### `STABILITY_URL` (optional)

自定义的Stability API请求地址


## 开发

点击下方按钮,开始二次开发:
Expand Down
3 changes: 3 additions & 0 deletions app/api/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
let systemApiKey: string | undefined;

switch (modelProvider) {
case ModelProvider.Stability:
systemApiKey = serverConfig.stabilityApiKey;
break;
case ModelProvider.GeminiPro:
systemApiKey = serverConfig.googleApiKey;
break;
Expand Down
104 changes: 104 additions & 0 deletions app/api/stability/[...path]/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import { NextRequest, NextResponse } from "next/server";
import { getServerSideConfig } from "@/app/config/server";
import { ModelProvider, STABILITY_BASE_URL } from "@/app/constant";
import { auth } from "@/app/api/auth";

async function handle(
req: NextRequest,
{ params }: { params: { path: string[] } },
) {
console.log("[Stability] params ", params);

if (req.method === "OPTIONS") {
return NextResponse.json({ body: "OK" }, { status: 200 });
}

const controller = new AbortController();

const serverConfig = getServerSideConfig();

let baseUrl = serverConfig.stabilityUrl || STABILITY_BASE_URL;

if (!baseUrl.startsWith("http")) {
baseUrl = `https://${baseUrl}`;
}

if (baseUrl.endsWith("/")) {
baseUrl = baseUrl.slice(0, -1);
}

let path = `${req.nextUrl.pathname}`.replaceAll("/api/stability/", "");

console.log("[Stability Proxy] ", path);
console.log("[Stability Base Url]", baseUrl);

const timeoutId = setTimeout(
() => {
controller.abort();
},
10 * 60 * 1000,
);

const authResult = auth(req, ModelProvider.Stability);

if (authResult.error) {
return NextResponse.json(authResult, {
status: 401,
});
}

const bearToken = req.headers.get("Authorization") ?? "";
const token = bearToken.trim().replaceAll("Bearer ", "").trim();

const key = token ? token : serverConfig.stabilityApiKey;

if (!key) {
return NextResponse.json(
{
error: true,
message: `missing STABILITY_API_KEY in server env vars`,
},
{
status: 401,
},
);
}

const fetchUrl = `${baseUrl}/${path}`;
console.log("[Stability Url] ", fetchUrl);
const fetchOptions: RequestInit = {
headers: {
"Content-Type": req.headers.get("Content-Type") || "multipart/form-data",
Accept: req.headers.get("Accept") || "application/json",
Authorization: `Bearer ${key}`,
},
method: req.method,
body: req.body,
// to fix #2485: https://stackoverflow.com/questions/55920957/cloudflare-worker-typeerror-one-time-use-body
redirect: "manual",
// @ts-ignore
duplex: "half",
signal: controller.signal,
};

try {
const res = await fetch(fetchUrl, fetchOptions);
// to prevent browser prompt for credentials
const newHeaders = new Headers(res.headers);
newHeaders.delete("www-authenticate");
// to disable nginx buffering
newHeaders.set("X-Accel-Buffering", "no");
return new Response(res.body, {
status: res.status,
statusText: res.statusText,
headers: newHeaders,
});
} finally {
clearTimeout(timeoutId);
}
}

export const GET = handle;
export const POST = handle;

export const runtime = "edge";
8 changes: 6 additions & 2 deletions app/api/webdav/[...path]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,13 @@ async function handle(
const normalizedAllowedEndpoint = normalizeUrl(allowedEndpoint);
const normalizedEndpoint = normalizeUrl(endpoint as string);

return normalizedEndpoint &&
return (
normalizedEndpoint &&
normalizedEndpoint.hostname === normalizedAllowedEndpoint?.hostname &&
normalizedEndpoint.pathname.startsWith(normalizedAllowedEndpoint.pathname);
normalizedEndpoint.pathname.startsWith(
normalizedAllowedEndpoint.pathname,
)
);
})
) {
return NextResponse.json(
Expand Down
22 changes: 13 additions & 9 deletions app/client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,19 @@ export class ClientApi {
}
}

export function getBearerToken(
apiKey: string,
noBearer: boolean = false,
): string {
return validString(apiKey)
? `${noBearer ? "" : "Bearer "}${apiKey.trim()}`
: "";
}

export function validString(x: string): boolean {
return x?.length > 0;
}

export function getHeaders() {
const accessStore = useAccessStore.getState();
const chatStore = useChatStore.getState();
Expand Down Expand Up @@ -214,15 +227,6 @@ export function getHeaders() {
return isAzure ? "api-key" : isAnthropic ? "x-api-key" : "Authorization";
}

function getBearerToken(apiKey: string, noBearer: boolean = false): string {
return validString(apiKey)
? `${noBearer ? "" : "Bearer "}${apiKey.trim()}`
: "";
}

function validString(x: string): boolean {
return x?.length > 0;
}
const {
isGoogle,
isAzure,
Expand Down
3 changes: 3 additions & 0 deletions app/components/button.tsx
Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -16,6 +17,7 @@ export function IconButton(props: {
disabled?: boolean;
tabIndex?: number;
autoFocus?: boolean;
style?: CSSProperties;
}) {
return (
<button
Expand All @@ -31,6 +33,7 @@ export function IconButton(props: {
role="button"
tabIndex={props.tabIndex}
autoFocus={props.autoFocus}
style={props.style}
>
{props.icon && (
<div
Expand Down
9 changes: 8 additions & 1 deletion app/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import AutoIcon from "../icons/auto.svg";
import BottomIcon from "../icons/bottom.svg";
import StopIcon from "../icons/pause.svg";
import RobotIcon from "../icons/robot.svg";
import PluginIcon from "../icons/plugin.svg";

import {
ChatMessage,
Expand Down Expand Up @@ -338,7 +339,7 @@ function ClearContextDivider() {
);
}

function ChatAction(props: {
export function ChatAction(props: {
text: string;
icon: JSX.Element;
onClick: () => void;
Expand Down Expand Up @@ -587,6 +588,12 @@ export function ChatActions(props: {
icon={<RobotIcon />}
/>

<ChatAction
onClick={() => showToast(Locale.WIP)}
text={Locale.Plugin.Name}
icon={<PluginIcon />}
/>

{showModelSelector && (
<Selector
defaultSelectedValue={`${currentModel}@${currentProviderName}`}
Expand Down
2 changes: 2 additions & 0 deletions app/components/error.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
"use client";

import React from "react";
import { IconButton } from "./button";
import GithubIcon from "../icons/github.svg";
Expand Down
64 changes: 39 additions & 25 deletions app/components/home.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,10 @@ const MaskPage = dynamic(async () => (await import("./mask")).MaskPage, {
loading: () => <Loading noLogo />,
});

const Sd = dynamic(async () => (await import("./sd")).Sd, {
loading: () => <Loading noLogo />,
});

export function useSwitchTheme() {
const config = useAppConfig();

Expand Down Expand Up @@ -122,11 +126,22 @@ const loadAsyncGoogleFont = () => {
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() {
const config = useAppConfig();
const location = useLocation();
const isHome = location.pathname === Path.Home;
const isAuth = location.pathname === Path.Auth;
const isSd = location.pathname === Path.Sd;
const isSdNew = location.pathname === Path.SdNew;

const isMobileScreen = useMobileScreen();
const shouldTightBorder =
getClientConfig()?.isApp || (config.tightBorder && !isMobileScreen);
Expand All @@ -135,34 +150,33 @@ function Screen() {
loadAsyncGoogleFont();
}, []);

const renderContent = () => {
if (isAuth) return <AuthPage />;
if (isSd) return <Sd />;
if (isSdNew) return <Sd />;
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.Settings} element={<Settings />} />
</Routes>
</WindowContent>
</>
);
};

return (
<div
className={
styles.container +
` ${shouldTightBorder ? styles["tight-container"] : styles.container} ${
getLang() === "ar" ? styles["rtl-screen"] : ""
}`
}
className={`${styles.container} ${
shouldTightBorder ? styles["tight-container"] : styles.container
} ${getLang() === "ar" ? styles["rtl-screen"] : ""}`}
>
{isAuth ? (
<>
<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.Settings} element={<Settings />} />
</Routes>
</div>
</>
)}
{renderContent()}
</div>
);
}
Expand Down
2 changes: 2 additions & 0 deletions app/components/sd/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./sd";
export * from "./sd-panel";
Loading
Loading