Skip to content

Commit

Permalink
Integration with SiliconFlow (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
jackalcooper committed Feb 12, 2025
1 parent 48cd4b1 commit 54262fe
Show file tree
Hide file tree
Showing 32 changed files with 1,025 additions and 98 deletions.
38 changes: 38 additions & 0 deletions .github/workflows/auto-rebase.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Auto Rebase

on:
schedule:
- cron: '0 * * * *' # This will run every hour
push:
branches:
- main # Change this to the branch you want to watch for updates
workflow_dispatch: # Allows manual triggering of the action

permissions:
id-token: write
contents: write

jobs:
rebase:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
fetch-depth: 0 # Fetch all history for all branches and tags

- name: Add upstream remote
run: git remote add upstream https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web.git

- name: Fetch upstream changes
run: git fetch upstream

- name: Rebase branch
run: |
git config --global user.email [email protected]
git config --global user.name tsai
git checkout main
git rebase upstream/main
- name: Push changes
run: git push origin main --force
38 changes: 20 additions & 18 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,18 @@ on:
workflow_dispatch:
release:
types: [published]
push:
branches:
- main
env:
REGION_ID: cn-beijing
REGISTRY: registry.cn-beijing.aliyuncs.com
NAMESPACE: oneflow

concurrency:
group: sf-next-chat-${{ github.ref }}
cancel-in-progress: true

jobs:
push_to_registry:
name: Push Docker image to Docker Hub
Expand All @@ -13,23 +24,13 @@ jobs:
-
name: Check out the repo
uses: actions/checkout@v3
-
name: Log in to Docker Hub
uses: docker/login-action@v2
- name: Login to ACR with the AccessKey pair
uses: aliyun/acr-login@v1
with:
username: ${{ secrets.DOCKER_USERNAME }}
password: ${{ secrets.DOCKER_PASSWORD }}

-
name: Extract metadata (tags, labels) for Docker
id: meta
uses: docker/metadata-action@v4
with:
images: yidadaa/chatgpt-next-web
tags: |
type=raw,value=latest
type=ref,event=tag
login-server: https://registry.${{env.REGION_ID}}.aliyuncs.com
username: "${{ secrets.ACR_USERNAME }}"
password: "${{ secrets.ACR_PASSWORD }}"

-
name: Set up QEMU
uses: docker/setup-qemu-action@v2
Expand All @@ -45,8 +46,9 @@ jobs:
context: .
platforms: linux/amd64,linux/arm64
push: true
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
build-args: |
NEXT_PUBLIC_SF_NEXT_CHAT_CLIENT_ID=${{ vars.NEXT_PUBLIC_SF_NEXT_CHAT_CLIENT_ID }}
tags: ${{ env.REGISTRY }}/${{ env.NAMESPACE }}/next-chat-sf
cache-from: type=gha
cache-to: type=gha,mode=max

1 change: 1 addition & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ RUN yarn config set registry 'https://registry.npmmirror.com/'
RUN yarn install

FROM base AS builder
ARG NEXT_PUBLIC_SF_NEXT_CHAT_CLIENT_ID

RUN apk update && apk add --no-cache git

Expand Down
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ For enterprise inquiries, please contact: **[email protected]**
- I18n: English, 简体中文, 繁体中文, 日本語, Français, Español, Italiano, Türkçe, Deutsch, Tiếng Việt, Русский, Čeština, 한국어, Indonesia

<div align="center">

![主界面](./docs/images/cover.png)

</div>
Expand All @@ -111,7 +111,7 @@ For enterprise inquiries, please contact: **[email protected]**
- 🚀 v2.15.8 Now supports Realtime Chat [#5672](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5672)
- 🚀 v2.15.4 The Application supports using Tauri fetch LLM API, MORE SECURITY! [#5379](https://github.com/ChatGPTNextWeb/ChatGPT-Next-Web/issues/5379)
- 🚀 v2.15.0 Now supports Plugins! Read this: [NextChat-Awesome-Plugins](https://github.com/ChatGPTNextWeb/NextChat-Awesome-Plugins)
- 🚀 v2.14.0 Now supports Artifacts & SD
- 🚀 v2.14.0 Now supports Artifacts & SD
- 🚀 v2.10.1 support Google Gemini Pro model.
- 🚀 v2.9.11 you can use azure endpoint now.
- 🚀 v2.8 now we have a client that runs across all platforms!
Expand All @@ -122,7 +122,7 @@ For enterprise inquiries, please contact: **[email protected]**

1. Get [OpenAI API Key](https://platform.openai.com/account/api-keys);
2. Click
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.jparrowsec.cn%2FYidadaa%2FChatGPT-Next-Web&env=OPENAI_API_KEY&env=CODE&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web), remember that `CODE` is your page password;
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/clone?repository-url=https%3A%2F%2Fgithub.jparrowsec.cn%2Fsiliconflow%2FChatGPT-Next-Web&env=NEXT_PUBLIC_SF_NEXT_CHAT_CLIENT_ID&env=SF_NEXT_CHAT_SECRET&project-name=chatgpt-next-web&repository-name=ChatGPT-Next-Web), remember that `CODE` is your page password;
3. Enjoy :)

## FAQ
Expand Down Expand Up @@ -331,7 +331,7 @@ Add additional models to have vision capabilities, beyond the default pattern ma
### `WHITE_WEBDAV_ENDPOINTS` (optional)

You can use this option if you want to increase the number of webdav service addresses you are allowed to access, as required by the format:
- Each address must be a complete endpoint
- Each address must be a complete endpoint
> `https://xxxx/yyy`
- Multiple addresses are connected by ', '

Expand Down
3 changes: 3 additions & 0 deletions app/api/[provider]/[...path]/route.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { handle as oauthHandler } from "../../oauth_callback";
import { ApiPath } from "@/app/constant";
import { NextRequest } from "next/server";
import { handle as openaiHandler } from "../../openai";
Expand All @@ -23,6 +24,8 @@ async function handle(
const apiPath = `/api/${params.provider}`;
console.log(`[${params.provider} Route] params `, params);
switch (apiPath) {
case ApiPath.OAuth:
return oauthHandler(req, { params });
case ApiPath.Azure:
return azureHandler(req, { params });
case ApiPath.Google:
Expand Down
66 changes: 66 additions & 0 deletions app/api/oauth_callback.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { NextRequest, NextResponse } from "next/server";
import { redirect } from "next/navigation";
import { cookies } from "next/headers";

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

if (req.method === "OPTIONS") {
return NextResponse.json({ body: "OK" }, { status: 200 });
}
const url = new URL(req.url);
const queryParams = new URLSearchParams(url.search);
const code = queryParams.get("code");
let sfak = "";
console.log("[SF] code ", code);
try {
const tokenFetch = await fetch(
`${
process.env.NEXT_PUBLIC_SF_NEXT_CHAT_SF_ACCOUNT_ENDPOINT ||
"https://account.siliconflow.cn"
}/api/open/oauth`,
{
method: "POST",
body: JSON.stringify({
clientId: process.env.NEXT_PUBLIC_SF_NEXT_CHAT_CLIENT_ID,
secret: process.env.SF_NEXT_CHAT_SECRET,
code,
}),
},
);
if (!tokenFetch.ok)
return Response.json(
{ status: false, message: "fetch error" },
{ status: 500 },
);
const tokenJson = await tokenFetch.json();
const access_token = tokenJson.status ? tokenJson.data?.access_token : null;
console.log("access_token", access_token);
const apiKey = await fetch(
`${
process.env.NEXT_PUBLIC_SF_NEXT_CHAT_SF_CLOUD_ENDPOINT ||
"https://cloud.siliconflow.cn"
}/api/oauth/apikeys`,
{
method: "POST",
headers: {
Authorization: `token ${access_token}`,
},
},
);
const apiKeysData = await apiKey.json();
console.log("apiKeysData", apiKeysData);
sfak = apiKeysData.data[0].secretKey;
} catch (error) {
console.log("error", error);
return Response.json(
{ status: false, message: "fetch error" },
{ status: 500 },
);
}
cookies().set("sfak", sfak);
redirect(`/#chat`);
}
5 changes: 3 additions & 2 deletions app/client/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
useAccessStore,
useChatStore,
} from "../store";
import { ChatGPTApi, DalleRequestPayload } from "./platforms/openai";
import { DalleRequestPayload } from "./platforms/openai";
import { GeminiProApi } from "./platforms/google";
import { ClaudeApi } from "./platforms/anthropic";
import { ErnieApi } from "./platforms/baidu";
Expand Down Expand Up @@ -169,7 +169,8 @@ export class ClientApi {
this.llm = new SiliconflowApi();
break;
default:
this.llm = new ChatGPTApi();
this.llm = new SiliconflowApi();
// this.llm = new ChatGPTApi();
}
}

Expand Down
2 changes: 2 additions & 0 deletions app/components/auth.module.scss
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,8 @@

.auth-tips {
font-size: 14px;
margin-top: 1vh;
margin-bottom: 1vh;
}

.auth-input {
Expand Down
46 changes: 43 additions & 3 deletions app/components/auth.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import styles from "./auth.module.scss";
import { IconButton } from "./button";
import { useState, useEffect } from "react";
import { useNavigate } from "react-router-dom";
import { Path, SAAS_CHAT_URL } from "../constant";
import { Path, SAAS_CHAT_URL, SiliconFlow } from "../constant";
import { useAccessStore } from "../store";
import Locale from "../locales";
import Delete from "../icons/close.svg";
Expand All @@ -22,6 +22,21 @@ import clsx from "clsx";

const storage = safeLocalStorage();

function AuthBtn(props: { disabled?: boolean }) {
return (
<IconButton
type={"primary"}
text={"使用 SiliconFlow 账号登录"}
onClick={() => {
window.location.href = `${
process.env.NEXT_PUBLIC_SF_NEXT_CHAT_SF_ACCOUNT_ENDPOINT ||
"https://account.siliconflow.cn"
}/oauth?client_id=${process.env.NEXT_PUBLIC_SF_NEXT_CHAT_CLIENT_ID}`;
}}
disabled={props.disabled}
/>
);
}
export function AuthPage() {
const navigate = useNavigate();
const accessStore = useAccessStore();
Expand All @@ -38,14 +53,39 @@ export function AuthPage() {
access.accessCode = "";
});
}; // Reset access code to empty string

useEffect(() => {
if (getClientConfig()?.isApp) {
navigate(Path.Settings);
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

useEffect(() => {
window.location.href = `${
process.env.NEXT_PUBLIC_SF_NEXT_CHAT_SF_ACCOUNT_ENDPOINT ||
"https://account.siliconflow.cn"
}/oauth?client_id=${process.env.NEXT_PUBLIC_SF_NEXT_CHAT_CLIENT_ID}`;
});

return (
<div className={styles["auth-page"]}>
<div className={styles["auth-header"]}></div>
<div className={styles["auth-title"]}>{"欢迎使用 SiliconChat"}</div>
<ul>
<li className={styles["auth-tips"]}>请先完成注册登录</li>
<li className={styles["auth-tips"]}>
如需体验「Pro」或「深度思考」请完成{" "}
<a href={SiliconFlow.VerificationPath}>实名认证</a>
{" 并确保 "}
<a href={SiliconFlow.BillPath}>充值余额</a>
{" 充足"}
</li>
</ul>
<div className={styles["auth-actions"]}>
<AuthBtn />
</div>
</div>
);
return (
<div className={styles["auth-page"]}>
<TopBanner></TopBanner>
Expand All @@ -62,7 +102,6 @@ export function AuthPage() {

<div className={styles["auth-title"]}>{Locale.Auth.Title}</div>
<div className={styles["auth-tips"]}>{Locale.Auth.Tips}</div>

<PasswordInput
style={{ marginTop: "3vh", marginBottom: "3vh" }}
aria={Locale.Settings.ShowPassword}
Expand Down Expand Up @@ -131,6 +170,7 @@ function TopBanner() {
const [isVisible, setIsVisible] = useState(true);
const isMobile = useMobileScreen();
useEffect(() => {
storage.setItem("bannerDismissed", "true");
// 检查 localStorage 中是否有标记
const bannerDismissed = storage.getItem("bannerDismissed");
// 如果标记不存在,存储默认值并显示横幅
Expand Down
4 changes: 4 additions & 0 deletions app/components/button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,11 @@ export function IconButton(props: {
autoFocus?: boolean;
style?: CSSProperties;
aria?: string;
hidden?: boolean;
}) {
if (props.hidden) {
return <></>;
}
return (
<button
className={clsx(
Expand Down
Loading

0 comments on commit 54262fe

Please sign in to comment.