mirror of
				https://github.com/songquanpeng/one-api.git
				synced 2025-11-04 15:53:42 +08:00 
			
		
		
		
	Compare commits
	
		
			61 Commits
		
	
	
		
			v0.6.11-al
			...
			v0.6.11-pr
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					3f421c4f04 | ||
| 
						 | 
					1ce6a226f6 | ||
| 
						 | 
					cafd0a0327 | ||
| 
						 | 
					8b8cd03e85 | ||
| 
						 | 
					54c38de813 | ||
| 
						 | 
					d6284bf6b0 | ||
| 
						 | 
					df5d2ca93d | ||
| 
						 | 
					fef7ae048b | ||
| 
						 | 
					6916debf66 | ||
| 
						 | 
					53da209134 | ||
| 
						 | 
					517f6ad211 | ||
| 
						 | 
					10aba11f18 | ||
| 
						 | 
					4d011c5f98 | ||
| 
						 | 
					eb96aa635e | ||
| 
						 | 
					c715f2bc1d | ||
| 
						 | 
					aed090dd55 | ||
| 
						 | 
					696265774e | ||
| 
						 | 
					974729426d | ||
| 
						 | 
					57c1367ec8 | ||
| 
						 | 
					44233d5c04 | ||
| 
						 | 
					bf45a955c3 | ||
| 
						 | 
					20435fcbfc | ||
| 
						 | 
					6e7a1c2323 | ||
| 
						 | 
					dd65b997dd | ||
| 
						 | 
					0b6d03d6c6 | ||
| 
						 | 
					4375246e24 | ||
| 
						 | 
					3e3b8230ac | ||
| 
						 | 
					07808122a6 | ||
| 
						 | 
					c96895e35b | ||
| 
						 | 
					2552c68249 | ||
| 
						 | 
					5c81e40612 | ||
| 
						 | 
					0d5318b1b7 | ||
| 
						 | 
					db65db2807 | ||
| 
						 | 
					e0b7e6a9e2 | ||
| 
						 | 
					27c2abe80f | ||
| 
						 | 
					2c867251b5 | ||
| 
						 | 
					108111ebd3 | ||
| 
						 | 
					293ba93ad6 | ||
| 
						 | 
					faced40d5b | ||
| 
						 | 
					2ae9997f29 | ||
| 
						 | 
					e146b14d46 | ||
| 
						 | 
					e19045f925 | ||
| 
						 | 
					d2903b673d | ||
| 
						 | 
					33edcf604c | ||
| 
						 | 
					78fd6ef161 | ||
| 
						 | 
					9b6d5f76e0 | ||
| 
						 | 
					2250f311e1 | ||
| 
						 | 
					e43f758623 | ||
| 
						 | 
					6ded638f70 | ||
| 
						 | 
					ea3331b79a | ||
| 
						 | 
					7a97ddc03c | ||
| 
						 | 
					d7028b55fd | ||
| 
						 | 
					36a03f465b | ||
| 
						 | 
					ae16647047 | ||
| 
						 | 
					4dbc2ad86d | ||
| 
						 | 
					94479c2800 | ||
| 
						 | 
					614903b524 | ||
| 
						 | 
					1dc255c219 | ||
| 
						 | 
					15c27e4b12 | ||
| 
						 | 
					a910d3ba25 | ||
| 
						 | 
					34f889437d | 
							
								
								
									
										7
									
								
								.github/workflows/docker-image.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/docker-image.yml
									
									
									
									
										vendored
									
									
								
							@@ -32,10 +32,10 @@ jobs:
 | 
				
			|||||||
          git describe --tags > VERSION 
 | 
					          git describe --tags > VERSION 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Set up QEMU
 | 
					      - name: Set up QEMU
 | 
				
			||||||
        uses: docker/setup-qemu-action@v2
 | 
					        uses: docker/setup-qemu-action@v3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Set up Docker Buildx
 | 
					      - name: Set up Docker Buildx
 | 
				
			||||||
        uses: docker/setup-buildx-action@v2
 | 
					        uses: docker/setup-buildx-action@v3
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Log in to Docker Hub
 | 
					      - name: Log in to Docker Hub
 | 
				
			||||||
        uses: docker/login-action@v2
 | 
					        uses: docker/login-action@v2
 | 
				
			||||||
@@ -62,8 +62,7 @@ jobs:
 | 
				
			|||||||
        uses: docker/build-push-action@v3
 | 
					        uses: docker/build-push-action@v3
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          context: .
 | 
					          context: .
 | 
				
			||||||
          #          platforms: linux/amd64,linux/arm64
 | 
					          platforms: ${{ contains(github.ref, 'alpha') && 'linux/amd64' || 'linux/amd64' }}
 | 
				
			||||||
          platforms: linux/amd64 # TODO disable arm64 for now, because it cause error
 | 
					 | 
				
			||||||
          push: true
 | 
					          push: true
 | 
				
			||||||
          tags: ${{ steps.meta.outputs.tags }}
 | 
					          tags: ${{ steps.meta.outputs.tags }}
 | 
				
			||||||
          labels: ${{ steps.meta.outputs.labels }}
 | 
					          labels: ${{ steps.meta.outputs.labels }}
 | 
				
			||||||
							
								
								
									
										1
									
								
								.github/workflows/linux-release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/linux-release.yml
									
									
									
									
										vendored
									
									
								
							@@ -7,6 +7,7 @@ on:
 | 
				
			|||||||
    tags:
 | 
					    tags:
 | 
				
			||||||
      - 'v*.*.*'
 | 
					      - 'v*.*.*'
 | 
				
			||||||
      - '!*-alpha*'
 | 
					      - '!*-alpha*'
 | 
				
			||||||
 | 
					      - '!*-preview*'
 | 
				
			||||||
  workflow_dispatch:
 | 
					  workflow_dispatch:
 | 
				
			||||||
    inputs:
 | 
					    inputs:
 | 
				
			||||||
      name:
 | 
					      name:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								.github/workflows/macos-release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/macos-release.yml
									
									
									
									
										vendored
									
									
								
							@@ -7,6 +7,7 @@ on:
 | 
				
			|||||||
    tags:
 | 
					    tags:
 | 
				
			||||||
      - 'v*.*.*'
 | 
					      - 'v*.*.*'
 | 
				
			||||||
      - '!*-alpha*'
 | 
					      - '!*-alpha*'
 | 
				
			||||||
 | 
					      - '!*-preview*'
 | 
				
			||||||
  workflow_dispatch:
 | 
					  workflow_dispatch:
 | 
				
			||||||
    inputs:
 | 
					    inputs:
 | 
				
			||||||
      name:
 | 
					      name:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										1
									
								
								.github/workflows/windows-release.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.github/workflows/windows-release.yml
									
									
									
									
										vendored
									
									
								
							@@ -7,6 +7,7 @@ on:
 | 
				
			|||||||
    tags:
 | 
					    tags:
 | 
				
			||||||
      - 'v*.*.*'
 | 
					      - 'v*.*.*'
 | 
				
			||||||
      - '!*-alpha*'
 | 
					      - '!*-alpha*'
 | 
				
			||||||
 | 
					      - '!*-preview*'
 | 
				
			||||||
  workflow_dispatch:
 | 
					  workflow_dispatch:
 | 
				
			||||||
    inputs:
 | 
					    inputs:
 | 
				
			||||||
      name:
 | 
					      name:
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										30
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										30
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -9,23 +9,22 @@ RUN npm install --prefix /web/default & \
 | 
				
			|||||||
    npm install --prefix /web/air & \
 | 
					    npm install --prefix /web/air & \
 | 
				
			||||||
    wait
 | 
					    wait
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat /web/default/VERSION) npm run build --prefix /web/default & \
 | 
					RUN DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat ./VERSION) npm run build --prefix /web/default & \
 | 
				
			||||||
    DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat /web/berry/VERSION) npm run build --prefix /web/berry & \
 | 
					    DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat ./VERSION) npm run build --prefix /web/berry & \
 | 
				
			||||||
    DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat /web/air/VERSION) npm run build --prefix /web/air & \
 | 
					    DISABLE_ESLINT_PLUGIN='true' REACT_APP_VERSION=$(cat ./VERSION) npm run build --prefix /web/air & \
 | 
				
			||||||
    wait
 | 
					    wait
 | 
				
			||||||
 | 
					
 | 
				
			||||||
FROM golang AS builder2
 | 
					FROM golang:alpine AS builder2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
 | 
					RUN apk add --no-cache \
 | 
				
			||||||
    build-essential \
 | 
					    gcc \
 | 
				
			||||||
    sqlite3 libsqlite3-dev \
 | 
					    musl-dev \
 | 
				
			||||||
    && rm -rf /var/lib/apt/lists/*
 | 
					    sqlite-dev \
 | 
				
			||||||
 | 
					    build-base
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ENV GO111MODULE=on \
 | 
					ENV GO111MODULE=on \
 | 
				
			||||||
    CGO_ENABLED=1 \
 | 
					    CGO_ENABLED=1 \
 | 
				
			||||||
    GOOS=linux \
 | 
					    GOOS=linux
 | 
				
			||||||
    CGO_CFLAGS="-I/usr/include" \
 | 
					 | 
				
			||||||
    CGO_LDFLAGS="-L/usr/lib"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /build
 | 
					WORKDIR /build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -35,14 +34,11 @@ RUN go mod download
 | 
				
			|||||||
COPY . .
 | 
					COPY . .
 | 
				
			||||||
COPY --from=builder /web/build ./web/build
 | 
					COPY --from=builder /web/build ./web/build
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN go build -trimpath -ldflags "-s -w -X 'github.com/songquanpeng/one-api/common.Version=$(cat VERSION)'" -o one-api
 | 
					RUN go build -trimpath -ldflags "-s -w -X 'github.com/songquanpeng/one-api/common.Version=$(cat VERSION)' -linkmode external -extldflags '-static'" -o one-api
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# Final runtime image
 | 
					FROM alpine:latest
 | 
				
			||||||
FROM ubuntu:22.04
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
RUN apt-get update && apt-get install -y --no-install-recommends \
 | 
					RUN apk add --no-cache ca-certificates tzdata
 | 
				
			||||||
    ca-certificates tzdata bash \
 | 
					 | 
				
			||||||
    && rm -rf /var/lib/apt/lists/*
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY --from=builder2 /build/one-api /
 | 
					COPY --from=builder2 /build/one-api /
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -315,6 +315,7 @@ If the channel ID is not provided, load balancing will be used to distribute the
 | 
				
			|||||||
* [FastGPT](https://github.com/labring/FastGPT): Knowledge question answering system based on the LLM
 | 
					* [FastGPT](https://github.com/labring/FastGPT): Knowledge question answering system based on the LLM
 | 
				
			||||||
* [VChart](https://github.com/VisActor/VChart):  More than just a cross-platform charting library, but also an expressive data storyteller.
 | 
					* [VChart](https://github.com/VisActor/VChart):  More than just a cross-platform charting library, but also an expressive data storyteller.
 | 
				
			||||||
* [VMind](https://github.com/VisActor/VMind):  Not just automatic, but also fantastic. Open-source solution for intelligent visualization.
 | 
					* [VMind](https://github.com/VisActor/VMind):  Not just automatic, but also fantastic. Open-source solution for intelligent visualization.
 | 
				
			||||||
 | 
					* * [CherryStudio](https://github.com/CherryHQ/cherry-studio):  A cross-platform AI client that integrates multiple service providers and supports local knowledge base management.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## Note
 | 
					## Note
 | 
				
			||||||
This project is an open-source project. Please use it in compliance with OpenAI's [Terms of Use](https://openai.com/policies/terms-of-use) and **applicable laws and regulations**. It must not be used for illegal purposes.
 | 
					This project is an open-source project. Please use it in compliance with OpenAI's [Terms of Use](https://openai.com/policies/terms-of-use) and **applicable laws and regulations**. It must not be used for illegal purposes.
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -287,8 +287,8 @@ graph LR
 | 
				
			|||||||
    + インターフェイスアドレスと API Key が正しいか再確認してください。
 | 
					    + インターフェイスアドレスと API Key が正しいか再確認してください。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 関連プロジェクト
 | 
					## 関連プロジェクト
 | 
				
			||||||
[FastGPT](https://github.com/labring/FastGPT): LLM に基づく知識質問応答システム
 | 
					* [FastGPT](https://github.com/labring/FastGPT): LLM に基づく知識質問応答システム
 | 
				
			||||||
 | 
					* [CherryStudio](https://github.com/CherryHQ/cherry-studio):  マルチプラットフォーム対応のAIクライアント。複数のサービスプロバイダーを統合管理し、ローカル知識ベースをサポートします。
 | 
				
			||||||
## 注
 | 
					## 注
 | 
				
			||||||
本プロジェクトはオープンソースプロジェクトです。OpenAI の[利用規約](https://openai.com/policies/terms-of-use)および**適用される法令**を遵守してご利用ください。違法な目的での利用はご遠慮ください。
 | 
					本プロジェクトはオープンソースプロジェクトです。OpenAI の[利用規約](https://openai.com/policies/terms-of-use)および**適用される法令**を遵守してご利用ください。違法な目的での利用はご遠慮ください。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -57,10 +57,10 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 
 | 
				
			|||||||
> 根据[《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区公众提供一切未经备案的生成式人工智能服务。
 | 
					> 根据[《生成式人工智能服务管理暂行办法》](http://www.cac.gov.cn/2023-07/13/c_1690898327029107.htm)的要求,请勿对中国地区公众提供一切未经备案的生成式人工智能服务。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> [!NOTE]
 | 
					> [!NOTE]
 | 
				
			||||||
> 稳定版镜像地址:[justsong/one-api](https://hub.docker.com/repository/docker/justsong/one-api)
 | 
					> 稳定版 / 预览版镜像地址:[justsong/one-api](https://hub.docker.com/repository/docker/justsong/one-api)
 | 
				
			||||||
> 或者 [ghcr.io/songquanpeng/one-api](https://github.com/songquanpeng/one-api/pkgs/container/one-api)
 | 
					> 或者 [ghcr.io/songquanpeng/one-api](https://github.com/songquanpeng/one-api/pkgs/container/one-api)
 | 
				
			||||||
>
 | 
					>
 | 
				
			||||||
> 预览版镜像地址:[justsong/one-api-alpha](https://hub.docker.com/repository/docker/justsong/one-api-alpha)
 | 
					> alpha 版镜像地址:[justsong/one-api-alpha](https://hub.docker.com/repository/docker/justsong/one-api-alpha)
 | 
				
			||||||
> 或者 [ghcr.io/songquanpeng/one-api-alpha](https://github.com/songquanpeng/one-api/pkgs/container/one-api-alpha)
 | 
					> 或者 [ghcr.io/songquanpeng/one-api-alpha](https://github.com/songquanpeng/one-api/pkgs/container/one-api-alpha)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
> [!WARNING]
 | 
					> [!WARNING]
 | 
				
			||||||
@@ -93,7 +93,7 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 
 | 
				
			|||||||
   + [x] [DeepL](https://www.deepl.com/)
 | 
					   + [x] [DeepL](https://www.deepl.com/)
 | 
				
			||||||
   + [x] [together.ai](https://www.together.ai/)
 | 
					   + [x] [together.ai](https://www.together.ai/)
 | 
				
			||||||
   + [x] [novita.ai](https://www.novita.ai/)
 | 
					   + [x] [novita.ai](https://www.novita.ai/)
 | 
				
			||||||
   + [x] [硅基流动 SiliconCloud](https://siliconflow.cn/siliconcloud)
 | 
					   + [x] [硅基流动 SiliconCloud](https://cloud.siliconflow.cn/i/rKXmRobW)
 | 
				
			||||||
   + [x] [xAI](https://x.ai/)
 | 
					   + [x] [xAI](https://x.ai/)
 | 
				
			||||||
2. 支持配置镜像以及众多[第三方代理服务](https://iamazing.cn/page/openai-api-third-party-services)。
 | 
					2. 支持配置镜像以及众多[第三方代理服务](https://iamazing.cn/page/openai-api-third-party-services)。
 | 
				
			||||||
3. 支持通过**负载均衡**的方式访问多个渠道。
 | 
					3. 支持通过**负载均衡**的方式访问多个渠道。
 | 
				
			||||||
@@ -115,7 +115,7 @@ _✨ 通过标准的 OpenAI API 格式访问所有的大模型,开箱即用 
 | 
				
			|||||||
19. 支持丰富的**自定义**设置,
 | 
					19. 支持丰富的**自定义**设置,
 | 
				
			||||||
    1. 支持自定义系统名称,logo 以及页脚。
 | 
					    1. 支持自定义系统名称,logo 以及页脚。
 | 
				
			||||||
    2. 支持自定义首页和关于页面,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入。
 | 
					    2. 支持自定义首页和关于页面,可以选择使用 HTML & Markdown 代码进行自定义,或者使用一个单独的网页通过 iframe 嵌入。
 | 
				
			||||||
20. 支持通过系统访问令牌调用管理 API,进而**在无需二开的情况下扩展和自定义** One API 的功能,详情请参考此处 [API 文档](./docs/API.md)。。
 | 
					20. 支持通过系统访问令牌调用管理 API,进而**在无需二开的情况下扩展和自定义** One API 的功能,详情请参考此处 [API 文档](./docs/API.md)。
 | 
				
			||||||
21. 支持 Cloudflare Turnstile 用户校验。
 | 
					21. 支持 Cloudflare Turnstile 用户校验。
 | 
				
			||||||
22. 支持用户管理,支持**多种用户登录注册方式**:
 | 
					22. 支持用户管理,支持**多种用户登录注册方式**:
 | 
				
			||||||
    + 邮箱登录注册(支持注册邮箱白名单)以及通过邮箱进行密码重置。
 | 
					    + 邮箱登录注册(支持注册邮箱白名单)以及通过邮箱进行密码重置。
 | 
				
			||||||
@@ -469,6 +469,7 @@ https://openai.justsong.cn
 | 
				
			|||||||
* [ChatGPT Next Web](https://github.com/Yidadaa/ChatGPT-Next-Web):  一键拥有你自己的跨平台 ChatGPT 应用
 | 
					* [ChatGPT Next Web](https://github.com/Yidadaa/ChatGPT-Next-Web):  一键拥有你自己的跨平台 ChatGPT 应用
 | 
				
			||||||
* [VChart](https://github.com/VisActor/VChart):  不只是开箱即用的多端图表库,更是生动灵活的数据故事讲述者。
 | 
					* [VChart](https://github.com/VisActor/VChart):  不只是开箱即用的多端图表库,更是生动灵活的数据故事讲述者。
 | 
				
			||||||
* [VMind](https://github.com/VisActor/VMind):  不仅自动,还很智能。开源智能可视化解决方案。
 | 
					* [VMind](https://github.com/VisActor/VMind):  不仅自动,还很智能。开源智能可视化解决方案。
 | 
				
			||||||
 | 
					* [CherryStudio](https://github.com/CherryHQ/cherry-studio):  全平台支持的AI客户端, 多服务商集成管理、本地知识库支持。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## 注意
 | 
					## 注意
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -126,10 +126,10 @@ var ValidThemes = map[string]bool{
 | 
				
			|||||||
// All duration's unit is seconds
 | 
					// All duration's unit is seconds
 | 
				
			||||||
// Shouldn't larger then RateLimitKeyExpirationDuration
 | 
					// Shouldn't larger then RateLimitKeyExpirationDuration
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	GlobalApiRateLimitNum            = env.Int("GLOBAL_API_RATE_LIMIT", 240)
 | 
						GlobalApiRateLimitNum            = env.Int("GLOBAL_API_RATE_LIMIT", 480)
 | 
				
			||||||
	GlobalApiRateLimitDuration int64 = 3 * 60
 | 
						GlobalApiRateLimitDuration int64 = 3 * 60
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	GlobalWebRateLimitNum            = env.Int("GLOBAL_WEB_RATE_LIMIT", 120)
 | 
						GlobalWebRateLimitNum            = env.Int("GLOBAL_WEB_RATE_LIMIT", 240)
 | 
				
			||||||
	GlobalWebRateLimitDuration int64 = 3 * 60
 | 
						GlobalWebRateLimitDuration int64 = 3 * 60
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	UploadRateLimitNum            = 10
 | 
						UploadRateLimitNum            = 10
 | 
				
			||||||
@@ -163,4 +163,4 @@ var UserContentRequestProxy = env.String("USER_CONTENT_REQUEST_PROXY", "")
 | 
				
			|||||||
var UserContentRequestTimeout = env.Int("USER_CONTENT_REQUEST_TIMEOUT", 30)
 | 
					var UserContentRequestTimeout = env.Int("USER_CONTENT_REQUEST_TIMEOUT", 30)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var EnforceIncludeUsage = env.Bool("ENFORCE_INCLUDE_USAGE", false)
 | 
					var EnforceIncludeUsage = env.Bool("ENFORCE_INCLUDE_USAGE", false)
 | 
				
			||||||
var TestPrompt = env.String("TEST_PROMPT", "Print your model name exactly and do not output without any other text.")
 | 
					var TestPrompt = env.String("TEST_PROMPT", "Output only your specific model name with no additional text.")
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,10 +3,11 @@ package common
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"bytes"
 | 
						"bytes"
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
					 | 
				
			||||||
	"github.com/songquanpeng/one-api/common/ctxkey"
 | 
					 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/common/ctxkey"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetRequestBody(c *gin.Context) ([]byte, error) {
 | 
					func GetRequestBody(c *gin.Context) ([]byte, error) {
 | 
				
			||||||
@@ -31,7 +32,6 @@ func UnmarshalBodyReusable(c *gin.Context, v any) error {
 | 
				
			|||||||
	contentType := c.Request.Header.Get("Content-Type")
 | 
						contentType := c.Request.Header.Get("Content-Type")
 | 
				
			||||||
	if strings.HasPrefix(contentType, "application/json") {
 | 
						if strings.HasPrefix(contentType, "application/json") {
 | 
				
			||||||
		err = json.Unmarshal(requestBody, &v)
 | 
							err = json.Unmarshal(requestBody, &v)
 | 
				
			||||||
		c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
 | 
					 | 
				
			||||||
	} else {
 | 
						} else {
 | 
				
			||||||
		c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
 | 
							c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
 | 
				
			||||||
		err = c.ShouldBind(&v)
 | 
							err = c.ShouldBind(&v)
 | 
				
			||||||
@@ -40,6 +40,7 @@ func UnmarshalBodyReusable(c *gin.Context, v any) error {
 | 
				
			|||||||
		return err
 | 
							return err
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	// Reset request body
 | 
						// Reset request body
 | 
				
			||||||
 | 
						c.Request.Body = io.NopCloser(bytes.NewBuffer(requestBody))
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -93,6 +93,9 @@ func Error(ctx context.Context, msg string) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func Debugf(ctx context.Context, format string, a ...any) {
 | 
					func Debugf(ctx context.Context, format string, a ...any) {
 | 
				
			||||||
 | 
						if !config.DebugEnabled {
 | 
				
			||||||
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	logHelper(ctx, loggerDEBUG, fmt.Sprintf(format, a...))
 | 
						logHelper(ctx, loggerDEBUG, fmt.Sprintf(format, a...))
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										13
									
								
								common/utils/array.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								common/utils/array.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					package utils
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func DeDuplication(slice []string) []string {
 | 
				
			||||||
 | 
						m := make(map[string]bool)
 | 
				
			||||||
 | 
						for _, v := range slice {
 | 
				
			||||||
 | 
							m[v] = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						result := make([]string, 0, len(m))
 | 
				
			||||||
 | 
						for v := range m {
 | 
				
			||||||
 | 
							result = append(result, v)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return result
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -112,6 +112,13 @@ type DeepSeekUsageResponse struct {
 | 
				
			|||||||
	} `json:"balance_infos"`
 | 
						} `json:"balance_infos"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type OpenRouterResponse struct {
 | 
				
			||||||
 | 
						Data struct {
 | 
				
			||||||
 | 
							TotalCredits float64 `json:"total_credits"`
 | 
				
			||||||
 | 
							TotalUsage   float64 `json:"total_usage"`
 | 
				
			||||||
 | 
						} `json:"data"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// GetAuthHeader get auth header
 | 
					// GetAuthHeader get auth header
 | 
				
			||||||
func GetAuthHeader(token string) http.Header {
 | 
					func GetAuthHeader(token string) http.Header {
 | 
				
			||||||
	h := http.Header{}
 | 
						h := http.Header{}
 | 
				
			||||||
@@ -285,6 +292,22 @@ func updateChannelDeepSeekBalance(channel *model.Channel) (float64, error) {
 | 
				
			|||||||
	return balance, nil
 | 
						return balance, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func updateChannelOpenRouterBalance(channel *model.Channel) (float64, error) {
 | 
				
			||||||
 | 
						url := "https://openrouter.ai/api/v1/credits"
 | 
				
			||||||
 | 
						body, err := GetResponseBody("GET", url, channel, GetAuthHeader(channel.Key))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						response := OpenRouterResponse{}
 | 
				
			||||||
 | 
						err = json.Unmarshal(body, &response)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return 0, err
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						balance := response.Data.TotalCredits - response.Data.TotalUsage
 | 
				
			||||||
 | 
						channel.UpdateBalance(balance)
 | 
				
			||||||
 | 
						return balance, nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func updateChannelBalance(channel *model.Channel) (float64, error) {
 | 
					func updateChannelBalance(channel *model.Channel) (float64, error) {
 | 
				
			||||||
	baseURL := channeltype.ChannelBaseURLs[channel.Type]
 | 
						baseURL := channeltype.ChannelBaseURLs[channel.Type]
 | 
				
			||||||
	if channel.GetBaseURL() == "" {
 | 
						if channel.GetBaseURL() == "" {
 | 
				
			||||||
@@ -313,6 +336,8 @@ func updateChannelBalance(channel *model.Channel) (float64, error) {
 | 
				
			|||||||
		return updateChannelSiliconFlowBalance(channel)
 | 
							return updateChannelSiliconFlowBalance(channel)
 | 
				
			||||||
	case channeltype.DeepSeek:
 | 
						case channeltype.DeepSeek:
 | 
				
			||||||
		return updateChannelDeepSeekBalance(channel)
 | 
							return updateChannelDeepSeekBalance(channel)
 | 
				
			||||||
 | 
						case channeltype.OpenRouter:
 | 
				
			||||||
 | 
							return updateChannelOpenRouterBalance(channel)
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		return 0, errors.New("尚未实现")
 | 
							return 0, errors.New("尚未实现")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -153,6 +153,7 @@ func testChannel(ctx context.Context, channel *model.Channel, request *relaymode
 | 
				
			|||||||
	rawResponse := w.Body.String()
 | 
						rawResponse := w.Body.String()
 | 
				
			||||||
	_, responseMessage, err = parseTestResponse(rawResponse)
 | 
						_, responseMessage, err = parseTestResponse(rawResponse)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
 | 
							logger.SysError(fmt.Sprintf("failed to parse error: %s, \nresponse: %s", err.Error(), rawResponse))
 | 
				
			||||||
		return "", err, nil
 | 
							return "", err, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	result := w.Result()
 | 
						result := w.Result()
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,10 +2,13 @@ package model
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/common"
 | 
					 | 
				
			||||||
	"gorm.io/gorm"
 | 
					 | 
				
			||||||
	"sort"
 | 
						"sort"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gorm.io/gorm"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/common"
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/common/utils"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Ability struct {
 | 
					type Ability struct {
 | 
				
			||||||
@@ -49,6 +52,7 @@ func GetRandomSatisfiedChannel(group string, model string, ignoreFirstPriority b
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func (channel *Channel) AddAbilities() error {
 | 
					func (channel *Channel) AddAbilities() error {
 | 
				
			||||||
	models_ := strings.Split(channel.Models, ",")
 | 
						models_ := strings.Split(channel.Models, ",")
 | 
				
			||||||
 | 
						models_ = utils.DeDuplication(models_)
 | 
				
			||||||
	groups_ := strings.Split(channel.Group, ",")
 | 
						groups_ := strings.Split(channel.Group, ",")
 | 
				
			||||||
	abilities := make([]Ability, 0, len(models_))
 | 
						abilities := make([]Ability, 0, len(models_))
 | 
				
			||||||
	for _, model := range models_ {
 | 
						for _, model := range models_ {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,6 +35,8 @@ func ShouldDisableChannel(err *model.Error, statusCode int) bool {
 | 
				
			|||||||
		strings.Contains(lowerMessage, "balance") ||
 | 
							strings.Contains(lowerMessage, "balance") ||
 | 
				
			||||||
		strings.Contains(lowerMessage, "permission denied") ||
 | 
							strings.Contains(lowerMessage, "permission denied") ||
 | 
				
			||||||
		strings.Contains(lowerMessage, "organization has been restricted") || // groq
 | 
							strings.Contains(lowerMessage, "organization has been restricted") || // groq
 | 
				
			||||||
 | 
							strings.Contains(lowerMessage, "api key not valid") || // gemini
 | 
				
			||||||
 | 
							strings.Contains(lowerMessage, "api key expired") || // gemini
 | 
				
			||||||
		strings.Contains(lowerMessage, "已欠费") {
 | 
							strings.Contains(lowerMessage, "已欠费") {
 | 
				
			||||||
		return true
 | 
							return true
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								relay/adaptor/alibailian/constants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								relay/adaptor/alibailian/constants.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					package alibailian
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://help.aliyun.com/zh/model-studio/getting-started/models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var ModelList = []string{
 | 
				
			||||||
 | 
						"qwen-turbo",
 | 
				
			||||||
 | 
						"qwen-plus",
 | 
				
			||||||
 | 
						"qwen-long",
 | 
				
			||||||
 | 
						"qwen-max",
 | 
				
			||||||
 | 
						"qwen-coder-plus",
 | 
				
			||||||
 | 
						"qwen-coder-plus-latest",
 | 
				
			||||||
 | 
						"qwen-coder-turbo",
 | 
				
			||||||
 | 
						"qwen-coder-turbo-latest",
 | 
				
			||||||
 | 
						"qwen-mt-plus",
 | 
				
			||||||
 | 
						"qwen-mt-turbo",
 | 
				
			||||||
 | 
						"qwq-32b-preview",
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"deepseek-r1",
 | 
				
			||||||
 | 
						"deepseek-v3",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										19
									
								
								relay/adaptor/alibailian/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								relay/adaptor/alibailian/main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,19 @@
 | 
				
			|||||||
 | 
					package alibailian
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/meta"
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/relaymode"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetRequestURL(meta *meta.Meta) (string, error) {
 | 
				
			||||||
 | 
						switch meta.Mode {
 | 
				
			||||||
 | 
						case relaymode.ChatCompletions:
 | 
				
			||||||
 | 
							return fmt.Sprintf("%s/compatible-mode/v1/chat/completions", meta.BaseURL), nil
 | 
				
			||||||
 | 
						case relaymode.Embeddings:
 | 
				
			||||||
 | 
							return fmt.Sprintf("%s/compatible-mode/v1/embeddings", meta.BaseURL), nil
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return "", fmt.Errorf("unsupported relay mode %d for ali bailian", meta.Mode)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -4,6 +4,7 @@ var ModelList = []string{
 | 
				
			|||||||
	"claude-instant-1.2", "claude-2.0", "claude-2.1",
 | 
						"claude-instant-1.2", "claude-2.0", "claude-2.1",
 | 
				
			||||||
	"claude-3-haiku-20240307",
 | 
						"claude-3-haiku-20240307",
 | 
				
			||||||
	"claude-3-5-haiku-20241022",
 | 
						"claude-3-5-haiku-20241022",
 | 
				
			||||||
 | 
						"claude-3-5-haiku-latest",
 | 
				
			||||||
	"claude-3-sonnet-20240229",
 | 
						"claude-3-sonnet-20240229",
 | 
				
			||||||
	"claude-3-opus-20240229",
 | 
						"claude-3-opus-20240229",
 | 
				
			||||||
	"claude-3-5-sonnet-20240620",
 | 
						"claude-3-5-sonnet-20240620",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										30
									
								
								relay/adaptor/baiduv2/constants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								relay/adaptor/baiduv2/constants.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,30 @@
 | 
				
			|||||||
 | 
					package baiduv2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://console.bce.baidu.com/support/?_=1692863460488×tamp=1739074632076#/api?product=QIANFAN&project=%E5%8D%83%E5%B8%86ModelBuilder&parent=%E5%AF%B9%E8%AF%9DChat%20V2&api=v2%2Fchat%2Fcompletions&method=post
 | 
				
			||||||
 | 
					// https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Fm2vrveyu#%E6%94%AF%E6%8C%81%E6%A8%A1%E5%9E%8B%E5%88%97%E8%A1%A8
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var ModelList = []string{
 | 
				
			||||||
 | 
						"ernie-4.0-8k-latest",
 | 
				
			||||||
 | 
						"ernie-4.0-8k-preview",
 | 
				
			||||||
 | 
						"ernie-4.0-8k",
 | 
				
			||||||
 | 
						"ernie-4.0-turbo-8k-latest",
 | 
				
			||||||
 | 
						"ernie-4.0-turbo-8k-preview",
 | 
				
			||||||
 | 
						"ernie-4.0-turbo-8k",
 | 
				
			||||||
 | 
						"ernie-4.0-turbo-128k",
 | 
				
			||||||
 | 
						"ernie-3.5-8k-preview",
 | 
				
			||||||
 | 
						"ernie-3.5-8k",
 | 
				
			||||||
 | 
						"ernie-3.5-128k",
 | 
				
			||||||
 | 
						"ernie-speed-8k",
 | 
				
			||||||
 | 
						"ernie-speed-128k",
 | 
				
			||||||
 | 
						"ernie-speed-pro-128k",
 | 
				
			||||||
 | 
						"ernie-lite-8k",
 | 
				
			||||||
 | 
						"ernie-lite-pro-128k",
 | 
				
			||||||
 | 
						"ernie-tiny-8k",
 | 
				
			||||||
 | 
						"ernie-char-8k",
 | 
				
			||||||
 | 
						"ernie-char-fiction-8k",
 | 
				
			||||||
 | 
						"ernie-novel-8k",
 | 
				
			||||||
 | 
						"deepseek-v3",
 | 
				
			||||||
 | 
						"deepseek-r1",
 | 
				
			||||||
 | 
						"deepseek-r1-distill-qwen-32b",
 | 
				
			||||||
 | 
						"deepseek-r1-distill-qwen-14b",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										17
									
								
								relay/adaptor/baiduv2/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								relay/adaptor/baiduv2/main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					package baiduv2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/meta"
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/relaymode"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetRequestURL(meta *meta.Meta) (string, error) {
 | 
				
			||||||
 | 
						switch meta.Mode {
 | 
				
			||||||
 | 
						case relaymode.ChatCompletions:
 | 
				
			||||||
 | 
							return fmt.Sprintf("%s/v2/chat/completions", meta.BaseURL), nil
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return "", fmt.Errorf("unsupported relay mode %d for baidu v2", meta.Mode)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -2,5 +2,5 @@ package deepseek
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var ModelList = []string{
 | 
					var ModelList = []string{
 | 
				
			||||||
	"deepseek-chat",
 | 
						"deepseek-chat",
 | 
				
			||||||
	"deepseek-coder",
 | 
						"deepseek-reasoner",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,8 +5,10 @@ import (
 | 
				
			|||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"io"
 | 
						"io"
 | 
				
			||||||
	"net/http"
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/common/config"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/common/helper"
 | 
						"github.com/songquanpeng/one-api/common/helper"
 | 
				
			||||||
	channelhelper "github.com/songquanpeng/one-api/relay/adaptor"
 | 
						channelhelper "github.com/songquanpeng/one-api/relay/adaptor"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/openai"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/openai"
 | 
				
			||||||
@@ -19,15 +21,12 @@ type Adaptor struct {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a *Adaptor) Init(meta *meta.Meta) {
 | 
					func (a *Adaptor) Init(meta *meta.Meta) {
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
 | 
					func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
 | 
				
			||||||
	var defaultVersion string
 | 
						defaultVersion := config.GeminiVersion
 | 
				
			||||||
	switch meta.ActualModelName {
 | 
						if strings.Contains(meta.ActualModelName, "gemini-2.0") ||
 | 
				
			||||||
	case "gemini-2.0-flash-exp",
 | 
							strings.Contains(meta.ActualModelName, "gemini-1.5") {
 | 
				
			||||||
		"gemini-2.0-flash-thinking-exp",
 | 
					 | 
				
			||||||
		"gemini-2.0-flash-thinking-exp-01-21":
 | 
					 | 
				
			||||||
		defaultVersion = "v1beta"
 | 
							defaultVersion = "v1beta"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,35 @@
 | 
				
			|||||||
package gemini
 | 
					package gemini
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/geminiv2"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// https://ai.google.dev/models/gemini
 | 
					// https://ai.google.dev/models/gemini
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var ModelList = []string{
 | 
					var ModelList = geminiv2.ModelList
 | 
				
			||||||
	"gemini-pro", "gemini-1.0-pro",
 | 
					
 | 
				
			||||||
	"gemini-1.5-flash", "gemini-1.5-pro",
 | 
					// ModelsSupportSystemInstruction is the list of models that support system instruction.
 | 
				
			||||||
	"text-embedding-004", "aqa",
 | 
					//
 | 
				
			||||||
	"gemini-2.0-flash-exp",
 | 
					// https://cloud.google.com/vertex-ai/generative-ai/docs/learn/prompts/system-instructions
 | 
				
			||||||
	"gemini-2.0-flash-thinking-exp", "gemini-2.0-flash-thinking-exp-01-21",
 | 
					var ModelsSupportSystemInstruction = []string{
 | 
				
			||||||
 | 
						// "gemini-1.0-pro-002",
 | 
				
			||||||
 | 
						// "gemini-1.5-flash", "gemini-1.5-flash-001", "gemini-1.5-flash-002",
 | 
				
			||||||
 | 
						// "gemini-1.5-flash-8b",
 | 
				
			||||||
 | 
						// "gemini-1.5-pro", "gemini-1.5-pro-001", "gemini-1.5-pro-002",
 | 
				
			||||||
 | 
						// "gemini-1.5-pro-experimental",
 | 
				
			||||||
 | 
						"gemini-2.0-flash", "gemini-2.0-flash-exp",
 | 
				
			||||||
 | 
						"gemini-2.0-flash-thinking-exp-01-21",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// IsModelSupportSystemInstruction check if the model support system instruction.
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// Because the main version of Go is 1.20, slice.Contains cannot be used
 | 
				
			||||||
 | 
					func IsModelSupportSystemInstruction(model string) bool {
 | 
				
			||||||
 | 
						for _, m := range ModelsSupportSystemInstruction {
 | 
				
			||||||
 | 
							if m == model {
 | 
				
			||||||
 | 
								return true
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return false
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -132,9 +132,16 @@ func ConvertRequest(textRequest model.GeneralOpenAIRequest) *ChatRequest {
 | 
				
			|||||||
		}
 | 
							}
 | 
				
			||||||
		// Converting system prompt to prompt from user for the same reason
 | 
							// Converting system prompt to prompt from user for the same reason
 | 
				
			||||||
		if content.Role == "system" {
 | 
							if content.Role == "system" {
 | 
				
			||||||
			content.Role = "user"
 | 
					 | 
				
			||||||
			shouldAddDummyModelMessage = true
 | 
								shouldAddDummyModelMessage = true
 | 
				
			||||||
 | 
								if IsModelSupportSystemInstruction(textRequest.Model) {
 | 
				
			||||||
 | 
									geminiRequest.SystemInstruction = &content
 | 
				
			||||||
 | 
									geminiRequest.SystemInstruction.Role = ""
 | 
				
			||||||
 | 
									continue
 | 
				
			||||||
 | 
								} else {
 | 
				
			||||||
 | 
									content.Role = "user"
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		geminiRequest.Contents = append(geminiRequest.Contents, content)
 | 
							geminiRequest.Contents = append(geminiRequest.Contents, content)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		// If a system message is the last message, we need to add a dummy model message to make gemini happy
 | 
							// If a system message is the last message, we need to add a dummy model message to make gemini happy
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -5,6 +5,7 @@ type ChatRequest struct {
 | 
				
			|||||||
	SafetySettings    []ChatSafetySettings `json:"safety_settings,omitempty"`
 | 
						SafetySettings    []ChatSafetySettings `json:"safety_settings,omitempty"`
 | 
				
			||||||
	GenerationConfig  ChatGenerationConfig `json:"generation_config,omitempty"`
 | 
						GenerationConfig  ChatGenerationConfig `json:"generation_config,omitempty"`
 | 
				
			||||||
	Tools             []ChatTools          `json:"tools,omitempty"`
 | 
						Tools             []ChatTools          `json:"tools,omitempty"`
 | 
				
			||||||
 | 
						SystemInstruction *ChatContent         `json:"system_instruction,omitempty"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type EmbeddingRequest struct {
 | 
					type EmbeddingRequest struct {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										15
									
								
								relay/adaptor/geminiv2/constants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								relay/adaptor/geminiv2/constants.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					package geminiv2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://ai.google.dev/models/gemini
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var ModelList = []string{
 | 
				
			||||||
 | 
						"gemini-pro", "gemini-1.0-pro",
 | 
				
			||||||
 | 
						// "gemma-2-2b-it", "gemma-2-9b-it", "gemma-2-27b-it",
 | 
				
			||||||
 | 
						"gemini-1.5-flash", "gemini-1.5-flash-8b",
 | 
				
			||||||
 | 
						"gemini-1.5-pro", "gemini-1.5-pro-experimental",
 | 
				
			||||||
 | 
						"text-embedding-004", "aqa",
 | 
				
			||||||
 | 
						"gemini-2.0-flash", "gemini-2.0-flash-exp",
 | 
				
			||||||
 | 
						"gemini-2.0-flash-lite-preview-02-05",
 | 
				
			||||||
 | 
						"gemini-2.0-flash-thinking-exp-01-21",
 | 
				
			||||||
 | 
						"gemini-2.0-pro-exp-02-05",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										14
									
								
								relay/adaptor/geminiv2/main.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								relay/adaptor/geminiv2/main.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,14 @@
 | 
				
			|||||||
 | 
					package geminiv2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/meta"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetRequestURL(meta *meta.Meta) (string, error) {
 | 
				
			||||||
 | 
						baseURL := strings.TrimSuffix(meta.BaseURL, "/")
 | 
				
			||||||
 | 
						requestPath := strings.TrimPrefix(meta.RequestURLPath, "/v1")
 | 
				
			||||||
 | 
						return fmt.Sprintf("%s%s", baseURL, requestPath), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -3,7 +3,6 @@ package groq
 | 
				
			|||||||
// https://console.groq.com/docs/models
 | 
					// https://console.groq.com/docs/models
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var ModelList = []string{
 | 
					var ModelList = []string{
 | 
				
			||||||
	"gemma-7b-it",
 | 
					 | 
				
			||||||
	"gemma2-9b-it",
 | 
						"gemma2-9b-it",
 | 
				
			||||||
	"llama-3.1-70b-versatile",
 | 
						"llama-3.1-70b-versatile",
 | 
				
			||||||
	"llama-3.1-8b-instant",
 | 
						"llama-3.1-8b-instant",
 | 
				
			||||||
@@ -11,7 +10,6 @@ var ModelList = []string{
 | 
				
			|||||||
	"llama-3.2-11b-vision-preview",
 | 
						"llama-3.2-11b-vision-preview",
 | 
				
			||||||
	"llama-3.2-1b-preview",
 | 
						"llama-3.2-1b-preview",
 | 
				
			||||||
	"llama-3.2-3b-preview",
 | 
						"llama-3.2-3b-preview",
 | 
				
			||||||
	"llama-3.2-11b-vision-preview",
 | 
					 | 
				
			||||||
	"llama-3.2-90b-text-preview",
 | 
						"llama-3.2-90b-text-preview",
 | 
				
			||||||
	"llama-3.2-90b-vision-preview",
 | 
						"llama-3.2-90b-vision-preview",
 | 
				
			||||||
	"llama-guard-3-8b",
 | 
						"llama-guard-3-8b",
 | 
				
			||||||
@@ -24,4 +22,6 @@ var ModelList = []string{
 | 
				
			|||||||
	"distil-whisper-large-v3-en",
 | 
						"distil-whisper-large-v3-en",
 | 
				
			||||||
	"whisper-large-v3",
 | 
						"whisper-large-v3",
 | 
				
			||||||
	"whisper-large-v3-turbo",
 | 
						"whisper-large-v3-turbo",
 | 
				
			||||||
 | 
						"deepseek-r1-distill-llama-70b-specdec",
 | 
				
			||||||
 | 
						"deepseek-r1-distill-llama-70b",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,4 +8,6 @@ var ModelList = []string{
 | 
				
			|||||||
	"abab6-chat",
 | 
						"abab6-chat",
 | 
				
			||||||
	"abab5.5-chat",
 | 
						"abab5.5-chat",
 | 
				
			||||||
	"abab5.5s-chat",
 | 
						"abab5.5s-chat",
 | 
				
			||||||
 | 
						"MiniMax-VL-01",
 | 
				
			||||||
 | 
						"MiniMax-Text-01",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -8,8 +8,12 @@ import (
 | 
				
			|||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor"
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/alibailian"
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/baiduv2"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/doubao"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/doubao"
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/geminiv2"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/minimax"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/minimax"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/novita"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/novita"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/channeltype"
 | 
						"github.com/songquanpeng/one-api/relay/channeltype"
 | 
				
			||||||
@@ -52,6 +56,12 @@ func (a *Adaptor) GetRequestURL(meta *meta.Meta) (string, error) {
 | 
				
			|||||||
		return doubao.GetRequestURL(meta)
 | 
							return doubao.GetRequestURL(meta)
 | 
				
			||||||
	case channeltype.Novita:
 | 
						case channeltype.Novita:
 | 
				
			||||||
		return novita.GetRequestURL(meta)
 | 
							return novita.GetRequestURL(meta)
 | 
				
			||||||
 | 
						case channeltype.BaiduV2:
 | 
				
			||||||
 | 
							return baiduv2.GetRequestURL(meta)
 | 
				
			||||||
 | 
						case channeltype.AliBailian:
 | 
				
			||||||
 | 
							return alibailian.GetRequestURL(meta)
 | 
				
			||||||
 | 
						case channeltype.GeminiOpenAICompatible:
 | 
				
			||||||
 | 
							return geminiv2.GetRequestURL(meta)
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		return GetFullRequestURL(meta.BaseURL, meta.RequestURLPath, meta.ChannelType), nil
 | 
							return GetFullRequestURL(meta.BaseURL, meta.RequestURLPath, meta.ChannelType), nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -2,19 +2,24 @@ package openai
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/ai360"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/ai360"
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/alibailian"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/baichuan"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/baichuan"
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/baiduv2"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/deepseek"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/deepseek"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/doubao"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/doubao"
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/geminiv2"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/groq"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/groq"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/lingyiwanwu"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/lingyiwanwu"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/minimax"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/minimax"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/mistral"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/mistral"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/moonshot"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/moonshot"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/novita"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/novita"
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/openrouter"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/siliconflow"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/siliconflow"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/stepfun"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/stepfun"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/togetherai"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/togetherai"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/adaptor/xai"
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/xai"
 | 
				
			||||||
 | 
						"github.com/songquanpeng/one-api/relay/adaptor/xunfeiv2"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/relay/channeltype"
 | 
						"github.com/songquanpeng/one-api/relay/channeltype"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -34,6 +39,8 @@ var CompatibleChannels = []int{
 | 
				
			|||||||
	channeltype.Novita,
 | 
						channeltype.Novita,
 | 
				
			||||||
	channeltype.SiliconFlow,
 | 
						channeltype.SiliconFlow,
 | 
				
			||||||
	channeltype.XAI,
 | 
						channeltype.XAI,
 | 
				
			||||||
 | 
						channeltype.BaiduV2,
 | 
				
			||||||
 | 
						channeltype.XunfeiV2,
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetCompatibleChannelMeta(channelType int) (string, []string) {
 | 
					func GetCompatibleChannelMeta(channelType int) (string, []string) {
 | 
				
			||||||
@@ -68,6 +75,16 @@ func GetCompatibleChannelMeta(channelType int) (string, []string) {
 | 
				
			|||||||
		return "siliconflow", siliconflow.ModelList
 | 
							return "siliconflow", siliconflow.ModelList
 | 
				
			||||||
	case channeltype.XAI:
 | 
						case channeltype.XAI:
 | 
				
			||||||
		return "xai", xai.ModelList
 | 
							return "xai", xai.ModelList
 | 
				
			||||||
 | 
						case channeltype.BaiduV2:
 | 
				
			||||||
 | 
							return "baiduv2", baiduv2.ModelList
 | 
				
			||||||
 | 
						case channeltype.XunfeiV2:
 | 
				
			||||||
 | 
							return "xunfeiv2", xunfeiv2.ModelList
 | 
				
			||||||
 | 
						case channeltype.OpenRouter:
 | 
				
			||||||
 | 
							return "openrouter", openrouter.ModelList
 | 
				
			||||||
 | 
						case channeltype.AliBailian:
 | 
				
			||||||
 | 
							return "alibailian", alibailian.ModelList
 | 
				
			||||||
 | 
						case channeltype.GeminiOpenAICompatible:
 | 
				
			||||||
 | 
							return "geminiv2", geminiv2.ModelList
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		return "openai", ModelList
 | 
							return "openai", ModelList
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -17,6 +17,9 @@ func ResponseText2Usage(responseText string, modelName string, promptTokens int)
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetFullRequestURL(baseURL string, requestURL string, channelType int) string {
 | 
					func GetFullRequestURL(baseURL string, requestURL string, channelType int) string {
 | 
				
			||||||
 | 
						if channelType == channeltype.OpenAICompatible {
 | 
				
			||||||
 | 
							return fmt.Sprintf("%s%s", strings.TrimSuffix(baseURL, "/"), strings.TrimPrefix(requestURL, "/v1"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	fullRequestURL := fmt.Sprintf("%s%s", baseURL, requestURL)
 | 
						fullRequestURL := fmt.Sprintf("%s%s", baseURL, requestURL)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if strings.HasPrefix(baseURL, "https://gateway.ai.cloudflare.com") {
 | 
						if strings.HasPrefix(baseURL, "https://gateway.ai.cloudflare.com") {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										20
									
								
								relay/adaptor/openrouter/constants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										20
									
								
								relay/adaptor/openrouter/constants.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,20 @@
 | 
				
			|||||||
 | 
					package openrouter
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var ModelList = []string{
 | 
				
			||||||
 | 
						"openai/gpt-3.5-turbo",
 | 
				
			||||||
 | 
						"openai/chatgpt-4o-latest",
 | 
				
			||||||
 | 
						"openai/o1",
 | 
				
			||||||
 | 
						"openai/o1-preview",
 | 
				
			||||||
 | 
						"openai/o1-mini",
 | 
				
			||||||
 | 
						"openai/o3-mini",
 | 
				
			||||||
 | 
						"google/gemini-2.0-flash-001",
 | 
				
			||||||
 | 
						"google/gemini-2.0-flash-thinking-exp:free",
 | 
				
			||||||
 | 
						"google/gemini-2.0-flash-lite-preview-02-05:free",
 | 
				
			||||||
 | 
						"google/gemini-2.0-pro-exp-02-05:free",
 | 
				
			||||||
 | 
						"google/gemini-flash-1.5-8b",
 | 
				
			||||||
 | 
						"anthropic/claude-3.5-sonnet",
 | 
				
			||||||
 | 
						"anthropic/claude-3.5-haiku",
 | 
				
			||||||
 | 
						"deepseek/deepseek-r1:free",
 | 
				
			||||||
 | 
						"deepseek/deepseek-r1",
 | 
				
			||||||
 | 
						"qwen/qwen-vl-plus:free",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -16,10 +16,12 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
var ModelList = []string{
 | 
					var ModelList = []string{
 | 
				
			||||||
	"gemini-pro", "gemini-pro-vision",
 | 
						"gemini-pro", "gemini-pro-vision",
 | 
				
			||||||
	"gemini-1.5-pro-001", "gemini-1.5-flash-001",
 | 
						"gemini-exp-1206",
 | 
				
			||||||
	"gemini-1.5-pro-002", "gemini-1.5-flash-002",
 | 
						"gemini-1.5-pro-001", "gemini-1.5-pro-002",
 | 
				
			||||||
	"gemini-2.0-flash-exp",
 | 
						"gemini-1.5-flash-001", "gemini-1.5-flash-002",
 | 
				
			||||||
	"gemini-2.0-flash-thinking-exp", "gemini-2.0-flash-thinking-exp-01-21",
 | 
						"gemini-2.0-flash-exp", "gemini-2.0-flash-001",
 | 
				
			||||||
 | 
						"gemini-2.0-flash-lite-preview-02-05",
 | 
				
			||||||
 | 
						"gemini-2.0-flash-thinking-exp-01-21",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Adaptor struct {
 | 
					type Adaptor struct {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,5 +1,14 @@
 | 
				
			|||||||
package xai
 | 
					package xai
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//https://console.x.ai/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var ModelList = []string{
 | 
					var ModelList = []string{
 | 
				
			||||||
 | 
						"grok-2",
 | 
				
			||||||
 | 
						"grok-vision-beta",
 | 
				
			||||||
 | 
						"grok-2-vision-1212",
 | 
				
			||||||
 | 
						"grok-2-vision",
 | 
				
			||||||
 | 
						"grok-2-vision-latest",
 | 
				
			||||||
 | 
						"grok-2-1212",
 | 
				
			||||||
 | 
						"grok-2-latest",
 | 
				
			||||||
	"grok-beta",
 | 
						"grok-beta",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,12 +1,10 @@
 | 
				
			|||||||
package xunfei
 | 
					package xunfei
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var ModelList = []string{
 | 
					var ModelList = []string{
 | 
				
			||||||
	"SparkDesk",
 | 
						"Spark-Lite",
 | 
				
			||||||
	"SparkDesk-v1.1",
 | 
						"Spark-Pro",
 | 
				
			||||||
	"SparkDesk-v2.1",
 | 
						"Spark-Pro-128K",
 | 
				
			||||||
	"SparkDesk-v3.1",
 | 
						"Spark-Max",
 | 
				
			||||||
	"SparkDesk-v3.1-128K",
 | 
						"Spark-Max-32K",
 | 
				
			||||||
	"SparkDesk-v3.5",
 | 
						"Spark-4.0-Ultra",
 | 
				
			||||||
	"SparkDesk-v3.5-32K",
 | 
					 | 
				
			||||||
	"SparkDesk-v4.0",
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										97
									
								
								relay/adaptor/xunfei/domain.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										97
									
								
								relay/adaptor/xunfei/domain.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,97 @@
 | 
				
			|||||||
 | 
					package xunfei
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					//Spark4.0 Ultra 请求地址,对应的domain参数为4.0Ultra:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//wss://spark-api.xf-yun.com/v4.0/chat
 | 
				
			||||||
 | 
					//Spark Max-32K请求地址,对应的domain参数为max-32k
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//wss://spark-api.xf-yun.com/chat/max-32k
 | 
				
			||||||
 | 
					//Spark Max请求地址,对应的domain参数为generalv3.5
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//wss://spark-api.xf-yun.com/v3.5/chat
 | 
				
			||||||
 | 
					//Spark Pro-128K请求地址,对应的domain参数为pro-128k:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					// wss://spark-api.xf-yun.com/chat/pro-128k
 | 
				
			||||||
 | 
					//Spark Pro请求地址,对应的domain参数为generalv3:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//wss://spark-api.xf-yun.com/v3.1/chat
 | 
				
			||||||
 | 
					//Spark Lite请求地址,对应的domain参数为lite:
 | 
				
			||||||
 | 
					//
 | 
				
			||||||
 | 
					//wss://spark-api.xf-yun.com/v1.1/chat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Lite、Pro、Pro-128K、Max、Max-32K和4.0 Ultra
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func parseAPIVersionByModelName(modelName string) string {
 | 
				
			||||||
 | 
						apiVersion := modelName2APIVersion(modelName)
 | 
				
			||||||
 | 
						if apiVersion != "" {
 | 
				
			||||||
 | 
							return apiVersion
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						index := strings.IndexAny(modelName, "-")
 | 
				
			||||||
 | 
						if index != -1 {
 | 
				
			||||||
 | 
							return modelName[index+1:]
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func modelName2APIVersion(modelName string) string {
 | 
				
			||||||
 | 
						switch modelName {
 | 
				
			||||||
 | 
						case "Spark-Lite":
 | 
				
			||||||
 | 
							return "v1.1"
 | 
				
			||||||
 | 
						case "Spark-Pro":
 | 
				
			||||||
 | 
							return "v3.1"
 | 
				
			||||||
 | 
						case "Spark-Pro-128K":
 | 
				
			||||||
 | 
							return "v3.1-128K"
 | 
				
			||||||
 | 
						case "Spark-Max":
 | 
				
			||||||
 | 
							return "v3.5"
 | 
				
			||||||
 | 
						case "Spark-Max-32K":
 | 
				
			||||||
 | 
							return "v3.5-32K"
 | 
				
			||||||
 | 
						case "Spark-4.0-Ultra":
 | 
				
			||||||
 | 
							return "v4.0"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return ""
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
 | 
				
			||||||
 | 
					func apiVersion2domain(apiVersion string) string {
 | 
				
			||||||
 | 
						switch apiVersion {
 | 
				
			||||||
 | 
						case "v1.1":
 | 
				
			||||||
 | 
							return "lite"
 | 
				
			||||||
 | 
						case "v2.1":
 | 
				
			||||||
 | 
							return "generalv2"
 | 
				
			||||||
 | 
						case "v3.1":
 | 
				
			||||||
 | 
							return "generalv3"
 | 
				
			||||||
 | 
						case "v3.1-128K":
 | 
				
			||||||
 | 
							return "pro-128k"
 | 
				
			||||||
 | 
						case "v3.5":
 | 
				
			||||||
 | 
							return "generalv3.5"
 | 
				
			||||||
 | 
						case "v3.5-32K":
 | 
				
			||||||
 | 
							return "max-32k"
 | 
				
			||||||
 | 
						case "v4.0":
 | 
				
			||||||
 | 
							return "4.0Ultra"
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return "general" + apiVersion
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func getXunfeiAuthUrl(apiVersion string, apiKey string, apiSecret string) (string, string) {
 | 
				
			||||||
 | 
						var authUrl string
 | 
				
			||||||
 | 
						domain := apiVersion2domain(apiVersion)
 | 
				
			||||||
 | 
						switch apiVersion {
 | 
				
			||||||
 | 
						case "v3.1-128K":
 | 
				
			||||||
 | 
							authUrl = buildXunfeiAuthUrl(fmt.Sprintf("wss://spark-api.xf-yun.com/chat/pro-128k"), apiKey, apiSecret)
 | 
				
			||||||
 | 
							break
 | 
				
			||||||
 | 
						case "v3.5-32K":
 | 
				
			||||||
 | 
							authUrl = buildXunfeiAuthUrl(fmt.Sprintf("wss://spark-api.xf-yun.com/chat/max-32k"), apiKey, apiSecret)
 | 
				
			||||||
 | 
							break
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
 | 
							authUrl = buildXunfeiAuthUrl(fmt.Sprintf("wss://spark-api.xf-yun.com/%s/chat", apiVersion), apiKey, apiSecret)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return domain, authUrl
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -15,6 +15,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"github.com/gin-gonic/gin"
 | 
						"github.com/gin-gonic/gin"
 | 
				
			||||||
	"github.com/gorilla/websocket"
 | 
						"github.com/gorilla/websocket"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/songquanpeng/one-api/common"
 | 
						"github.com/songquanpeng/one-api/common"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/common/helper"
 | 
						"github.com/songquanpeng/one-api/common/helper"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/common/logger"
 | 
						"github.com/songquanpeng/one-api/common/logger"
 | 
				
			||||||
@@ -270,48 +271,3 @@ func xunfeiMakeRequest(textRequest model.GeneralOpenAIRequest, domain, authUrl,
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	return dataChan, stopChan, nil
 | 
						return dataChan, stopChan, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					 | 
				
			||||||
func parseAPIVersionByModelName(modelName string) string {
 | 
					 | 
				
			||||||
	index := strings.IndexAny(modelName, "-")
 | 
					 | 
				
			||||||
	if index != -1 {
 | 
					 | 
				
			||||||
		return modelName[index+1:]
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return ""
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
 | 
					 | 
				
			||||||
func apiVersion2domain(apiVersion string) string {
 | 
					 | 
				
			||||||
	switch apiVersion {
 | 
					 | 
				
			||||||
	case "v1.1":
 | 
					 | 
				
			||||||
		return "lite"
 | 
					 | 
				
			||||||
	case "v2.1":
 | 
					 | 
				
			||||||
		return "generalv2"
 | 
					 | 
				
			||||||
	case "v3.1":
 | 
					 | 
				
			||||||
		return "generalv3"
 | 
					 | 
				
			||||||
	case "v3.1-128K":
 | 
					 | 
				
			||||||
		return "pro-128k"
 | 
					 | 
				
			||||||
	case "v3.5":
 | 
					 | 
				
			||||||
		return "generalv3.5"
 | 
					 | 
				
			||||||
	case "v3.5-32K":
 | 
					 | 
				
			||||||
		return "max-32k"
 | 
					 | 
				
			||||||
	case "v4.0":
 | 
					 | 
				
			||||||
		return "4.0Ultra"
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return "general" + apiVersion
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
func getXunfeiAuthUrl(apiVersion string, apiKey string, apiSecret string) (string, string) {
 | 
					 | 
				
			||||||
	var authUrl string
 | 
					 | 
				
			||||||
	domain := apiVersion2domain(apiVersion)
 | 
					 | 
				
			||||||
	switch apiVersion {
 | 
					 | 
				
			||||||
	case "v3.1-128K":
 | 
					 | 
				
			||||||
		authUrl = buildXunfeiAuthUrl(fmt.Sprintf("wss://spark-api.xf-yun.com/chat/pro-128k"), apiKey, apiSecret)
 | 
					 | 
				
			||||||
		break
 | 
					 | 
				
			||||||
	case "v3.5-32K":
 | 
					 | 
				
			||||||
		authUrl = buildXunfeiAuthUrl(fmt.Sprintf("wss://spark-api.xf-yun.com/chat/max-32k"), apiKey, apiSecret)
 | 
					 | 
				
			||||||
		break
 | 
					 | 
				
			||||||
	default:
 | 
					 | 
				
			||||||
		authUrl = buildXunfeiAuthUrl(fmt.Sprintf("wss://spark-api.xf-yun.com/%s/chat", apiVersion), apiKey, apiSecret)
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	return domain, authUrl
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										12
									
								
								relay/adaptor/xunfeiv2/constants.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								relay/adaptor/xunfeiv2/constants.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,12 @@
 | 
				
			|||||||
 | 
					package xunfeiv2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://www.xfyun.cn/doc/spark/HTTP%E8%B0%83%E7%94%A8%E6%96%87%E6%A1%A3.html#_3-%E8%AF%B7%E6%B1%82%E8%AF%B4%E6%98%8E
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var ModelList = []string{
 | 
				
			||||||
 | 
						"lite",
 | 
				
			||||||
 | 
						"generalv3",
 | 
				
			||||||
 | 
						"pro-128k",
 | 
				
			||||||
 | 
						"generalv3.5",
 | 
				
			||||||
 | 
						"max-32k",
 | 
				
			||||||
 | 
						"4.0Ultra",
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,7 +1,14 @@
 | 
				
			|||||||
package zhipu
 | 
					package zhipu
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// https://open.bigmodel.cn/pricing
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var ModelList = []string{
 | 
					var ModelList = []string{
 | 
				
			||||||
	"chatglm_turbo", "chatglm_pro", "chatglm_std", "chatglm_lite",
 | 
						"glm-zero-preview", "glm-4-plus", "glm-4-0520", "glm-4-airx",
 | 
				
			||||||
	"glm-4", "glm-4v", "glm-3-turbo", "embedding-2",
 | 
						"glm-4-air", "glm-4-long", "glm-4-flashx", "glm-4-flash",
 | 
				
			||||||
	"cogview-3",
 | 
						"glm-4", "glm-3-turbo",
 | 
				
			||||||
 | 
						"glm-4v-plus", "glm-4v", "glm-4v-flash",
 | 
				
			||||||
 | 
						"cogview-3-plus", "cogview-3", "cogview-3-flash",
 | 
				
			||||||
 | 
						"cogviewx", "cogviewx-flash",
 | 
				
			||||||
 | 
						"charglm-4", "emohaa", "codegeex-4",
 | 
				
			||||||
 | 
						"embedding-2", "embedding-3",
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,8 +3,10 @@ package ratio
 | 
				
			|||||||
import (
 | 
					import (
 | 
				
			||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"github.com/songquanpeng/one-api/common/logger"
 | 
						"github.com/songquanpeng/one-api/common/logger"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var groupRatioLock sync.RWMutex
 | 
				
			||||||
var GroupRatio = map[string]float64{
 | 
					var GroupRatio = map[string]float64{
 | 
				
			||||||
	"default": 1,
 | 
						"default": 1,
 | 
				
			||||||
	"vip":     1,
 | 
						"vip":     1,
 | 
				
			||||||
@@ -20,11 +22,15 @@ func GroupRatio2JSONString() string {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func UpdateGroupRatioByJSONString(jsonStr string) error {
 | 
					func UpdateGroupRatioByJSONString(jsonStr string) error {
 | 
				
			||||||
 | 
						groupRatioLock.Lock()
 | 
				
			||||||
 | 
						defer groupRatioLock.Unlock()
 | 
				
			||||||
	GroupRatio = make(map[string]float64)
 | 
						GroupRatio = make(map[string]float64)
 | 
				
			||||||
	return json.Unmarshal([]byte(jsonStr), &GroupRatio)
 | 
						return json.Unmarshal([]byte(jsonStr), &GroupRatio)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetGroupRatio(name string) float64 {
 | 
					func GetGroupRatio(name string) float64 {
 | 
				
			||||||
 | 
						groupRatioLock.RLock()
 | 
				
			||||||
 | 
						defer groupRatioLock.RUnlock()
 | 
				
			||||||
	ratio, ok := GroupRatio[name]
 | 
						ratio, ok := GroupRatio[name]
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		logger.SysError("group ratio not found: " + name)
 | 
							logger.SysError("group ratio not found: " + name)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,7 @@ import (
 | 
				
			|||||||
	"encoding/json"
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
	"strings"
 | 
						"strings"
 | 
				
			||||||
 | 
						"sync"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/songquanpeng/one-api/common/logger"
 | 
						"github.com/songquanpeng/one-api/common/logger"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
@@ -15,6 +16,8 @@ const (
 | 
				
			|||||||
	RMB       = USD / USD2RMB
 | 
						RMB       = USD / USD2RMB
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var modelRatioLock sync.RWMutex
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// ModelRatio
 | 
					// ModelRatio
 | 
				
			||||||
// https://platform.openai.com/docs/models/model-endpoint-compatibility
 | 
					// https://platform.openai.com/docs/models/model-endpoint-compatibility
 | 
				
			||||||
// https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Blfmc9dlf
 | 
					// https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Blfmc9dlf
 | 
				
			||||||
@@ -82,15 +85,17 @@ var ModelRatio = map[string]float64{
 | 
				
			|||||||
	"text-moderation-latest":  0.1,
 | 
						"text-moderation-latest":  0.1,
 | 
				
			||||||
	"dall-e-2":                0.02 * USD, // $0.016 - $0.020 / image
 | 
						"dall-e-2":                0.02 * USD, // $0.016 - $0.020 / image
 | 
				
			||||||
	"dall-e-3":                0.04 * USD, // $0.040 - $0.120 / image
 | 
						"dall-e-3":                0.04 * USD, // $0.040 - $0.120 / image
 | 
				
			||||||
	// https://www.anthropic.com/api#pricing
 | 
						// https://docs.anthropic.com/en/docs/about-claude/models
 | 
				
			||||||
	"claude-instant-1.2":         0.8 / 1000 * USD,
 | 
						"claude-instant-1.2":         0.8 / 1000 * USD,
 | 
				
			||||||
	"claude-2.0":                 8.0 / 1000 * USD,
 | 
						"claude-2.0":                 8.0 / 1000 * USD,
 | 
				
			||||||
	"claude-2.1":                 8.0 / 1000 * USD,
 | 
						"claude-2.1":                 8.0 / 1000 * USD,
 | 
				
			||||||
	"claude-3-haiku-20240307":    0.25 / 1000 * USD,
 | 
						"claude-3-haiku-20240307":    0.25 / 1000 * USD,
 | 
				
			||||||
	"claude-3-5-haiku-20241022":  1.0 / 1000 * USD,
 | 
						"claude-3-5-haiku-20241022":  1.0 / 1000 * USD,
 | 
				
			||||||
 | 
						"claude-3-5-haiku-latest":    1.0 / 1000 * USD,
 | 
				
			||||||
	"claude-3-sonnet-20240229":   3.0 / 1000 * USD,
 | 
						"claude-3-sonnet-20240229":   3.0 / 1000 * USD,
 | 
				
			||||||
	"claude-3-5-sonnet-20240620": 3.0 / 1000 * USD,
 | 
						"claude-3-5-sonnet-20240620": 3.0 / 1000 * USD,
 | 
				
			||||||
	"claude-3-5-sonnet-20241022": 3.0 / 1000 * USD,
 | 
						"claude-3-5-sonnet-20241022": 3.0 / 1000 * USD,
 | 
				
			||||||
 | 
						"claude-3-5-sonnet-latest":   3.0 / 1000 * USD,
 | 
				
			||||||
	"claude-3-opus-20240229":     15.0 / 1000 * USD,
 | 
						"claude-3-opus-20240229":     15.0 / 1000 * USD,
 | 
				
			||||||
	// https://cloud.baidu.com/doc/WENXINWORKSHOP/s/hlrk4akp7
 | 
						// https://cloud.baidu.com/doc/WENXINWORKSHOP/s/hlrk4akp7
 | 
				
			||||||
	"ERNIE-4.0-8K":       0.120 * RMB,
 | 
						"ERNIE-4.0-8K":       0.120 * RMB,
 | 
				
			||||||
@@ -110,26 +115,49 @@ var ModelRatio = map[string]float64{
 | 
				
			|||||||
	"bge-large-en":       0.002 * RMB,
 | 
						"bge-large-en":       0.002 * RMB,
 | 
				
			||||||
	"tao-8k":             0.002 * RMB,
 | 
						"tao-8k":             0.002 * RMB,
 | 
				
			||||||
	// https://ai.google.dev/pricing
 | 
						// https://ai.google.dev/pricing
 | 
				
			||||||
	"gemini-pro":                          1, // $0.00025 / 1k characters -> $0.001 / 1k tokens
 | 
						// https://cloud.google.com/vertex-ai/generative-ai/pricing
 | 
				
			||||||
	"gemini-1.0-pro":                      1,
 | 
						// "gemma-2-2b-it":                       0,
 | 
				
			||||||
	"gemini-1.5-pro":                      1,
 | 
						// "gemma-2-9b-it":                       0,
 | 
				
			||||||
	"gemini-1.5-pro-001":                  1,
 | 
						// "gemma-2-27b-it":                      0,
 | 
				
			||||||
	"gemini-1.5-flash":                    1,
 | 
						"gemini-pro":                          0.25 * MILLI_USD, // $0.00025 / 1k characters -> $0.001 / 1k tokens
 | 
				
			||||||
	"gemini-1.5-flash-001":                1,
 | 
						"gemini-1.0-pro":                      0.125 * MILLI_USD,
 | 
				
			||||||
	"gemini-2.0-flash-exp":                1,
 | 
						"gemini-1.5-pro":                      1.25 * MILLI_USD,
 | 
				
			||||||
	"gemini-2.0-flash-thinking-exp":       1,
 | 
						"gemini-1.5-pro-001":                  1.25 * MILLI_USD,
 | 
				
			||||||
	"gemini-2.0-flash-thinking-exp-01-21": 1,
 | 
						"gemini-1.5-pro-experimental":         1.25 * MILLI_USD,
 | 
				
			||||||
 | 
						"gemini-1.5-flash":                    0.075 * MILLI_USD,
 | 
				
			||||||
 | 
						"gemini-1.5-flash-001":                0.075 * MILLI_USD,
 | 
				
			||||||
 | 
						"gemini-1.5-flash-8b":                 0.0375 * MILLI_USD,
 | 
				
			||||||
 | 
						"gemini-2.0-flash-exp":                0.075 * MILLI_USD,
 | 
				
			||||||
 | 
						"gemini-2.0-flash":                    0.15 * MILLI_USD,
 | 
				
			||||||
 | 
						"gemini-2.0-flash-001":                0.15 * MILLI_USD,
 | 
				
			||||||
 | 
						"gemini-2.0-flash-lite-preview-02-05": 0.075 * MILLI_USD,
 | 
				
			||||||
 | 
						"gemini-2.0-flash-thinking-exp-01-21": 0.075 * MILLI_USD,
 | 
				
			||||||
 | 
						"gemini-2.0-pro-exp-02-05":            1.25 * MILLI_USD,
 | 
				
			||||||
	"aqa":                                 1,
 | 
						"aqa":                                 1,
 | 
				
			||||||
	// https://open.bigmodel.cn/pricing
 | 
						// https://open.bigmodel.cn/pricing
 | 
				
			||||||
	"glm-4":         0.1 * RMB,
 | 
						"glm-zero-preview": 0.01 * RMB,
 | 
				
			||||||
	"glm-4v":        0.1 * RMB,
 | 
						"glm-4-plus":       0.05 * RMB,
 | 
				
			||||||
	"glm-3-turbo":   0.005 * RMB,
 | 
						"glm-4-0520":       0.1 * RMB,
 | 
				
			||||||
 | 
						"glm-4-airx":       0.01 * RMB,
 | 
				
			||||||
 | 
						"glm-4-air":        0.0005 * RMB,
 | 
				
			||||||
 | 
						"glm-4-long":       0.001 * RMB,
 | 
				
			||||||
 | 
						"glm-4-flashx":     0.0001 * RMB,
 | 
				
			||||||
 | 
						"glm-4-flash":      0,
 | 
				
			||||||
 | 
						"glm-4":            0.1 * RMB,   // deprecated model, available until 2025/06
 | 
				
			||||||
 | 
						"glm-3-turbo":      0.001 * RMB, // deprecated model, available until 2025/06
 | 
				
			||||||
 | 
						"glm-4v-plus":      0.004 * RMB,
 | 
				
			||||||
 | 
						"glm-4v":           0.05 * RMB,
 | 
				
			||||||
 | 
						"glm-4v-flash":     0,
 | 
				
			||||||
 | 
						"cogview-3-plus":   0.06 * RMB,
 | 
				
			||||||
 | 
						"cogview-3":        0.1 * RMB,
 | 
				
			||||||
 | 
						"cogview-3-flash":  0,
 | 
				
			||||||
 | 
						"cogviewx":         0.5 * RMB,
 | 
				
			||||||
 | 
						"cogviewx-flash":   0,
 | 
				
			||||||
 | 
						"charglm-4":        0.001 * RMB,
 | 
				
			||||||
 | 
						"emohaa":           0.015 * RMB,
 | 
				
			||||||
 | 
						"codegeex-4":       0.0001 * RMB,
 | 
				
			||||||
	"embedding-2":      0.0005 * RMB,
 | 
						"embedding-2":      0.0005 * RMB,
 | 
				
			||||||
	"chatglm_turbo": 0.3572, // ¥0.005 / 1k tokens
 | 
						"embedding-3":      0.0005 * RMB,
 | 
				
			||||||
	"chatglm_pro":   0.7143, // ¥0.01 / 1k tokens
 | 
					 | 
				
			||||||
	"chatglm_std":   0.3572, // ¥0.005 / 1k tokens
 | 
					 | 
				
			||||||
	"chatglm_lite":  0.1429, // ¥0.002 / 1k tokens
 | 
					 | 
				
			||||||
	"cogview-3":     0.25 * RMB,
 | 
					 | 
				
			||||||
	// https://help.aliyun.com/zh/dashscope/developer-reference/tongyi-thousand-questions-metering-and-billing
 | 
						// https://help.aliyun.com/zh/dashscope/developer-reference/tongyi-thousand-questions-metering-and-billing
 | 
				
			||||||
	"qwen-turbo":                  1.4286, // ¥0.02 / 1k tokens
 | 
						"qwen-turbo":                  1.4286, // ¥0.02 / 1k tokens
 | 
				
			||||||
	"qwen-turbo-latest":           1.4286,
 | 
						"qwen-turbo-latest":           1.4286,
 | 
				
			||||||
@@ -216,9 +244,19 @@ var ModelRatio = map[string]float64{
 | 
				
			|||||||
	"embedding-bert-512-v1":       0.0715, // ¥0.001 / 1k tokens
 | 
						"embedding-bert-512-v1":       0.0715, // ¥0.001 / 1k tokens
 | 
				
			||||||
	"embedding_s1_v1":             0.0715, // ¥0.001 / 1k tokens
 | 
						"embedding_s1_v1":             0.0715, // ¥0.001 / 1k tokens
 | 
				
			||||||
	"semantic_similarity_s1_v1":   0.0715, // ¥0.001 / 1k tokens
 | 
						"semantic_similarity_s1_v1":   0.0715, // ¥0.001 / 1k tokens
 | 
				
			||||||
	"hunyuan":                     7.143,  // ¥0.1 / 1k tokens  // https://cloud.tencent.com/document/product/1729/97731#e0e6be58-60c8-469f-bdeb-6c264ce3b4d0
 | 
						// https://cloud.tencent.com/document/product/1729/97731#e0e6be58-60c8-469f-bdeb-6c264ce3b4d0
 | 
				
			||||||
	"ChatStd":                     0.01 * RMB,
 | 
						"hunyuan-turbo":             0.015 * RMB,
 | 
				
			||||||
	"ChatPro":                     0.1 * RMB,
 | 
						"hunyuan-large":             0.004 * RMB,
 | 
				
			||||||
 | 
						"hunyuan-large-longcontext": 0.006 * RMB,
 | 
				
			||||||
 | 
						"hunyuan-standard":          0.0008 * RMB,
 | 
				
			||||||
 | 
						"hunyuan-standard-256K":     0.0005 * RMB,
 | 
				
			||||||
 | 
						"hunyuan-translation-lite":  0.005 * RMB,
 | 
				
			||||||
 | 
						"hunyuan-role":              0.004 * RMB,
 | 
				
			||||||
 | 
						"hunyuan-functioncall":      0.004 * RMB,
 | 
				
			||||||
 | 
						"hunyuan-code":              0.004 * RMB,
 | 
				
			||||||
 | 
						"hunyuan-turbo-vision":      0.08 * RMB,
 | 
				
			||||||
 | 
						"hunyuan-vision":            0.018 * RMB,
 | 
				
			||||||
 | 
						"hunyuan-embedding":         0.0007 * RMB,
 | 
				
			||||||
	// https://platform.moonshot.cn/pricing
 | 
						// https://platform.moonshot.cn/pricing
 | 
				
			||||||
	"moonshot-v1-8k":   0.012 * RMB,
 | 
						"moonshot-v1-8k":   0.012 * RMB,
 | 
				
			||||||
	"moonshot-v1-32k":  0.024 * RMB,
 | 
						"moonshot-v1-32k":  0.024 * RMB,
 | 
				
			||||||
@@ -391,11 +429,15 @@ func ModelRatio2JSONString() string {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func UpdateModelRatioByJSONString(jsonStr string) error {
 | 
					func UpdateModelRatioByJSONString(jsonStr string) error {
 | 
				
			||||||
 | 
						modelRatioLock.Lock()
 | 
				
			||||||
 | 
						defer modelRatioLock.Unlock()
 | 
				
			||||||
	ModelRatio = make(map[string]float64)
 | 
						ModelRatio = make(map[string]float64)
 | 
				
			||||||
	return json.Unmarshal([]byte(jsonStr), &ModelRatio)
 | 
						return json.Unmarshal([]byte(jsonStr), &ModelRatio)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetModelRatio(name string, channelType int) float64 {
 | 
					func GetModelRatio(name string, channelType int) float64 {
 | 
				
			||||||
 | 
						modelRatioLock.RLock()
 | 
				
			||||||
 | 
						defer modelRatioLock.RUnlock()
 | 
				
			||||||
	if strings.HasPrefix(name, "qwen-") && strings.HasSuffix(name, "-internet") {
 | 
						if strings.HasPrefix(name, "qwen-") && strings.HasSuffix(name, "-internet") {
 | 
				
			||||||
		name = strings.TrimSuffix(name, "-internet")
 | 
							name = strings.TrimSuffix(name, "-internet")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,5 +48,10 @@ const (
 | 
				
			|||||||
	SiliconFlow
 | 
						SiliconFlow
 | 
				
			||||||
	XAI
 | 
						XAI
 | 
				
			||||||
	Replicate
 | 
						Replicate
 | 
				
			||||||
 | 
						BaiduV2
 | 
				
			||||||
 | 
						XunfeiV2
 | 
				
			||||||
 | 
						AliBailian
 | 
				
			||||||
 | 
						OpenAICompatible
 | 
				
			||||||
 | 
						GeminiOpenAICompatible
 | 
				
			||||||
	Dummy
 | 
						Dummy
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -48,6 +48,12 @@ var ChannelBaseURLs = []string{
 | 
				
			|||||||
	"https://api.siliconflow.cn",                // 44
 | 
						"https://api.siliconflow.cn",                // 44
 | 
				
			||||||
	"https://api.x.ai",                          // 45
 | 
						"https://api.x.ai",                          // 45
 | 
				
			||||||
	"https://api.replicate.com/v1/models/",      // 46
 | 
						"https://api.replicate.com/v1/models/",      // 46
 | 
				
			||||||
 | 
						"https://qianfan.baidubce.com",              // 47
 | 
				
			||||||
 | 
						"https://spark-api-open.xf-yun.com",         // 48
 | 
				
			||||||
 | 
						"https://dashscope.aliyuncs.com",            // 49
 | 
				
			||||||
 | 
						"",                                          // 50
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"https://generativelanguage.googleapis.com/v1beta/openai/", // 51
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -38,7 +38,7 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode {
 | 
				
			|||||||
	textRequest.Model, _ = getMappedModelName(textRequest.Model, meta.ModelMapping)
 | 
						textRequest.Model, _ = getMappedModelName(textRequest.Model, meta.ModelMapping)
 | 
				
			||||||
	meta.ActualModelName = textRequest.Model
 | 
						meta.ActualModelName = textRequest.Model
 | 
				
			||||||
	// set system prompt if not empty
 | 
						// set system prompt if not empty
 | 
				
			||||||
	systemPromptReset := setSystemPrompt(ctx, textRequest, meta.SystemPrompt)
 | 
						systemPromptReset := setSystemPrompt(ctx, textRequest, meta.ForcedSystemPrompt)
 | 
				
			||||||
	// get model ratio & group ratio
 | 
						// get model ratio & group ratio
 | 
				
			||||||
	modelRatio := billingratio.GetModelRatio(textRequest.Model, meta.ChannelType)
 | 
						modelRatio := billingratio.GetModelRatio(textRequest.Model, meta.ChannelType)
 | 
				
			||||||
	groupRatio := billingratio.GetGroupRatio(meta.Group)
 | 
						groupRatio := billingratio.GetGroupRatio(meta.Group)
 | 
				
			||||||
@@ -88,7 +88,11 @@ func RelayTextHelper(c *gin.Context) *model.ErrorWithStatusCode {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func getRequestBody(c *gin.Context, meta *meta.Meta, textRequest *model.GeneralOpenAIRequest, adaptor adaptor.Adaptor) (io.Reader, error) {
 | 
					func getRequestBody(c *gin.Context, meta *meta.Meta, textRequest *model.GeneralOpenAIRequest, adaptor adaptor.Adaptor) (io.Reader, error) {
 | 
				
			||||||
	if !config.EnforceIncludeUsage && meta.APIType == apitype.OpenAI && meta.OriginModelName == meta.ActualModelName && meta.ChannelType != channeltype.Baichuan {
 | 
						if !config.EnforceIncludeUsage &&
 | 
				
			||||||
 | 
							meta.APIType == apitype.OpenAI &&
 | 
				
			||||||
 | 
							meta.OriginModelName == meta.ActualModelName &&
 | 
				
			||||||
 | 
							meta.ChannelType != channeltype.Baichuan &&
 | 
				
			||||||
 | 
							meta.ForcedSystemPrompt == "" {
 | 
				
			||||||
		// no need to convert request for openai
 | 
							// no need to convert request for openai
 | 
				
			||||||
		return c.Request.Body, nil
 | 
							return c.Request.Body, nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -33,7 +33,7 @@ type Meta struct {
 | 
				
			|||||||
	ActualModelName    string
 | 
						ActualModelName    string
 | 
				
			||||||
	RequestURLPath     string
 | 
						RequestURLPath     string
 | 
				
			||||||
	PromptTokens       int // only for DoResponse
 | 
						PromptTokens       int // only for DoResponse
 | 
				
			||||||
	SystemPrompt    string
 | 
						ForcedSystemPrompt string
 | 
				
			||||||
	StartTime          time.Time
 | 
						StartTime          time.Time
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -51,7 +51,7 @@ func GetByContext(c *gin.Context) *Meta {
 | 
				
			|||||||
		BaseURL:            c.GetString(ctxkey.BaseURL),
 | 
							BaseURL:            c.GetString(ctxkey.BaseURL),
 | 
				
			||||||
		APIKey:             strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "),
 | 
							APIKey:             strings.TrimPrefix(c.Request.Header.Get("Authorization"), "Bearer "),
 | 
				
			||||||
		RequestURLPath:     c.Request.URL.String(),
 | 
							RequestURLPath:     c.Request.URL.String(),
 | 
				
			||||||
		SystemPrompt:    c.GetString(ctxkey.SystemPrompt),
 | 
							ForcedSystemPrompt: c.GetString(ctxkey.SystemPrompt),
 | 
				
			||||||
		StartTime:          time.Now(),
 | 
							StartTime:          time.Now(),
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	cfg, ok := c.Get(ctxkey.Config)
 | 
						cfg, ok := c.Get(ctxkey.Config)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -26,6 +26,7 @@ type GeneralOpenAIRequest struct {
 | 
				
			|||||||
	Messages            []Message       `json:"messages,omitempty"`
 | 
						Messages            []Message       `json:"messages,omitempty"`
 | 
				
			||||||
	Model               string          `json:"model,omitempty"`
 | 
						Model               string          `json:"model,omitempty"`
 | 
				
			||||||
	Store               *bool           `json:"store,omitempty"`
 | 
						Store               *bool           `json:"store,omitempty"`
 | 
				
			||||||
 | 
						ReasoningEffort     *string         `json:"reasoning_effort,omitempty"`
 | 
				
			||||||
	Metadata            any             `json:"metadata,omitempty"`
 | 
						Metadata            any             `json:"metadata,omitempty"`
 | 
				
			||||||
	FrequencyPenalty    *float64        `json:"frequency_penalty,omitempty"`
 | 
						FrequencyPenalty    *float64        `json:"frequency_penalty,omitempty"`
 | 
				
			||||||
	LogitBias           any             `json:"logit_bias,omitempty"`
 | 
						LogitBias           any             `json:"logit_bias,omitempty"`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -3,6 +3,7 @@ package model
 | 
				
			|||||||
type Message struct {
 | 
					type Message struct {
 | 
				
			||||||
	Role             string  `json:"role,omitempty"`
 | 
						Role             string  `json:"role,omitempty"`
 | 
				
			||||||
	Content          any     `json:"content,omitempty"`
 | 
						Content          any     `json:"content,omitempty"`
 | 
				
			||||||
 | 
						ReasoningContent any     `json:"reasoning_content,omitempty"`
 | 
				
			||||||
	Name             *string `json:"name,omitempty"`
 | 
						Name             *string `json:"name,omitempty"`
 | 
				
			||||||
	ToolCalls        []Tool  `json:"tool_calls,omitempty"`
 | 
						ToolCalls        []Tool  `json:"tool_calls,omitempty"`
 | 
				
			||||||
	ToolCallId       string  `json:"tool_call_id,omitempty"`
 | 
						ToolCallId       string  `json:"tool_call_id,omitempty"`
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -4,6 +4,14 @@ type Usage struct {
 | 
				
			|||||||
	PromptTokens     int `json:"prompt_tokens"`
 | 
						PromptTokens     int `json:"prompt_tokens"`
 | 
				
			||||||
	CompletionTokens int `json:"completion_tokens"`
 | 
						CompletionTokens int `json:"completion_tokens"`
 | 
				
			||||||
	TotalTokens      int `json:"total_tokens"`
 | 
						TotalTokens      int `json:"total_tokens"`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CompletionTokensDetails *CompletionTokensDetails `json:"completion_tokens_details,omitempty"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type CompletionTokensDetails struct {
 | 
				
			||||||
 | 
						ReasoningTokens          int `json:"reasoning_tokens"`
 | 
				
			||||||
 | 
						AcceptedPredictionTokens int `json:"accepted_prediction_tokens"`
 | 
				
			||||||
 | 
						RejectedPredictionTokens int `json:"rejected_prediction_tokens"`
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type Error struct {
 | 
					type Error struct {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,7 +7,7 @@ export const CHANNEL_OPTIONS = [
 | 
				
			|||||||
  { key: 24, text: 'Google Gemini', value: 24, color: 'orange' },
 | 
					  { key: 24, text: 'Google Gemini', value: 24, color: 'orange' },
 | 
				
			||||||
  { key: 28, text: 'Mistral AI', value: 28, color: 'orange' },
 | 
					  { key: 28, text: 'Mistral AI', value: 28, color: 'orange' },
 | 
				
			||||||
  { key: 41, text: 'Novita', value: 41, color: 'purple' },
 | 
					  { key: 41, text: 'Novita', value: 41, color: 'purple' },
 | 
				
			||||||
  { key: 40, text: '字节跳动豆包', value: 40, color: 'blue' },
 | 
					  {key: 40, text: '字节火山引擎', value: 40, color: 'blue'},
 | 
				
			||||||
  { key: 15, text: '百度文心千帆', value: 15, color: 'blue' },
 | 
					  { key: 15, text: '百度文心千帆', value: 15, color: 'blue' },
 | 
				
			||||||
  { key: 17, text: '阿里通义千问', value: 17, color: 'orange' },
 | 
					  { key: 17, text: '阿里通义千问', value: 17, color: 'orange' },
 | 
				
			||||||
  { key: 18, text: '讯飞星火认知', value: 18, color: 'blue' },
 | 
					  { key: 18, text: '讯飞星火认知', value: 18, color: 'blue' },
 | 
				
			||||||
@@ -35,7 +35,7 @@ export const CHANNEL_OPTIONS = [
 | 
				
			|||||||
  { key: 8, text: '自定义渠道', value: 8, color: 'pink' },
 | 
					  { key: 8, text: '自定义渠道', value: 8, color: 'pink' },
 | 
				
			||||||
  { key: 22, text: '知识库:FastGPT', value: 22, color: 'blue' },
 | 
					  { key: 22, text: '知识库:FastGPT', value: 22, color: 'blue' },
 | 
				
			||||||
  { key: 21, text: '知识库:AI Proxy', value: 21, color: 'purple' },
 | 
					  { key: 21, text: '知识库:AI Proxy', value: 21, color: 'purple' },
 | 
				
			||||||
  { key: 20, text: '代理:OpenRouter', value: 20, color: 'black' },
 | 
					  {key: 20, text: 'OpenRouter', value: 20, color: 'black'},
 | 
				
			||||||
  { key: 2, text: '代理:API2D', value: 2, color: 'blue' },
 | 
					  { key: 2, text: '代理:API2D', value: 2, color: 'blue' },
 | 
				
			||||||
  { key: 5, text: '代理:OpenAI-SB', value: 5, color: 'brown' },
 | 
					  { key: 5, text: '代理:OpenAI-SB', value: 5, color: 'brown' },
 | 
				
			||||||
  { key: 7, text: '代理:OhMyGPT', value: 7, color: 'purple' },
 | 
					  { key: 7, text: '代理:OhMyGPT', value: 7, color: 'purple' },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -49,7 +49,7 @@ export const CHANNEL_OPTIONS = {
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  40: {
 | 
					  40: {
 | 
				
			||||||
    key: 40,
 | 
					    key: 40,
 | 
				
			||||||
    text: '字节跳动豆包',
 | 
					    text: '字节火山引擎',
 | 
				
			||||||
    value: 40,
 | 
					    value: 40,
 | 
				
			||||||
    color: 'primary'
 | 
					    color: 'primary'
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
@@ -217,7 +217,7 @@ export const CHANNEL_OPTIONS = {
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  20: {
 | 
					  20: {
 | 
				
			||||||
    key: 20,
 | 
					    key: 20,
 | 
				
			||||||
    text: '代理:OpenRouter',
 | 
					      text: 'OpenRouter',
 | 
				
			||||||
    value: 20,
 | 
					    value: 20,
 | 
				
			||||||
    color: 'success'
 | 
					    color: 'success'
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,6 @@
 | 
				
			|||||||
import React, {useEffect, useState} from 'react';
 | 
					import React, {useEffect, useState} from 'react';
 | 
				
			||||||
import {useTranslation} from 'react-i18next';
 | 
					import {useTranslation} from 'react-i18next';
 | 
				
			||||||
import {
 | 
					import {Button, Dropdown, Form, Input, Label, Message, Pagination, Popup, Table,} from 'semantic-ui-react';
 | 
				
			||||||
  Button,
 | 
					 | 
				
			||||||
  Dropdown,
 | 
					 | 
				
			||||||
  Form,
 | 
					 | 
				
			||||||
  Input,
 | 
					 | 
				
			||||||
  Label,
 | 
					 | 
				
			||||||
  Message,
 | 
					 | 
				
			||||||
  Pagination,
 | 
					 | 
				
			||||||
  Popup,
 | 
					 | 
				
			||||||
  Table,
 | 
					 | 
				
			||||||
} from 'semantic-ui-react';
 | 
					 | 
				
			||||||
import {Link} from 'react-router-dom';
 | 
					import {Link} from 'react-router-dom';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  API,
 | 
					  API,
 | 
				
			||||||
@@ -54,6 +44,9 @@ function renderType(type, t) {
 | 
				
			|||||||
function renderBalance(type, balance, t) {
 | 
					function renderBalance(type, balance, t) {
 | 
				
			||||||
  switch (type) {
 | 
					  switch (type) {
 | 
				
			||||||
    case 1: // OpenAI
 | 
					    case 1: // OpenAI
 | 
				
			||||||
 | 
					        if (balance === 0) {
 | 
				
			||||||
 | 
					            return <span>{t('channel.table.balance_not_supported')}</span>;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
      return <span>${balance.toFixed(2)}</span>;
 | 
					      return <span>${balance.toFixed(2)}</span>;
 | 
				
			||||||
    case 4: // CloseAI
 | 
					    case 4: // CloseAI
 | 
				
			||||||
      return <span>¥{balance.toFixed(2)}</span>;
 | 
					      return <span>¥{balance.toFixed(2)}</span>;
 | 
				
			||||||
@@ -67,6 +60,8 @@ function renderBalance(type, balance, t) {
 | 
				
			|||||||
      return <span>¥{balance.toFixed(2)}</span>;
 | 
					      return <span>¥{balance.toFixed(2)}</span>;
 | 
				
			||||||
    case 13: // AIGC2D
 | 
					    case 13: // AIGC2D
 | 
				
			||||||
      return <span>{renderNumber(balance)}</span>;
 | 
					      return <span>{renderNumber(balance)}</span>;
 | 
				
			||||||
 | 
					    case 20: // OpenRouter
 | 
				
			||||||
 | 
					      return <span>${balance.toFixed(2)}</span>;
 | 
				
			||||||
    case 36: // DeepSeek
 | 
					    case 36: // DeepSeek
 | 
				
			||||||
      return <span>¥{balance.toFixed(2)}</span>;
 | 
					      return <span>¥{balance.toFixed(2)}</span>;
 | 
				
			||||||
    case 44: // SiliconFlow
 | 
					    case 44: // SiliconFlow
 | 
				
			||||||
@@ -93,11 +88,7 @@ const ChannelsTable = () => {
 | 
				
			|||||||
  const [showPrompt, setShowPrompt] = useState(shouldShowPrompt(promptID));
 | 
					  const [showPrompt, setShowPrompt] = useState(shouldShowPrompt(promptID));
 | 
				
			||||||
  const [showDetail, setShowDetail] = useState(isShowDetail());
 | 
					  const [showDetail, setShowDetail] = useState(isShowDetail());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const loadChannels = async (startIdx) => {
 | 
					  const processChannelData = (channel) => {
 | 
				
			||||||
    const res = await API.get(`/api/channel/?p=${startIdx}`);
 | 
					 | 
				
			||||||
    const { success, message, data } = res.data;
 | 
					 | 
				
			||||||
    if (success) {
 | 
					 | 
				
			||||||
      let localChannels = data.map((channel) => {
 | 
					 | 
				
			||||||
    if (channel.models === '') {
 | 
					    if (channel.models === '') {
 | 
				
			||||||
      channel.models = [];
 | 
					      channel.models = [];
 | 
				
			||||||
      channel.test_model = '';
 | 
					      channel.test_model = '';
 | 
				
			||||||
@@ -116,7 +107,13 @@ const ChannelsTable = () => {
 | 
				
			|||||||
      console.log('channel', channel);
 | 
					      console.log('channel', channel);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return channel;
 | 
					    return channel;
 | 
				
			||||||
      });
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const loadChannels = async (startIdx) => {
 | 
				
			||||||
 | 
					    const res = await API.get(`/api/channel/?p=${startIdx}`);
 | 
				
			||||||
 | 
					    const { success, message, data } = res.data;
 | 
				
			||||||
 | 
					    if (success) {
 | 
				
			||||||
 | 
					      let localChannels = data.map(processChannelData);
 | 
				
			||||||
      if (startIdx === 0) {
 | 
					      if (startIdx === 0) {
 | 
				
			||||||
        setChannels(localChannels);
 | 
					        setChannels(localChannels);
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
@@ -301,7 +298,8 @@ const ChannelsTable = () => {
 | 
				
			|||||||
    const res = await API.get(`/api/channel/search?keyword=${searchKeyword}`);
 | 
					    const res = await API.get(`/api/channel/search?keyword=${searchKeyword}`);
 | 
				
			||||||
    const { success, message, data } = res.data;
 | 
					    const { success, message, data } = res.data;
 | 
				
			||||||
    if (success) {
 | 
					    if (success) {
 | 
				
			||||||
      setChannels(data);
 | 
					      let localChannels = data.map(processChannelData);
 | 
				
			||||||
 | 
					      setChannels(localChannels);
 | 
				
			||||||
      setActivePage(1);
 | 
					      setActivePage(1);
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      showError(message);
 | 
					      showError(message);
 | 
				
			||||||
@@ -495,7 +493,6 @@ const ChannelsTable = () => {
 | 
				
			|||||||
              onClick={() => {
 | 
					              onClick={() => {
 | 
				
			||||||
                sortChannel('balance');
 | 
					                sortChannel('balance');
 | 
				
			||||||
              }}
 | 
					              }}
 | 
				
			||||||
              hidden={!showDetail}
 | 
					 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
              {t('channel.table.balance')}
 | 
					              {t('channel.table.balance')}
 | 
				
			||||||
            </Table.HeaderCell>
 | 
					            </Table.HeaderCell>
 | 
				
			||||||
@@ -504,6 +501,7 @@ const ChannelsTable = () => {
 | 
				
			|||||||
              onClick={() => {
 | 
					              onClick={() => {
 | 
				
			||||||
                sortChannel('priority');
 | 
					                sortChannel('priority');
 | 
				
			||||||
              }}
 | 
					              }}
 | 
				
			||||||
 | 
					              hidden={!showDetail}
 | 
				
			||||||
            >
 | 
					            >
 | 
				
			||||||
              {t('channel.table.priority')}
 | 
					              {t('channel.table.priority')}
 | 
				
			||||||
            </Table.HeaderCell>
 | 
					            </Table.HeaderCell>
 | 
				
			||||||
@@ -543,7 +541,7 @@ const ChannelsTable = () => {
 | 
				
			|||||||
                      basic
 | 
					                      basic
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                  </Table.Cell>
 | 
					                  </Table.Cell>
 | 
				
			||||||
                  <Table.Cell hidden={!showDetail}>
 | 
					                  <Table.Cell>
 | 
				
			||||||
                    <Popup
 | 
					                    <Popup
 | 
				
			||||||
                      trigger={
 | 
					                      trigger={
 | 
				
			||||||
                        <span
 | 
					                        <span
 | 
				
			||||||
@@ -559,7 +557,7 @@ const ChannelsTable = () => {
 | 
				
			|||||||
                      basic
 | 
					                      basic
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                  </Table.Cell>
 | 
					                  </Table.Cell>
 | 
				
			||||||
                  <Table.Cell>
 | 
					                  <Table.Cell hidden={!showDetail}>
 | 
				
			||||||
                    <Popup
 | 
					                    <Popup
 | 
				
			||||||
                      trigger={
 | 
					                      trigger={
 | 
				
			||||||
                        <Input
 | 
					                        <Input
 | 
				
			||||||
@@ -593,7 +591,15 @@ const ChannelsTable = () => {
 | 
				
			|||||||
                    />
 | 
					                    />
 | 
				
			||||||
                  </Table.Cell>
 | 
					                  </Table.Cell>
 | 
				
			||||||
                  <Table.Cell>
 | 
					                  <Table.Cell>
 | 
				
			||||||
                    <div>
 | 
					                    <div
 | 
				
			||||||
 | 
					                      style={{
 | 
				
			||||||
 | 
					                        display: 'flex',
 | 
				
			||||||
 | 
					                        alignItems: 'center',
 | 
				
			||||||
 | 
					                        flexWrap: 'wrap',
 | 
				
			||||||
 | 
					                        gap: '2px',
 | 
				
			||||||
 | 
					                        rowGap: '6px',
 | 
				
			||||||
 | 
					                      }}
 | 
				
			||||||
 | 
					                    >
 | 
				
			||||||
                      <Button
 | 
					                      <Button
 | 
				
			||||||
                        size={'tiny'}
 | 
					                        size={'tiny'}
 | 
				
			||||||
                        positive
 | 
					                        positive
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,69 @@
 | 
				
			|||||||
export const CHANNEL_OPTIONS = [
 | 
					export const CHANNEL_OPTIONS = [
 | 
				
			||||||
  { key: 1, text: 'OpenAI', value: 1, color: 'green' },
 | 
					  { key: 1, text: 'OpenAI', value: 1, color: 'green' },
 | 
				
			||||||
    { key: 14, text: 'Anthropic Claude', value: 14, color: 'black' },
 | 
					  {
 | 
				
			||||||
 | 
					    key: 50,
 | 
				
			||||||
 | 
					    text: 'OpenAI 兼容',
 | 
				
			||||||
 | 
					    value: 50,
 | 
				
			||||||
 | 
					    color: 'olive',
 | 
				
			||||||
 | 
					    description: 'OpenAI 兼容渠道,支持设置 Base URL',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {key: 14, text: 'Anthropic', value: 14, color: 'black'},
 | 
				
			||||||
  { key: 33, text: 'AWS', value: 33, color: 'black' },
 | 
					  { key: 33, text: 'AWS', value: 33, color: 'black' },
 | 
				
			||||||
    { key: 3, text: 'Azure OpenAI', value: 3, color: 'olive' },
 | 
					  {key: 3, text: 'Azure', value: 3, color: 'olive'},
 | 
				
			||||||
    { key: 11, text: 'Google PaLM2', value: 11, color: 'orange' },
 | 
					  {key: 11, text: 'PaLM2', value: 11, color: 'orange'},
 | 
				
			||||||
    { key: 24, text: 'Google Gemini', value: 24, color: 'orange' },
 | 
					  {key: 24, text: 'Gemini', value: 24, color: 'orange'},
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    key: 51,
 | 
				
			||||||
 | 
					    text: 'Gemini (OpenAI)',
 | 
				
			||||||
 | 
					    value: 51,
 | 
				
			||||||
 | 
					    color: 'orange',
 | 
				
			||||||
 | 
					    description: 'Gemini OpenAI 兼容格式',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  { key: 28, text: 'Mistral AI', value: 28, color: 'orange' },
 | 
					  { key: 28, text: 'Mistral AI', value: 28, color: 'orange' },
 | 
				
			||||||
  { key: 41, text: 'Novita', value: 41, color: 'purple' },
 | 
					  { key: 41, text: 'Novita', value: 41, color: 'purple' },
 | 
				
			||||||
    { key: 40, text: '字节跳动豆包', value: 40, color: 'blue' },
 | 
					  {
 | 
				
			||||||
    { key: 15, text: '百度文心千帆', value: 15, color: 'blue' },
 | 
					    key: 40,
 | 
				
			||||||
    { key: 17, text: '阿里通义千问', value: 17, color: 'orange' },
 | 
					    text: '字节火山引擎',
 | 
				
			||||||
    { key: 18, text: '讯飞星火认知', value: 18, color: 'blue' },
 | 
					    value: 40,
 | 
				
			||||||
 | 
					    color: 'blue',
 | 
				
			||||||
 | 
					    description: '原字节跳动豆包',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    key: 15,
 | 
				
			||||||
 | 
					    text: '百度文心千帆',
 | 
				
			||||||
 | 
					    value: 15,
 | 
				
			||||||
 | 
					    color: 'blue',
 | 
				
			||||||
 | 
					    tip: '请前往<a href="https://console.bce.baidu.com/qianfan/ais/console/applicationConsole/application/v1" target="_blank">此处</a>获取 AK(API Key)以及 SK(Secret Key),注意,V2 版本接口请使用 <strong>百度文心千帆 V2 </strong>渠道类型',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    key: 47,
 | 
				
			||||||
 | 
					    text: '百度文心千帆 V2',
 | 
				
			||||||
 | 
					    value: 47,
 | 
				
			||||||
 | 
					    color: 'blue',
 | 
				
			||||||
 | 
					    tip: '请前往<a href="https://console.bce.baidu.com/iam/#/iam/apikey/list" target="_blank">此处</a>获取 API Key,注意本渠道仅支持<a target="_blank" href="https://cloud.baidu.com/doc/WENXINWORKSHOP/s/em4tsqo3v">推理服务 V2</a>相关模型',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    key: 17,
 | 
				
			||||||
 | 
					    text: '阿里通义千问',
 | 
				
			||||||
 | 
					    value: 17,
 | 
				
			||||||
 | 
					    color: 'orange',
 | 
				
			||||||
 | 
					    tip: '如需使用阿里云百炼,请使用<strong>阿里云百炼</strong>渠道',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  { key: 49, text: '阿里云百炼', value: 49, color: 'orange' },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    key: 18,
 | 
				
			||||||
 | 
					    text: '讯飞星火认知',
 | 
				
			||||||
 | 
					    value: 18,
 | 
				
			||||||
 | 
					    color: 'blue',
 | 
				
			||||||
 | 
					    tip: '本渠道基于讯飞 WebSocket 版本 API,如需 HTTP 版本,请使用<strong>讯飞星火认知 V2</strong>渠道',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    key: 48,
 | 
				
			||||||
 | 
					    text: '讯飞星火认知 V2',
 | 
				
			||||||
 | 
					    value: 48,
 | 
				
			||||||
 | 
					    color: 'blue',
 | 
				
			||||||
 | 
					    tip: 'HTTP 版本的讯飞接口,前往<a href="https://console.xfyun.cn/services/cbm" target="_blank">此处</a>获取 HTTP 服务接口认证密钥',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  { key: 16, text: '智谱 ChatGLM', value: 16, color: 'violet' },
 | 
					  { key: 16, text: '智谱 ChatGLM', value: 16, color: 'violet' },
 | 
				
			||||||
  { key: 19, text: '360 智脑', value: 19, color: 'blue' },
 | 
					  { key: 19, text: '360 智脑', value: 19, color: 'blue' },
 | 
				
			||||||
  { key: 25, text: 'Moonshot AI', value: 25, color: 'black' },
 | 
					  { key: 25, text: 'Moonshot AI', value: 25, color: 'black' },
 | 
				
			||||||
@@ -32,10 +85,17 @@ export const CHANNEL_OPTIONS = [
 | 
				
			|||||||
  { key: 44, text: 'SiliconFlow', value: 44, color: 'blue' },
 | 
					  { key: 44, text: 'SiliconFlow', value: 44, color: 'blue' },
 | 
				
			||||||
  { key: 45, text: 'xAI', value: 45, color: 'blue' },
 | 
					  { key: 45, text: 'xAI', value: 45, color: 'blue' },
 | 
				
			||||||
  { key: 46, text: 'Replicate', value: 46, color: 'blue' },
 | 
					  { key: 46, text: 'Replicate', value: 46, color: 'blue' },
 | 
				
			||||||
    { key: 8, text: '自定义渠道', value: 8, color: 'pink' },
 | 
					  {
 | 
				
			||||||
 | 
					    key: 8,
 | 
				
			||||||
 | 
					    text: '自定义渠道',
 | 
				
			||||||
 | 
					    value: 8,
 | 
				
			||||||
 | 
					    color: 'pink',
 | 
				
			||||||
 | 
					    tip: '不推荐使用,请使用 <strong>OpenAI 兼容</strong>渠道类型。注意,这里所需要填入的代理地址仅会在实际请求时替换域名部分,如果你想填入 OpenAI SDK 中所要求的 Base URL,请使用 OpenAI 兼容渠道类型',
 | 
				
			||||||
 | 
					    description: '不推荐使用,请使用 OpenAI 兼容渠道类型',
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  { key: 22, text: '知识库:FastGPT', value: 22, color: 'blue' },
 | 
					  { key: 22, text: '知识库:FastGPT', value: 22, color: 'blue' },
 | 
				
			||||||
  { key: 21, text: '知识库:AI Proxy', value: 21, color: 'purple' },
 | 
					  { key: 21, text: '知识库:AI Proxy', value: 21, color: 'purple' },
 | 
				
			||||||
    { key: 20, text: '代理:OpenRouter', value: 20, color: 'black' },
 | 
					  { key: 20, text: 'OpenRouter', value: 20, color: 'black' },
 | 
				
			||||||
  { key: 2, text: '代理:API2D', value: 2, color: 'blue' },
 | 
					  { key: 2, text: '代理:API2D', value: 2, color: 'blue' },
 | 
				
			||||||
  { key: 5, text: '代理:OpenAI-SB', value: 5, color: 'brown' },
 | 
					  { key: 5, text: '代理:OpenAI-SB', value: 5, color: 'brown' },
 | 
				
			||||||
  { key: 7, text: '代理:OhMyGPT', value: 7, color: 'purple' },
 | 
					  { key: 7, text: '代理:OhMyGPT', value: 7, color: 'purple' },
 | 
				
			||||||
@@ -44,5 +104,5 @@ export const CHANNEL_OPTIONS = [
 | 
				
			|||||||
  { key: 6, text: '代理:OpenAI Max', value: 6, color: 'violet' },
 | 
					  { key: 6, text: '代理:OpenAI Max', value: 6, color: 'violet' },
 | 
				
			||||||
  { key: 9, text: '代理:AI.LS', value: 9, color: 'yellow' },
 | 
					  { key: 9, text: '代理:AI.LS', value: 9, color: 'yellow' },
 | 
				
			||||||
  { key: 12, text: '代理:API2GPT', value: 12, color: 'blue' },
 | 
					  { key: 12, text: '代理:API2GPT', value: 12, color: 'blue' },
 | 
				
			||||||
    { key: 13, text: '代理:AIGC2D', value: 13, color: 'purple' }
 | 
					  { key: 13, text: '代理:AIGC2D', value: 13, color: 'purple' },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
export const toastConstants = {
 | 
					export const toastConstants = {
 | 
				
			||||||
  SUCCESS_TIMEOUT: 1500,
 | 
					  SUCCESS_TIMEOUT: 5000,
 | 
				
			||||||
  INFO_TIMEOUT: 3000,
 | 
					  INFO_TIMEOUT: 8000,
 | 
				
			||||||
  ERROR_TIMEOUT: 5000,
 | 
					  ERROR_TIMEOUT: 10000,
 | 
				
			||||||
  WARNING_TIMEOUT: 10000,
 | 
					  WARNING_TIMEOUT: 10000,
 | 
				
			||||||
  NOTICE_TIMEOUT: 20000
 | 
					  NOTICE_TIMEOUT: 20000,
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										13
									
								
								web/default/src/helpers/helper.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								web/default/src/helpers/helper.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import {CHANNEL_OPTIONS} from '../constants';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let channelMap = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function getChannelOption(channelId) {
 | 
				
			||||||
 | 
					    if (channelMap === undefined) {
 | 
				
			||||||
 | 
					        channelMap = {};
 | 
				
			||||||
 | 
					        CHANNEL_OPTIONS.forEach((option) => {
 | 
				
			||||||
 | 
					            channelMap[option.key] = option;
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    return channelMap[channelId];
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,5 +1,6 @@
 | 
				
			|||||||
import { Label } from 'semantic-ui-react';
 | 
					import { Label, Message } from 'semantic-ui-react';
 | 
				
			||||||
import { useTranslation } from 'react-i18next';
 | 
					import { getChannelOption } from './helper';
 | 
				
			||||||
 | 
					import React from 'react';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function renderText(text, limit) {
 | 
					export function renderText(text, limit) {
 | 
				
			||||||
  if (text.length > limit) {
 | 
					  if (text.length > limit) {
 | 
				
			||||||
@@ -15,7 +16,15 @@ export function renderGroup(group) {
 | 
				
			|||||||
  let groups = group.split(',');
 | 
					  let groups = group.split(',');
 | 
				
			||||||
  groups.sort();
 | 
					  groups.sort();
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    <>
 | 
					    <div
 | 
				
			||||||
 | 
					      style={{
 | 
				
			||||||
 | 
					        display: 'flex',
 | 
				
			||||||
 | 
					        alignItems: 'center',
 | 
				
			||||||
 | 
					        flexWrap: 'wrap',
 | 
				
			||||||
 | 
					        gap: '2px',
 | 
				
			||||||
 | 
					        rowGap: '6px',
 | 
				
			||||||
 | 
					      }}
 | 
				
			||||||
 | 
					    >
 | 
				
			||||||
      {groups.map((group) => {
 | 
					      {groups.map((group) => {
 | 
				
			||||||
        if (group === 'vip' || group === 'pro') {
 | 
					        if (group === 'vip' || group === 'pro') {
 | 
				
			||||||
          return <Label color='yellow'>{group}</Label>;
 | 
					          return <Label color='yellow'>{group}</Label>;
 | 
				
			||||||
@@ -24,7 +33,7 @@ export function renderGroup(group) {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
        return <Label>{group}</Label>;
 | 
					        return <Label>{group}</Label>;
 | 
				
			||||||
      })}
 | 
					      })}
 | 
				
			||||||
    </>
 | 
					    </div>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -98,3 +107,15 @@ export function renderColorLabel(text) {
 | 
				
			|||||||
    </Label>
 | 
					    </Label>
 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export function renderChannelTip(channelId) {
 | 
				
			||||||
 | 
					  let channel = getChannelOption(channelId);
 | 
				
			||||||
 | 
					  if (channel === undefined || channel.tip === undefined) {
 | 
				
			||||||
 | 
					    return <></>;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					  return (
 | 
				
			||||||
 | 
					    <Message>
 | 
				
			||||||
 | 
					      <div dangerouslySetInnerHTML={{ __html: channel.tip }}></div>
 | 
				
			||||||
 | 
					    </Message>
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -74,6 +74,7 @@ if (isMobile()) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function showError(error) {
 | 
					export function showError(error) {
 | 
				
			||||||
 | 
					  if (!error) return;
 | 
				
			||||||
  console.error(error);
 | 
					  console.error(error);
 | 
				
			||||||
  if (error.message) {
 | 
					  if (error.message) {
 | 
				
			||||||
    if (error.name === 'AxiosError') {
 | 
					    if (error.name === 'AxiosError') {
 | 
				
			||||||
@@ -158,17 +159,7 @@ export function timestamp2string(timestamp) {
 | 
				
			|||||||
    second = '0' + second;
 | 
					    second = '0' + second;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return (
 | 
					  return (
 | 
				
			||||||
    year +
 | 
					      year + '-' + month + '-' + day + ' ' + hour + ':' + minute + ':' + second
 | 
				
			||||||
    '-' +
 | 
					 | 
				
			||||||
    month +
 | 
					 | 
				
			||||||
    '-' +
 | 
					 | 
				
			||||||
    day +
 | 
					 | 
				
			||||||
    ' ' +
 | 
					 | 
				
			||||||
    hour +
 | 
					 | 
				
			||||||
    ':' +
 | 
					 | 
				
			||||||
    minute +
 | 
					 | 
				
			||||||
    ':' +
 | 
					 | 
				
			||||||
    second
 | 
					 | 
				
			||||||
  );
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -193,7 +184,6 @@ export const verifyJSON = (str) => {
 | 
				
			|||||||
export function shouldShowPrompt(id) {
 | 
					export function shouldShowPrompt(id) {
 | 
				
			||||||
  let prompt = localStorage.getItem(`prompt-${id}`);
 | 
					  let prompt = localStorage.getItem(`prompt-${id}`);
 | 
				
			||||||
  return !prompt;
 | 
					  return !prompt;
 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function setPromptShown(id) {
 | 
					export function setPromptShown(id) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,7 +62,8 @@
 | 
				
			|||||||
      "not_tested": "Not Tested",
 | 
					      "not_tested": "Not Tested",
 | 
				
			||||||
      "priority_tip": "Channel selection priority, higher is preferred",
 | 
					      "priority_tip": "Channel selection priority, higher is preferred",
 | 
				
			||||||
      "select_test_model": "Please select test model",
 | 
					      "select_test_model": "Please select test model",
 | 
				
			||||||
      "click_to_update": "Click to update"
 | 
					      "click_to_update": "Click to update",
 | 
				
			||||||
 | 
					      "balance_not_supported": "-"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
      "test": "Test",
 | 
					      "test": "Test",
 | 
				
			||||||
@@ -85,7 +86,8 @@
 | 
				
			|||||||
      "test_all_started": "Channel testing started successfully, please refresh page to see results.",
 | 
					      "test_all_started": "Channel testing started successfully, please refresh page to see results.",
 | 
				
			||||||
      "delete_disabled_success": "Deleted all disabled channels, total: {{count}}",
 | 
					      "delete_disabled_success": "Deleted all disabled channels, total: {{count}}",
 | 
				
			||||||
      "balance_update_success": "Channel {{name}} balance updated successfully!",
 | 
					      "balance_update_success": "Channel {{name}} balance updated successfully!",
 | 
				
			||||||
      "all_balance_updated": "All enabled channel balances have been updated!"
 | 
					      "all_balance_updated": "All enabled channel balances have been updated!",
 | 
				
			||||||
 | 
					      "operation_success": "Operation completed successfully!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "edit": {
 | 
					    "edit": {
 | 
				
			||||||
      "title_edit": "Update Channel Information",
 | 
					      "title_edit": "Update Channel Information",
 | 
				
			||||||
@@ -102,8 +104,10 @@
 | 
				
			|||||||
      "model_mapping_placeholder": "Optional, used to modify model names in request body. A JSON string where keys are request model names and values are target model names",
 | 
					      "model_mapping_placeholder": "Optional, used to modify model names in request body. A JSON string where keys are request model names and values are target model names",
 | 
				
			||||||
      "system_prompt": "System Prompt",
 | 
					      "system_prompt": "System Prompt",
 | 
				
			||||||
      "system_prompt_placeholder": "Optional, used to force set system prompt. Use with custom model & model mapping. First create a unique custom model name above, then map it to a natively supported model",
 | 
					      "system_prompt_placeholder": "Optional, used to force set system prompt. Use with custom model & model mapping. First create a unique custom model name above, then map it to a natively supported model",
 | 
				
			||||||
      "base_url": "Proxy",
 | 
					      "proxy_url": "Proxy",
 | 
				
			||||||
      "base_url_placeholder": "Optional, used for API calls through proxy. Enter proxy address in format: https://domain.com",
 | 
					      "proxy_url_placeholder": "This is optional and used for API calls via a proxy. Please enter the proxy URL, formatted as: https://domain.com",
 | 
				
			||||||
 | 
					      "base_url": "Base URL",
 | 
				
			||||||
 | 
					      "base_url_placeholder": "The Base URL required by the OpenAPI SDK",
 | 
				
			||||||
      "key": "Key",
 | 
					      "key": "Key",
 | 
				
			||||||
      "key_placeholder": "Please enter key",
 | 
					      "key_placeholder": "Please enter key",
 | 
				
			||||||
      "batch": "Batch Create",
 | 
					      "batch": "Batch Create",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -62,7 +62,8 @@
 | 
				
			|||||||
      "not_tested": "未测试",
 | 
					      "not_tested": "未测试",
 | 
				
			||||||
      "priority_tip": "渠道选择优先级,越高越优先",
 | 
					      "priority_tip": "渠道选择优先级,越高越优先",
 | 
				
			||||||
      "select_test_model": "请选择测试模型",
 | 
					      "select_test_model": "请选择测试模型",
 | 
				
			||||||
      "click_to_update": "点击更新"
 | 
					      "click_to_update": "点击更新",
 | 
				
			||||||
 | 
					      "balance_not_supported": "-"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "buttons": {
 | 
					    "buttons": {
 | 
				
			||||||
      "test": "测试",
 | 
					      "test": "测试",
 | 
				
			||||||
@@ -85,7 +86,8 @@
 | 
				
			|||||||
      "test_all_started": "已成功开始测试渠道,请刷新页面查看结果。",
 | 
					      "test_all_started": "已成功开始测试渠道,请刷新页面查看结果。",
 | 
				
			||||||
      "delete_disabled_success": "已删除所有禁用渠道,共计 {{count}} 个",
 | 
					      "delete_disabled_success": "已删除所有禁用渠道,共计 {{count}} 个",
 | 
				
			||||||
      "balance_update_success": "渠道 {{name}} 余额更新成功!",
 | 
					      "balance_update_success": "渠道 {{name}} 余额更新成功!",
 | 
				
			||||||
      "all_balance_updated": "已更新完毕所有已启用渠道余额!"
 | 
					      "all_balance_updated": "已更新完毕所有已启用渠道余额!",
 | 
				
			||||||
 | 
					      "operation_success": "操作成功完成!"
 | 
				
			||||||
    },
 | 
					    },
 | 
				
			||||||
    "edit": {
 | 
					    "edit": {
 | 
				
			||||||
      "title_edit": "更新渠道信息",
 | 
					      "title_edit": "更新渠道信息",
 | 
				
			||||||
@@ -102,8 +104,10 @@
 | 
				
			|||||||
      "model_mapping_placeholder": "此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称",
 | 
					      "model_mapping_placeholder": "此项可选,用于修改请求体中的模型名称,为一个 JSON 字符串,键为请求中模型名称,值为要替换的模型名称",
 | 
				
			||||||
      "system_prompt": "系统提示词",
 | 
					      "system_prompt": "系统提示词",
 | 
				
			||||||
      "system_prompt_placeholder": "此项可选,用于强制设置给定的系统提示词,请配合自定义模型 & 模型重定向使用,首先创建一个唯一的自定义模型名称并在上面填入,之后将该自定义模型重定向映射到该渠道一个原生支持的模型",
 | 
					      "system_prompt_placeholder": "此项可选,用于强制设置给定的系统提示词,请配合自定义模型 & 模型重定向使用,首先创建一个唯一的自定义模型名称并在上面填入,之后将该自定义模型重定向映射到该渠道一个原生支持的模型",
 | 
				
			||||||
      "base_url": "代理",
 | 
					      "proxy_url": "代理",
 | 
				
			||||||
      "base_url_placeholder": "此项可选,用于通过代理站来进行 API 调用,请输入代理站地址,格式为:https://domain.com",
 | 
					      "proxy_url_placeholder": "此项可选,用于通过代理站来进行 API 调用,请输入代理站地址,格式为:https://domain.com。注意,这里所需要填入的代理地址仅会在实际请求时替换域名部分,如果你想填入 OpenAI SDK 中所要求的 Base URL,请使用 OpenAI 兼容渠道类型",
 | 
				
			||||||
 | 
					      "base_url": "Base URL",
 | 
				
			||||||
 | 
					      "base_url_placeholder": "OpenAPI SDK 中所要求的 Base URL",
 | 
				
			||||||
      "key": "密钥",
 | 
					      "key": "密钥",
 | 
				
			||||||
      "key_placeholder": "请输入密钥",
 | 
					      "key_placeholder": "请输入密钥",
 | 
				
			||||||
      "batch": "批量创建",
 | 
					      "batch": "批量创建",
 | 
				
			||||||
@@ -133,7 +137,7 @@
 | 
				
			|||||||
      "coze_notice": "对于 Coze 而言,模型名称即 Bot ID,你可以添加一个前缀 `bot-`,例如:`bot-123456`。",
 | 
					      "coze_notice": "对于 Coze 而言,模型名称即 Bot ID,你可以添加一个前缀 `bot-`,例如:`bot-123456`。",
 | 
				
			||||||
      "douban_notice": "对于豆包而言,需要手动去",
 | 
					      "douban_notice": "对于豆包而言,需要手动去",
 | 
				
			||||||
      "douban_notice_link": "模型推理页面",
 | 
					      "douban_notice_link": "模型推理页面",
 | 
				
			||||||
      "douban_notice_2": "创建推理接入点,以接入点名称作为模型名称,例如:`ep-20240608051426-tkxvl`。",
 | 
					      "douban_notice_2": "创建推理接入点,以接入点名称作为模型名称,例如:`ep-20240608051426-tkxvl`。你可以结合模型重定向功能将其转换为常规的模型名称,例如:doubao-lite-4k -> ep-20240608051426-tkxvl(前者作为 JSON 的 key,后者作为 value)。注意,doubao-lite-4k 和 ep-20240608051426-tkxvl 都需要通过自定义模型的方式填入到本渠道的模型列表中。",
 | 
				
			||||||
      "aws_region_placeholder": "region,例如:us-west-2",
 | 
					      "aws_region_placeholder": "region,例如:us-west-2",
 | 
				
			||||||
      "aws_ak_placeholder": "AWS IAM Access Key",
 | 
					      "aws_ak_placeholder": "AWS IAM Access Key",
 | 
				
			||||||
      "aws_sk_placeholder": "AWS IAM Secret Key",
 | 
					      "aws_sk_placeholder": "AWS IAM Secret Key",
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,25 +1,10 @@
 | 
				
			|||||||
import React, {useEffect, useState} from 'react';
 | 
					import React, {useEffect, useState} from 'react';
 | 
				
			||||||
import {useTranslation} from 'react-i18next';
 | 
					import {useTranslation} from 'react-i18next';
 | 
				
			||||||
import {
 | 
					import {Button, Card, Form, Input, Message} from 'semantic-ui-react';
 | 
				
			||||||
  Button,
 | 
					 | 
				
			||||||
  Form,
 | 
					 | 
				
			||||||
  Header,
 | 
					 | 
				
			||||||
  Input,
 | 
					 | 
				
			||||||
  Message,
 | 
					 | 
				
			||||||
  Segment,
 | 
					 | 
				
			||||||
  Card,
 | 
					 | 
				
			||||||
} from 'semantic-ui-react';
 | 
					 | 
				
			||||||
import {useNavigate, useParams} from 'react-router-dom';
 | 
					import {useNavigate, useParams} from 'react-router-dom';
 | 
				
			||||||
import {
 | 
					import {API, copy, getChannelModels, showError, showInfo, showSuccess, verifyJSON,} from '../../helpers';
 | 
				
			||||||
  API,
 | 
					 | 
				
			||||||
  copy,
 | 
					 | 
				
			||||||
  getChannelModels,
 | 
					 | 
				
			||||||
  showError,
 | 
					 | 
				
			||||||
  showInfo,
 | 
					 | 
				
			||||||
  showSuccess,
 | 
					 | 
				
			||||||
  verifyJSON,
 | 
					 | 
				
			||||||
} from '../../helpers';
 | 
					 | 
				
			||||||
import {CHANNEL_OPTIONS} from '../../constants';
 | 
					import {CHANNEL_OPTIONS} from '../../constants';
 | 
				
			||||||
 | 
					import {renderChannelTip} from '../../helpers/render';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const MODEL_MAPPING_EXAMPLE = {
 | 
					const MODEL_MAPPING_EXAMPLE = {
 | 
				
			||||||
  'gpt-3.5-turbo-0301': 'gpt-3.5-turbo',
 | 
					  'gpt-3.5-turbo-0301': 'gpt-3.5-turbo',
 | 
				
			||||||
@@ -207,6 +192,9 @@ const EditChannel = () => {
 | 
				
			|||||||
      return;
 | 
					      return;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    let localInputs = { ...inputs };
 | 
					    let localInputs = { ...inputs };
 | 
				
			||||||
 | 
					    if (localInputs.key === 'undefined|undefined|undefined') {
 | 
				
			||||||
 | 
					      localInputs.key = ''; // prevent potential bug
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
    if (localInputs.base_url && localInputs.base_url.endsWith('/')) {
 | 
					    if (localInputs.base_url && localInputs.base_url.endsWith('/')) {
 | 
				
			||||||
      localInputs.base_url = localInputs.base_url.slice(
 | 
					      localInputs.base_url = localInputs.base_url.slice(
 | 
				
			||||||
        0,
 | 
					        0,
 | 
				
			||||||
@@ -307,6 +295,7 @@ const EditChannel = () => {
 | 
				
			|||||||
                options={groupOptions}
 | 
					                options={groupOptions}
 | 
				
			||||||
              />
 | 
					              />
 | 
				
			||||||
            </Form.Field>
 | 
					            </Form.Field>
 | 
				
			||||||
 | 
					            {renderChannelTip(inputs.type)}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            {/* Azure OpenAI specific fields */}
 | 
					            {/* Azure OpenAI specific fields */}
 | 
				
			||||||
            {inputs.type === 3 && (
 | 
					            {inputs.type === 3 && (
 | 
				
			||||||
@@ -350,6 +339,20 @@ const EditChannel = () => {
 | 
				
			|||||||
            {inputs.type === 8 && (
 | 
					            {inputs.type === 8 && (
 | 
				
			||||||
              <Form.Field>
 | 
					              <Form.Field>
 | 
				
			||||||
                <Form.Input
 | 
					                <Form.Input
 | 
				
			||||||
 | 
					                    required
 | 
				
			||||||
 | 
					                    label={t('channel.edit.proxy_url')}
 | 
				
			||||||
 | 
					                    name='base_url'
 | 
				
			||||||
 | 
					                    placeholder={t('channel.edit.proxy_url_placeholder')}
 | 
				
			||||||
 | 
					                    onChange={handleInputChange}
 | 
				
			||||||
 | 
					                    value={inputs.base_url}
 | 
				
			||||||
 | 
					                    autoComplete='new-password'
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              </Form.Field>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
 | 
					            {inputs.type === 50 && (
 | 
				
			||||||
 | 
					                <Form.Field>
 | 
				
			||||||
 | 
					                  <Form.Input
 | 
				
			||||||
 | 
					                      required
 | 
				
			||||||
                  label={t('channel.edit.base_url')}
 | 
					                  label={t('channel.edit.base_url')}
 | 
				
			||||||
                  name='base_url'
 | 
					                  name='base_url'
 | 
				
			||||||
                  placeholder={t('channel.edit.base_url_placeholder')}
 | 
					                  placeholder={t('channel.edit.base_url_placeholder')}
 | 
				
			||||||
@@ -622,6 +625,21 @@ const EditChannel = () => {
 | 
				
			|||||||
                  />
 | 
					                  />
 | 
				
			||||||
                </Form.Field>
 | 
					                </Form.Field>
 | 
				
			||||||
              ))}
 | 
					              ))}
 | 
				
			||||||
 | 
					            {inputs.type === 37 && (
 | 
				
			||||||
 | 
					              <Form.Field>
 | 
				
			||||||
 | 
					                <Form.Input
 | 
				
			||||||
 | 
					                  label='Account ID'
 | 
				
			||||||
 | 
					                  name='user_id'
 | 
				
			||||||
 | 
					                  required
 | 
				
			||||||
 | 
					                  placeholder={
 | 
				
			||||||
 | 
					                    '请输入 Account ID,例如:d8d7c61dbc334c32d3ced580e4bf42b4'
 | 
				
			||||||
 | 
					                  }
 | 
				
			||||||
 | 
					                  onChange={handleConfigChange}
 | 
				
			||||||
 | 
					                  value={config.user_id}
 | 
				
			||||||
 | 
					                  autoComplete=''
 | 
				
			||||||
 | 
					                />
 | 
				
			||||||
 | 
					              </Form.Field>
 | 
				
			||||||
 | 
					            )}
 | 
				
			||||||
            {inputs.type !== 33 && !isEdit && (
 | 
					            {inputs.type !== 33 && !isEdit && (
 | 
				
			||||||
              <Form.Checkbox
 | 
					              <Form.Checkbox
 | 
				
			||||||
                checked={batch}
 | 
					                checked={batch}
 | 
				
			||||||
@@ -633,12 +651,13 @@ const EditChannel = () => {
 | 
				
			|||||||
            {inputs.type !== 3 &&
 | 
					            {inputs.type !== 3 &&
 | 
				
			||||||
              inputs.type !== 33 &&
 | 
					              inputs.type !== 33 &&
 | 
				
			||||||
              inputs.type !== 8 &&
 | 
					              inputs.type !== 8 &&
 | 
				
			||||||
 | 
					                inputs.type !== 50 &&
 | 
				
			||||||
              inputs.type !== 22 && (
 | 
					              inputs.type !== 22 && (
 | 
				
			||||||
                <Form.Field>
 | 
					                <Form.Field>
 | 
				
			||||||
                  <Form.Input
 | 
					                  <Form.Input
 | 
				
			||||||
                    label={t('channel.edit.base_url')}
 | 
					                      label={t('channel.edit.proxy_url')}
 | 
				
			||||||
                    name='base_url'
 | 
					                    name='base_url'
 | 
				
			||||||
                    placeholder={t('channel.edit.base_url_placeholder')}
 | 
					                      placeholder={t('channel.edit.proxy_url_placeholder')}
 | 
				
			||||||
                    onChange={handleInputChange}
 | 
					                    onChange={handleInputChange}
 | 
				
			||||||
                    value={inputs.base_url}
 | 
					                    value={inputs.base_url}
 | 
				
			||||||
                    autoComplete='new-password'
 | 
					                    autoComplete='new-password'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -91,15 +91,15 @@
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.settings-tab .item {
 | 
					.settings-tab .item {
 | 
				
			||||||
    color: #2B3674 !important;
 | 
					    color: #000 !important;
 | 
				
			||||||
    font-weight: 500 !important;
 | 
					    font-weight: 500 !important;
 | 
				
			||||||
    padding: 0.8rem 1.2rem !important;
 | 
					    padding: 0.8rem 1.2rem !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.settings-tab .active.item {
 | 
					.settings-tab .active.item {
 | 
				
			||||||
    color: #4318FF !important;
 | 
					    color: #000 !important;
 | 
				
			||||||
    font-weight: 600 !important;
 | 
					    font-weight: 600 !important;
 | 
				
			||||||
    border-color: #4318FF !important;
 | 
					    border-color: #000 !important;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.ui.tab.segment {
 | 
					.ui.tab.segment {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,17 @@
 | 
				
			|||||||
import React, {useEffect, useState} from 'react';
 | 
					import React, {useEffect, useState} from 'react';
 | 
				
			||||||
import {useTranslation} from 'react-i18next';
 | 
					import {useTranslation} from 'react-i18next';
 | 
				
			||||||
import { Card, Grid, Header, Segment, Statistic } from 'semantic-ui-react';
 | 
					import {Card, Grid} from 'semantic-ui-react';
 | 
				
			||||||
import { API, showError } from '../../helpers';
 | 
					 | 
				
			||||||
import moment from 'moment';
 | 
					 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  LineChart,
 | 
					  Bar,
 | 
				
			||||||
 | 
					  BarChart,
 | 
				
			||||||
 | 
					  CartesianGrid,
 | 
				
			||||||
 | 
					  Legend,
 | 
				
			||||||
  Line,
 | 
					  Line,
 | 
				
			||||||
 | 
					  LineChart,
 | 
				
			||||||
 | 
					  ResponsiveContainer,
 | 
				
			||||||
 | 
					  Tooltip,
 | 
				
			||||||
  XAxis,
 | 
					  XAxis,
 | 
				
			||||||
  YAxis,
 | 
					  YAxis,
 | 
				
			||||||
  CartesianGrid,
 | 
					 | 
				
			||||||
  Tooltip,
 | 
					 | 
				
			||||||
  ResponsiveContainer,
 | 
					 | 
				
			||||||
  BarChart,
 | 
					 | 
				
			||||||
  Bar,
 | 
					 | 
				
			||||||
  Legend,
 | 
					 | 
				
			||||||
} from 'recharts';
 | 
					} from 'recharts';
 | 
				
			||||||
import axios from 'axios';
 | 
					import axios from 'axios';
 | 
				
			||||||
import './Dashboard.css';
 | 
					import './Dashboard.css';
 | 
				
			||||||
@@ -124,11 +122,11 @@ const Dashboard = () => {
 | 
				
			|||||||
        ? new Date(Math.min(...dates.map((d) => new Date(d))))
 | 
					        ? new Date(Math.min(...dates.map((d) => new Date(d))))
 | 
				
			||||||
        : new Date();
 | 
					        : new Date();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 确保至少显示5天的数据
 | 
					    // 确保至少显示7天的数据
 | 
				
			||||||
    const fiveDaysAgo = new Date();
 | 
					    const sevenDaysAgo = new Date();
 | 
				
			||||||
    fiveDaysAgo.setDate(fiveDaysAgo.getDate() - 4); // -4是因为包含今天
 | 
					    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 6); // -6是因为包含今天
 | 
				
			||||||
    if (minDate > fiveDaysAgo) {
 | 
					    if (minDate > sevenDaysAgo) {
 | 
				
			||||||
      minDate = fiveDaysAgo;
 | 
					      minDate = sevenDaysAgo;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 生成所有日期
 | 
					    // 生成所有日期
 | 
				
			||||||
@@ -166,11 +164,11 @@ const Dashboard = () => {
 | 
				
			|||||||
        ? new Date(Math.min(...dates.map((d) => new Date(d))))
 | 
					        ? new Date(Math.min(...dates.map((d) => new Date(d))))
 | 
				
			||||||
        : new Date();
 | 
					        : new Date();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 确保至少显示5天的数据
 | 
					    // 确保至少显示7天的数据
 | 
				
			||||||
    const fiveDaysAgo = new Date();
 | 
					    const sevenDaysAgo = new Date();
 | 
				
			||||||
    fiveDaysAgo.setDate(fiveDaysAgo.getDate() - 4); // -4是因为包含今天
 | 
					    sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 6); // -6是因为包含今天
 | 
				
			||||||
    if (minDate > fiveDaysAgo) {
 | 
					    if (minDate > sevenDaysAgo) {
 | 
				
			||||||
      minDate = fiveDaysAgo;
 | 
					      minDate = sevenDaysAgo;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // 生成所有日期
 | 
					    // 生成所有日期
 | 
				
			||||||
@@ -244,7 +242,7 @@ const Dashboard = () => {
 | 
				
			|||||||
            <Card.Content>
 | 
					            <Card.Content>
 | 
				
			||||||
              <Card.Header>
 | 
					              <Card.Header>
 | 
				
			||||||
                {t('dashboard.charts.requests.title')}
 | 
					                {t('dashboard.charts.requests.title')}
 | 
				
			||||||
                <span className='stat-value'>{summaryData.todayRequests}</span>
 | 
					                {/* <span className='stat-value'>{summaryData.todayRequests}</span> */}
 | 
				
			||||||
              </Card.Header>
 | 
					              </Card.Header>
 | 
				
			||||||
              <div className='chart-container'>
 | 
					              <div className='chart-container'>
 | 
				
			||||||
                <ResponsiveContainer
 | 
					                <ResponsiveContainer
 | 
				
			||||||
@@ -273,7 +271,9 @@ const Dashboard = () => {
 | 
				
			|||||||
                        t('dashboard.charts.requests.tooltip'),
 | 
					                        t('dashboard.charts.requests.tooltip'),
 | 
				
			||||||
                      ]}
 | 
					                      ]}
 | 
				
			||||||
                      labelFormatter={(label) =>
 | 
					                      labelFormatter={(label) =>
 | 
				
			||||||
                        `${t('dashboard.tooltip.date')}: ${formatDate(label)}`
 | 
					                        `${t(
 | 
				
			||||||
 | 
					                          'dashboard.statistics.tooltip.date'
 | 
				
			||||||
 | 
					                        )}: ${formatDate(label)}`
 | 
				
			||||||
                      }
 | 
					                      }
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                    <Line
 | 
					                    <Line
 | 
				
			||||||
@@ -296,9 +296,9 @@ const Dashboard = () => {
 | 
				
			|||||||
            <Card.Content>
 | 
					            <Card.Content>
 | 
				
			||||||
              <Card.Header>
 | 
					              <Card.Header>
 | 
				
			||||||
                {t('dashboard.charts.quota.title')}
 | 
					                {t('dashboard.charts.quota.title')}
 | 
				
			||||||
                <span className='stat-value'>
 | 
					                {/* <span className='stat-value'>
 | 
				
			||||||
                  ${summaryData.todayQuota.toFixed(3)}
 | 
					                  ${summaryData.todayQuota.toFixed(3)}
 | 
				
			||||||
                </span>
 | 
					                </span> */}
 | 
				
			||||||
              </Card.Header>
 | 
					              </Card.Header>
 | 
				
			||||||
              <div className='chart-container'>
 | 
					              <div className='chart-container'>
 | 
				
			||||||
                <ResponsiveContainer
 | 
					                <ResponsiveContainer
 | 
				
			||||||
@@ -323,11 +323,13 @@ const Dashboard = () => {
 | 
				
			|||||||
                        boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
 | 
					                        boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
 | 
				
			||||||
                      }}
 | 
					                      }}
 | 
				
			||||||
                      formatter={(value) => [
 | 
					                      formatter={(value) => [
 | 
				
			||||||
                        value,
 | 
					                        value.toFixed(6),
 | 
				
			||||||
                        t('dashboard.charts.quota.tooltip'),
 | 
					                        t('dashboard.charts.quota.tooltip'),
 | 
				
			||||||
                      ]}
 | 
					                      ]}
 | 
				
			||||||
                      labelFormatter={(label) =>
 | 
					                      labelFormatter={(label) =>
 | 
				
			||||||
                        `${t('dashboard.tooltip.date')}: ${formatDate(label)}`
 | 
					                        `${t(
 | 
				
			||||||
 | 
					                          'dashboard.statistics.tooltip.date'
 | 
				
			||||||
 | 
					                        )}: ${formatDate(label)}`
 | 
				
			||||||
                      }
 | 
					                      }
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                    <Line
 | 
					                    <Line
 | 
				
			||||||
@@ -350,7 +352,7 @@ const Dashboard = () => {
 | 
				
			|||||||
            <Card.Content>
 | 
					            <Card.Content>
 | 
				
			||||||
              <Card.Header>
 | 
					              <Card.Header>
 | 
				
			||||||
                {t('dashboard.charts.tokens.title')}
 | 
					                {t('dashboard.charts.tokens.title')}
 | 
				
			||||||
                <span className='stat-value'>{summaryData.todayTokens}</span>
 | 
					                {/* <span className='stat-value'>{summaryData.todayTokens}</span> */}
 | 
				
			||||||
              </Card.Header>
 | 
					              </Card.Header>
 | 
				
			||||||
              <div className='chart-container'>
 | 
					              <div className='chart-container'>
 | 
				
			||||||
                <ResponsiveContainer
 | 
					                <ResponsiveContainer
 | 
				
			||||||
@@ -379,7 +381,9 @@ const Dashboard = () => {
 | 
				
			|||||||
                        t('dashboard.charts.tokens.tooltip'),
 | 
					                        t('dashboard.charts.tokens.tooltip'),
 | 
				
			||||||
                      ]}
 | 
					                      ]}
 | 
				
			||||||
                      labelFormatter={(label) =>
 | 
					                      labelFormatter={(label) =>
 | 
				
			||||||
                        `${t('dashboard.tooltip.date')}: ${formatDate(label)}`
 | 
					                        `${t(
 | 
				
			||||||
 | 
					                          'dashboard.statistics.tooltip.date'
 | 
				
			||||||
 | 
					                        )}: ${formatDate(label)}`
 | 
				
			||||||
                      }
 | 
					                      }
 | 
				
			||||||
                    />
 | 
					                    />
 | 
				
			||||||
                    <Line
 | 
					                    <Line
 | 
				
			||||||
@@ -424,7 +428,9 @@ const Dashboard = () => {
 | 
				
			|||||||
                    boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
 | 
					                    boxShadow: '0 2px 8px rgba(0,0,0,0.1)',
 | 
				
			||||||
                  }}
 | 
					                  }}
 | 
				
			||||||
                  labelFormatter={(label) =>
 | 
					                  labelFormatter={(label) =>
 | 
				
			||||||
                    `${t('dashboard.tooltip.date')}: ${formatDate(label)}`
 | 
					                    `${t('dashboard.statistics.tooltip.date')}: ${formatDate(
 | 
				
			||||||
 | 
					                      label
 | 
				
			||||||
 | 
					                    )}`
 | 
				
			||||||
                  }
 | 
					                  }
 | 
				
			||||||
                />
 | 
					                />
 | 
				
			||||||
                <Legend
 | 
					                <Legend
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user