Skip to content

Commit 825929f

Browse files
committed
merge main
2 parents d8afd1a + 941a03e commit 825929f

35 files changed

+1707
-142
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,3 +44,5 @@ dev
4444

4545
*.key
4646
*.key.pub
47+
48+
masks.json

README.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -326,6 +326,14 @@ You can use this option if you want to increase the number of webdav service add
326326

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

329+
### `STABILITY_API_KEY` (optional)
330+
331+
Stability API key.
332+
333+
### `STABILITY_URL` (optional)
334+
335+
Customize Stability API url.
336+
329337
## Requirements
330338

331339
NodeJS >= 18, Docker >= 20

README_CN.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,15 @@ ByteDance Api Url.
218218

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

221+
### `STABILITY_API_KEY` (optional)
222+
223+
Stability API密钥
224+
225+
### `STABILITY_URL` (optional)
226+
227+
自定义的Stability API请求地址
228+
229+
221230
## 开发
222231

223232
点击下方按钮,开始二次开发:

app/api/auth.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,9 @@ export function auth(req: NextRequest, modelProvider: ModelProvider) {
6767
let systemApiKey: string | undefined;
6868

6969
switch (modelProvider) {
70+
case ModelProvider.Stability:
71+
systemApiKey = serverConfig.stabilityApiKey;
72+
break;
7073
case ModelProvider.GeminiPro:
7174
systemApiKey = serverConfig.googleApiKey;
7275
break;
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import { NextRequest, NextResponse } from "next/server";
2+
import { getServerSideConfig } from "@/app/config/server";
3+
import { ModelProvider, STABILITY_BASE_URL } from "@/app/constant";
4+
import { auth } from "@/app/api/auth";
5+
6+
async function handle(
7+
req: NextRequest,
8+
{ params }: { params: { path: string[] } },
9+
) {
10+
console.log("[Stability] params ", params);
11+
12+
if (req.method === "OPTIONS") {
13+
return NextResponse.json({ body: "OK" }, { status: 200 });
14+
}
15+
16+
const controller = new AbortController();
17+
18+
const serverConfig = getServerSideConfig();
19+
20+
let baseUrl = serverConfig.stabilityUrl || STABILITY_BASE_URL;
21+
22+
if (!baseUrl.startsWith("http")) {
23+
baseUrl = `https://${baseUrl}`;
24+
}
25+
26+
if (baseUrl.endsWith("/")) {
27+
baseUrl = baseUrl.slice(0, -1);
28+
}
29+
30+
let path = `${req.nextUrl.pathname}`.replaceAll("/api/stability/", "");
31+
32+
console.log("[Stability Proxy] ", path);
33+
console.log("[Stability Base Url]", baseUrl);
34+
35+
const timeoutId = setTimeout(
36+
() => {
37+
controller.abort();
38+
},
39+
10 * 60 * 1000,
40+
);
41+
42+
const authResult = auth(req, ModelProvider.Stability);
43+
44+
if (authResult.error) {
45+
return NextResponse.json(authResult, {
46+
status: 401,
47+
});
48+
}
49+
50+
const bearToken = req.headers.get("Authorization") ?? "";
51+
const token = bearToken.trim().replaceAll("Bearer ", "").trim();
52+
53+
const key = token ? token : serverConfig.stabilityApiKey;
54+
55+
if (!key) {
56+
return NextResponse.json(
57+
{
58+
error: true,
59+
message: `missing STABILITY_API_KEY in server env vars`,
60+
},
61+
{
62+
status: 401,
63+
},
64+
);
65+
}
66+
67+
const fetchUrl = `${baseUrl}/${path}`;
68+
console.log("[Stability Url] ", fetchUrl);
69+
const fetchOptions: RequestInit = {
70+
headers: {
71+
"Content-Type": req.headers.get("Content-Type") || "multipart/form-data",
72+
Accept: req.headers.get("Accept") || "application/json",
73+
Authorization: `Bearer ${key}`,
74+
},
75+
method: req.method,
76+
body: req.body,
77+
// to fix #2485: https://stackoverflow.com/questions/55920957/cloudflare-worker-typeerror-one-time-use-body
78+
redirect: "manual",
79+
// @ts-ignore
80+
duplex: "half",
81+
signal: controller.signal,
82+
};
83+
84+
try {
85+
const res = await fetch(fetchUrl, fetchOptions);
86+
// to prevent browser prompt for credentials
87+
const newHeaders = new Headers(res.headers);
88+
newHeaders.delete("www-authenticate");
89+
// to disable nginx buffering
90+
newHeaders.set("X-Accel-Buffering", "no");
91+
return new Response(res.body, {
92+
status: res.status,
93+
statusText: res.statusText,
94+
headers: newHeaders,
95+
});
96+
} finally {
97+
clearTimeout(timeoutId);
98+
}
99+
}
100+
101+
export const GET = handle;
102+
export const POST = handle;
103+
104+
export const runtime = "edge";

app/api/webdav/[...path]/route.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,9 +37,13 @@ async function handle(
3737
const normalizedAllowedEndpoint = normalizeUrl(allowedEndpoint);
3838
const normalizedEndpoint = normalizeUrl(endpoint as string);
3939

40-
return normalizedEndpoint &&
40+
return (
41+
normalizedEndpoint &&
4142
normalizedEndpoint.hostname === normalizedAllowedEndpoint?.hostname &&
42-
normalizedEndpoint.pathname.startsWith(normalizedAllowedEndpoint.pathname);
43+
normalizedEndpoint.pathname.startsWith(
44+
normalizedAllowedEndpoint.pathname,
45+
)
46+
);
4347
})
4448
) {
4549
return NextResponse.json(

app/client/api.ts

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -168,6 +168,19 @@ export class ClientApi {
168168
}
169169
}
170170

171+
export function getBearerToken(
172+
apiKey: string,
173+
noBearer: boolean = false,
174+
): string {
175+
return validString(apiKey)
176+
? `${noBearer ? "" : "Bearer "}${apiKey.trim()}`
177+
: "";
178+
}
179+
180+
export function validString(x: string): boolean {
181+
return x?.length > 0;
182+
}
183+
171184
export function getHeaders() {
172185
const accessStore = useAccessStore.getState();
173186
const chatStore = useChatStore.getState();
@@ -214,15 +227,6 @@ export function getHeaders() {
214227
return isAzure ? "api-key" : isAnthropic ? "x-api-key" : "Authorization";
215228
}
216229

217-
function getBearerToken(apiKey: string, noBearer: boolean = false): string {
218-
return validString(apiKey)
219-
? `${noBearer ? "" : "Bearer "}${apiKey.trim()}`
220-
: "";
221-
}
222-
223-
function validString(x: string): boolean {
224-
return x?.length > 0;
225-
}
226230
const {
227231
isGoogle,
228232
isAzure,

app/components/button.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as React from "react";
22

33
import styles from "./button.module.scss";
4+
import { CSSProperties } from "react";
45

56
export type ButtonType = "primary" | "danger" | null;
67

@@ -16,6 +17,7 @@ export function IconButton(props: {
1617
disabled?: boolean;
1718
tabIndex?: number;
1819
autoFocus?: boolean;
20+
style?: CSSProperties;
1921
}) {
2022
return (
2123
<button
@@ -31,6 +33,7 @@ export function IconButton(props: {
3133
role="button"
3234
tabIndex={props.tabIndex}
3335
autoFocus={props.autoFocus}
36+
style={props.style}
3437
>
3538
{props.icon && (
3639
<div

app/components/chat.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ function ClearContextDivider() {
340340
);
341341
}
342342

343-
function ChatAction(props: {
343+
export function ChatAction(props: {
344344
text: string;
345345
icon: JSX.Element;
346346
onClick: () => void;

app/components/error.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
"use client";
2+
13
import React from "react";
24
import { IconButton } from "./button";
35
import GithubIcon from "../icons/github.svg";

0 commit comments

Comments
 (0)