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 a029b43 commit 41223d9
Show file tree
Hide file tree
Showing 36 changed files with 1,082 additions and 105 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
52 changes: 48 additions & 4 deletions app/client/platforms/siliconflow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import {
SILICONFLOW_BASE_URL,
SiliconFlow,
REQUEST_TIMEOUT_MS_FOR_THINKING,
DEFAULT_MODELS,
} from "@/app/constant";
import {
useAccessStore,
Expand All @@ -13,7 +14,7 @@ import {
ChatMessageTool,
usePluginStore,
} from "@/app/store";
import { streamWithThink } from "@/app/utils/chat";
import { preProcessImageContent, streamWithThink } from "@/app/utils/chat";
import {
ChatOptions,
getHeaders,
Expand All @@ -25,12 +26,22 @@ import { getClientConfig } from "@/app/config/client";
import {
getMessageTextContent,
getMessageTextContentWithoutThinking,
isVisionModel,
} from "@/app/utils";
import { RequestPayload } from "./openai";

import { fetch } from "@/app/utils/stream";
export interface SiliconFlowListModelResponse {
object: string;
data: Array<{
id: string;
object: string;
root: string;
}>;
}

export class SiliconflowApi implements LLMApi {
private disableListModels = true;
private disableListModels = false;

path(path: string): string {
const accessStore = useAccessStore.getState();
Expand Down Expand Up @@ -71,13 +82,16 @@ export class SiliconflowApi implements LLMApi {
}

async chat(options: ChatOptions) {
const visionModel = isVisionModel(options.config.model);
const messages: ChatOptions["messages"] = [];
for (const v of options.messages) {
if (v.role === "assistant") {
const content = getMessageTextContentWithoutThinking(v);
messages.push({ role: v.role, content });
} else {
const content = getMessageTextContent(v);
const content = visionModel
? await preProcessImageContent(v.content)
: getMessageTextContent(v);
messages.push({ role: v.role, content });
}
}
Expand Down Expand Up @@ -238,6 +252,36 @@ export class SiliconflowApi implements LLMApi {
}

async models(): Promise<LLMModel[]> {
return [];
if (this.disableListModels) {
return DEFAULT_MODELS.slice();
}

const res = await fetch(this.path(SiliconFlow.ListModelPath), {
method: "GET",
headers: {
...getHeaders(),
},
});

const resJson = (await res.json()) as SiliconFlowListModelResponse;
const chatModels = resJson.data;
console.log("[Models]", chatModels);

if (!chatModels) {
return [];
}

let seq = 1000; //同 Constant.ts 中的排序保持一致
return chatModels.map((m) => ({
name: m.id,
available: true,
sorted: seq++,
provider: {
id: "siliconflow",
providerName: "SiliconFlow",
providerType: "siliconflow",
sorted: 14,
},
}));
}
}
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
Loading

0 comments on commit 41223d9

Please sign in to comment.