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 Jan 13, 2025
1 parent 93652db commit 4595275
Show file tree
Hide file tree
Showing 12 changed files with 782 additions and 6 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
8 changes: 4 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,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 @@ -97,7 +97,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 @@ -108,7 +108,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.com%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.com%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 @@ -317,7 +317,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 @@ -22,6 +23,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(`/`);
}
60 changes: 60 additions & 0 deletions app/components/chat.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ import {
DEFAULT_TOPIC,
ModelType,
usePluginStore,
DEFAULT_CONFIG,
} from "../store";

import {
Expand Down Expand Up @@ -116,6 +117,7 @@ import { ExportMessageModal } from "./exporter";
import { getClientConfig } from "../config/client";
import { useAllModels } from "../utils/hooks";
import { MultimodalContent } from "../client/api";
import { useCookies } from "react-cookie";

import { ClientApi } from "../client/api";
import { createTTSPlayer } from "../utils/audio";
Expand Down Expand Up @@ -1293,6 +1295,64 @@ function _Chat() {
return session.mask.hideContext ? [] : session.mask.context.slice();
}, [session.mask.context, session.mask.hideContext]);

const [cookies, setCookie, removeCookie] = useCookies(["sfak"], {
doNotParse: true,
});

type ModelInfo = { id: string };
const updateCustomModels = async (apiURL: String, token: String) => {
const options = {
method: "GET",
headers: { Authorization: `Bearer ${token}` },
};
const res = await fetch(
`${apiURL}/v1/models?type=text&sub_type=chat`,
options,
).then((res) => res.json());
const models = res.data as ModelInfo[];
config.update((c) => {
c.customModels = models
.map((m) => m.id)
.sort((a, b) => a.toLowerCase().localeCompare(b.toLowerCase()))
.join(",");
});
};
const updateDefaultModelConfig = (modelConfig: {
model: any;
providerName: any;
}) => {
const FeaturedModel = "Qwen/Qwen2-7B-Instruct";
if (modelConfig.model == DEFAULT_CONFIG.modelConfig.model) {
modelConfig.model = FeaturedModel as ModelType;
modelConfig.providerName = FeaturedModel as ServiceProvider;
}
};
useEffect(() => {
config.update((config) => {
if (accessStore.openaiApiKey) {
updateCustomModels(accessStore.openaiUrl, accessStore.openaiApiKey);
}
updateDefaultModelConfig(config.modelConfig);
});
const sfakValue = cookies.sfak;
if (sfakValue) {
const apiURL =
process.env.NEXT_PUBLIC_SF_NEXT_CHAT_SF_API_ENDPOINT ||
"https://api.siliconflow.cn";
chatStore.updateTargetSession(session, (session) => {
updateDefaultModelConfig(session.mask.modelConfig);
});
updateCustomModels(apiURL, sfakValue);
accessStore.update((access) => {
console.log("update access store with SF API key");
access.useCustomConfig = true;
access.openaiApiKey = sfakValue;
access.openaiUrl = apiURL;
});
removeCookie("sfak");
}
}, []);

if (
context.length === 0 &&
session.messages.at(0)?.content !== BOT_HELLO.content
Expand Down
64 changes: 63 additions & 1 deletion app/components/sidebar.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
import { useCookies } from "react-cookie";
import React, { useEffect, useRef, useMemo, useState, Fragment } from "react";

import styles from "./home.module.scss";

import { IconButton } from "./button";
import SettingsIcon from "../icons/settings.svg";
import GithubIcon from "../icons/github.svg";
import SiliconFlowIcon from "../icons/sf.svg";
import SiliconFlowActiveIcon from "../icons/sf.active.svg";
import ChatGptIcon from "../icons/chatgpt.svg";
import AddIcon from "../icons/add.svg";
import DeleteIcon from "../icons/delete.svg";
Expand All @@ -14,7 +17,12 @@ import DiscoveryIcon from "../icons/discovery.svg";

import Locale from "../locales";

import { useAppConfig, useChatStore } from "../store";
import {
DEFAULT_CONFIG,
useAccessStore,
useAppConfig,
useChatStore,
} from "../store";

import {
DEFAULT_SIDEBAR_WIDTH,
Expand Down Expand Up @@ -228,6 +236,10 @@ export function SideBar(props: { className?: string }) {
const navigate = useNavigate();
const config = useAppConfig();
const chatStore = useChatStore();
const accessStore = useAccessStore();
const [cookies, setCookie, removeCookie] = useCookies(["sfak"], {
doNotParse: true,
});

return (
<SideBarContainer
Expand Down Expand Up @@ -320,6 +332,56 @@ export function SideBar(props: { className?: string }) {
/>
</a>
</div>
<div className={styles["sidebar-action"]}>
<a
rel="noopener noreferrer"
onClick={() => {
if (accessStore.useCustomConfig && accessStore.openaiApiKey) {
const confirmLogout = window.confirm(
"Are you sure you want to log out?",
);
if (confirmLogout) {
chatStore.updateTargetSession(
chatStore.currentSession(),
(session) => {
session.mask.modelConfig.model =
DEFAULT_CONFIG.modelConfig.model;
session.mask.modelConfig.providerName =
DEFAULT_CONFIG.modelConfig.providerName;
accessStore.update((access) => {
removeCookie("sfak");
access.openaiApiKey = "";
access.useCustomConfig = false;
window.location.href = "/";
});
},
);
}
} else {
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
}`;
}
}}
>
<IconButton
aria={Locale.Export.MessageFromChatGPT}
key={accessStore.openaiApiKey + accessStore.openaiUrl}
icon={
accessStore.useCustomConfig && accessStore.openaiApiKey ? (
<SiliconFlowActiveIcon />
) : (
<SiliconFlowIcon />
)
}
shadow
/>
</a>
</div>
</>
}
secondaryAction={
Expand Down
3 changes: 2 additions & 1 deletion app/constant.ts
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ export enum Path {
}

export enum ApiPath {
OAuth = "/api/oauth_callback",
Cors = "",
Azure = "/api/azure",
OpenAI = "/api/openai",
Expand Down Expand Up @@ -261,7 +262,7 @@ You are ChatGPT, a large language model trained by {{ServiceProvider}}.
Knowledge cutoff: {{cutoff}}
Current model: {{model}}
Current time: {{time}}
Latex inline: \\(x^2\\)
Latex inline: \\(x^2\\)
Latex block: $$e=mc^2$$
`;

Expand Down
3 changes: 3 additions & 0 deletions app/icons/sf.active.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
3 changes: 3 additions & 0 deletions app/icons/sf.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading

0 comments on commit 4595275

Please sign in to comment.