Merge remote-tracking branch 'origin/upstream/main'

This commit is contained in:
Laisky.Cai 2024-03-09 01:31:04 +00:00
commit 099e15f98b
11 changed files with 313 additions and 201 deletions

View File

@ -38,35 +38,37 @@ const (
) )
const ( const (
ChannelTypeUnknown = 0 ChannelTypeUnknown = iota
ChannelTypeOpenAI = 1 ChannelTypeOpenAI
ChannelTypeAPI2D = 2 ChannelTypeAPI2D
ChannelTypeAzure = 3 ChannelTypeAzure
ChannelTypeCloseAI = 4 ChannelTypeCloseAI
ChannelTypeOpenAISB = 5 ChannelTypeOpenAISB
ChannelTypeOpenAIMax = 6 ChannelTypeOpenAIMax
ChannelTypeOhMyGPT = 7 ChannelTypeOhMyGPT
ChannelTypeCustom = 8 ChannelTypeCustom
ChannelTypeAILS = 9 ChannelTypeAILS
ChannelTypeAIProxy = 10 ChannelTypeAIProxy
ChannelTypePaLM = 11 ChannelTypePaLM
ChannelTypeAPI2GPT = 12 ChannelTypeAPI2GPT
ChannelTypeAIGC2D = 13 ChannelTypeAIGC2D
ChannelTypeAnthropic = 14 ChannelTypeAnthropic
ChannelTypeBaidu = 15 ChannelTypeBaidu
ChannelTypeZhipu = 16 ChannelTypeZhipu
ChannelTypeAli = 17 ChannelTypeAli
ChannelTypeXunfei = 18 ChannelTypeXunfei
ChannelType360 = 19 ChannelType360
ChannelTypeOpenRouter = 20 ChannelTypeOpenRouter
ChannelTypeAIProxyLibrary = 21 ChannelTypeAIProxyLibrary
ChannelTypeFastGPT = 22 ChannelTypeFastGPT
ChannelTypeTencent = 23 ChannelTypeTencent
ChannelTypeGemini = 24 ChannelTypeGemini
ChannelTypeMoonshot = 25 ChannelTypeMoonshot
ChannelTypeBaichuan = 26 ChannelTypeBaichuan
ChannelTypeMinimax = 27 ChannelTypeMinimax
ChannelTypeMistral = 28 ChannelTypeMistral
ChannelTypeDummy
) )
var ChannelBaseURLs = []string{ var ChannelBaseURLs = []string{

View File

@ -65,10 +65,13 @@ var ModelRatio = map[string]float64{
"text-moderation-latest": 0.1, "text-moderation-latest": 0.1,
"dall-e-2": 8, // $0.016 - $0.020 / image "dall-e-2": 8, // $0.016 - $0.020 / image
"dall-e-3": 20, // $0.040 - $0.120 / image "dall-e-3": 20, // $0.040 - $0.120 / image
"claude-instant-1": 0.815, // $1.63 / 1M tokens // https://www.anthropic.com/api#pricing
"claude-2": 5.51, // $11.02 / 1M tokens "claude-instant-1.2": 0.8 / 1000 * USD,
"claude-2.0": 5.51, // $11.02 / 1M tokens "claude-2.0": 8.0 / 1000 * USD,
"claude-2.1": 5.51, // $11.02 / 1M tokens "claude-2.1": 8.0 / 1000 * USD,
"claude-3-haiku-20240229": 0.25 / 1000 * USD,
"claude-3-sonnet-20240229": 3.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-Bot": 0.8572, // ¥0.012 / 1k tokens "ERNIE-Bot": 0.8572, // ¥0.012 / 1k tokens
"ERNIE-Bot-turbo": 0.5715, // ¥0.008 / 1k tokens "ERNIE-Bot-turbo": 0.5715, // ¥0.008 / 1k tokens
@ -217,11 +220,11 @@ func GetCompletionRatio(name string) float64 {
} }
return 2 return 2
} }
if strings.HasPrefix(name, "claude-instant-1") { if strings.HasPrefix(name, "claude-3") {
return 3.38 return 5
} }
if strings.HasPrefix(name, "claude-2") { if strings.HasPrefix(name, "claude-") {
return 2.965517 return 3
} }
if strings.HasPrefix(name, "mistral-") { if strings.HasPrefix(name, "mistral-") {
return 3 return 3

View File

@ -3,6 +3,7 @@ package controller
import ( import (
"fmt" "fmt"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/songquanpeng/one-api/common"
"github.com/songquanpeng/one-api/relay/channel/ai360" "github.com/songquanpeng/one-api/relay/channel/ai360"
"github.com/songquanpeng/one-api/relay/channel/baichuan" "github.com/songquanpeng/one-api/relay/channel/baichuan"
"github.com/songquanpeng/one-api/relay/channel/minimax" "github.com/songquanpeng/one-api/relay/channel/minimax"
@ -11,6 +12,8 @@ import (
"github.com/songquanpeng/one-api/relay/constant" "github.com/songquanpeng/one-api/relay/constant"
"github.com/songquanpeng/one-api/relay/helper" "github.com/songquanpeng/one-api/relay/helper"
relaymodel "github.com/songquanpeng/one-api/relay/model" relaymodel "github.com/songquanpeng/one-api/relay/model"
"github.com/songquanpeng/one-api/relay/util"
"net/http"
) )
// https://platform.openai.com/docs/api-reference/models/list // https://platform.openai.com/docs/api-reference/models/list
@ -42,6 +45,7 @@ type OpenAIModels struct {
var openAIModels []OpenAIModels var openAIModels []OpenAIModels
var openAIModelsMap map[string]OpenAIModels var openAIModelsMap map[string]OpenAIModels
var channelId2Models map[int][]string
func init() { func init() {
var permission []OpenAIModelPermission var permission []OpenAIModelPermission
@ -142,6 +146,23 @@ func init() {
for _, model := range openAIModels { for _, model := range openAIModels {
openAIModelsMap[model.Id] = model openAIModelsMap[model.Id] = model
} }
channelId2Models = make(map[int][]string)
for i := 1; i < common.ChannelTypeDummy; i++ {
adaptor := helper.GetAdaptor(constant.ChannelType2APIType(i))
meta := &util.RelayMeta{
ChannelType: i,
}
adaptor.Init(meta)
channelId2Models[i] = adaptor.GetModelList()
}
}
func DashboardListModels(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{
"success": true,
"message": "",
"data": channelId2Models,
})
} }
func ListModels(c *gin.Context) { func ListModels(c *gin.Context) {

View File

@ -8,7 +8,6 @@ import (
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"github.com/songquanpeng/one-api/relay/channel" "github.com/songquanpeng/one-api/relay/channel"
"github.com/songquanpeng/one-api/relay/channel/openai"
"github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/model"
"github.com/songquanpeng/one-api/relay/util" "github.com/songquanpeng/one-api/relay/util"
) )
@ -34,6 +33,7 @@ func (a *Adaptor) SetupRequestHeader(c *gin.Context, req *http.Request, meta *ut
anthropicVersion = "2023-06-01" anthropicVersion = "2023-06-01"
} }
req.Header.Set("anthropic-version", anthropicVersion) req.Header.Set("anthropic-version", anthropicVersion)
req.Header.Set("anthropic-beta", "messages-2023-12-15")
return nil return nil
} }
@ -52,9 +52,7 @@ func (a *Adaptor) DoRequest(c *gin.Context, meta *util.RelayMeta, requestBody io
func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *util.RelayMeta) (usage *model.Usage, err *model.ErrorWithStatusCode) { func (a *Adaptor) DoResponse(c *gin.Context, resp *http.Response, meta *util.RelayMeta) (usage *model.Usage, err *model.ErrorWithStatusCode) {
if meta.IsStream { if meta.IsStream {
var responseText string err, usage = StreamHandler(c, resp)
err, responseText = StreamHandler(c, resp)
usage = openai.ResponseText2Usage(responseText, meta.ActualModelName, meta.PromptTokens)
} else { } else {
err, usage = Handler(c, resp, meta.PromptTokens, meta.ActualModelName) err, usage = Handler(c, resp, meta.PromptTokens, meta.ActualModelName)
} }

View File

@ -1,5 +1,8 @@
package anthropic package anthropic
var ModelList = []string{ var ModelList = []string{
"claude-instant-1", "claude-2", "claude-2.0", "claude-2.1", "claude-instant-1.2", "claude-2.0", "claude-2.1",
"claude-3-haiku-20240229",
"claude-3-sonnet-20240229",
"claude-3-opus-20240229",
} }

View File

@ -6,79 +6,146 @@ import (
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"regexp"
"strings" "strings"
"github.com/gin-gonic/gin" "github.com/gin-gonic/gin"
"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/image"
"github.com/songquanpeng/one-api/common/logger" "github.com/songquanpeng/one-api/common/logger"
"github.com/songquanpeng/one-api/relay/channel/openai" "github.com/songquanpeng/one-api/relay/channel/openai"
"github.com/songquanpeng/one-api/relay/model" "github.com/songquanpeng/one-api/relay/model"
) )
func stopReasonClaude2OpenAI(reason string) string { func stopReasonClaude2OpenAI(reason *string) string {
switch reason { if reason == nil {
return ""
}
switch *reason {
case "end_turn":
return "stop"
case "stop_sequence": case "stop_sequence":
return "stop" return "stop"
case "max_tokens": case "max_tokens":
return "length" return "length"
default: default:
return reason return *reason
} }
} }
func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request { func ConvertRequest(textRequest model.GeneralOpenAIRequest) *Request {
claudeRequest := Request{ claudeRequest := Request{
GeneralOpenAIRequest: textRequest, Model: textRequest.Model,
MaxTokens: textRequest.MaxTokens,
Temperature: textRequest.Temperature,
TopP: textRequest.TopP,
Stream: textRequest.Stream,
} }
if claudeRequest.MaxTokens == 0 { if claudeRequest.MaxTokens == 0 {
claudeRequest.MaxTokens = 500 // max_tokens is required claudeRequest.MaxTokens = 4096
} }
// legacy model name mapping
// anthropic's new messages API use system to represent the system prompt if claudeRequest.Model == "claude-instant-1" {
var filteredMessages []model.Message claudeRequest.Model = "claude-instant-1.1"
for _, msg := range claudeRequest.Messages { } else if claudeRequest.Model == "claude-2" {
if msg.Role != "system" { claudeRequest.Model = "claude-2.1"
filteredMessages = append(filteredMessages, msg) }
for _, message := range textRequest.Messages {
if message.Role == "system" && claudeRequest.System == "" {
claudeRequest.System = message.StringContent()
continue continue
} }
claudeMessage := Message{
claudeRequest.System += msg.Content.(string) Role: message.Role,
}
var content Content
if message.IsStringContent() {
content.Type = "text"
content.Text = message.StringContent()
claudeMessage.Content = append(claudeMessage.Content, content)
claudeRequest.Messages = append(claudeRequest.Messages, claudeMessage)
continue
}
var contents []Content
openaiContent := message.ParseContent()
for _, part := range openaiContent {
var content Content
if part.Type == model.ContentTypeText {
content.Type = "text"
content.Text = part.Text
} else if part.Type == model.ContentTypeImageURL {
content.Type = "image"
content.Source = &ImageSource{
Type: "base64",
}
mimeType, data, _ := image.GetImageFromUrl(part.ImageURL.Url)
content.Source.MediaType = mimeType
content.Source.Data = data
}
contents = append(contents, content)
}
claudeMessage.Content = contents
claudeRequest.Messages = append(claudeRequest.Messages, claudeMessage)
} }
claudeRequest.Messages = filteredMessages
claudeRequest.N = 0 // anthropic's messages API not support n
return &claudeRequest return &claudeRequest
} }
func streamResponseClaude2OpenAI(claudeResponse *Response) *openai.ChatCompletionsStreamResponse { // https://docs.anthropic.com/claude/reference/messages-streaming
func streamResponseClaude2OpenAI(claudeResponse *StreamResponse) (*openai.ChatCompletionsStreamResponse, *Response) {
var response *Response
var responseText string
var stopReason string
switch claudeResponse.Type {
case "message_start":
return nil, claudeResponse.Message
case "content_block_start":
if claudeResponse.ContentBlock != nil {
responseText = claudeResponse.ContentBlock.Text
}
case "content_block_delta":
if claudeResponse.Delta != nil {
responseText = claudeResponse.Delta.Text
}
case "message_delta":
if claudeResponse.Usage != nil {
response = &Response{
Usage: *claudeResponse.Usage,
}
}
if claudeResponse.Delta != nil && claudeResponse.Delta.StopReason != nil {
stopReason = *claudeResponse.Delta.StopReason
}
}
var choice openai.ChatCompletionsStreamResponseChoice var choice openai.ChatCompletionsStreamResponseChoice
choice.Delta.Content = claudeResponse.Delta.Text choice.Delta.Content = responseText
finishReason := stopReasonClaude2OpenAI(claudeResponse.Delta.StopReason) choice.Delta.Role = "assistant"
finishReason := stopReasonClaude2OpenAI(&stopReason)
if finishReason != "null" { if finishReason != "null" {
choice.FinishReason = &finishReason choice.FinishReason = &finishReason
} }
var response openai.ChatCompletionsStreamResponse var openaiResponse openai.ChatCompletionsStreamResponse
response.Object = "chat.completion.chunk" openaiResponse.Object = "chat.completion.chunk"
// response.Model = claudeResponse.Model openaiResponse.Choices = []openai.ChatCompletionsStreamResponseChoice{choice}
response.Choices = []openai.ChatCompletionsStreamResponseChoice{choice} return &openaiResponse, response
return &response
} }
func responseClaude2OpenAI(claudeResponse *Response) *openai.TextResponse { func responseClaude2OpenAI(claudeResponse *Response) *openai.TextResponse {
var responseText string
if len(claudeResponse.Content) > 0 {
responseText = claudeResponse.Content[0].Text
}
choice := openai.TextResponseChoice{ choice := openai.TextResponseChoice{
Index: 0, Index: 0,
Message: model.Message{ Message: model.Message{
Role: "assistant", Role: "assistant",
Content: strings.TrimPrefix(claudeResponse.Delta.Text, " "), Content: responseText,
Name: nil, Name: nil,
}, },
FinishReason: stopReasonClaude2OpenAI(claudeResponse.Delta.StopReason), FinishReason: stopReasonClaude2OpenAI(claudeResponse.StopReason),
} }
fullTextResponse := openai.TextResponse{ fullTextResponse := openai.TextResponse{
Id: fmt.Sprintf("chatcmpl-%s", helper.GetUUID()), Id: fmt.Sprintf("chatcmpl-%s", claudeResponse.Id),
Model: claudeResponse.Model,
Object: "chat.completion", Object: "chat.completion",
Created: helper.GetTimestamp(), Created: helper.GetTimestamp(),
Choices: []openai.TextResponseChoice{choice}, Choices: []openai.TextResponseChoice{choice},
@ -86,76 +153,66 @@ func responseClaude2OpenAI(claudeResponse *Response) *openai.TextResponse {
return &fullTextResponse return &fullTextResponse
} }
var dataRegexp = regexp.MustCompile(`^data: (\{.*\})\B`) func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, *model.Usage) {
func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusCode, string) {
responseText := ""
responseId := fmt.Sprintf("chatcmpl-%s", helper.GetUUID())
createdTime := helper.GetTimestamp() createdTime := helper.GetTimestamp()
scanner := bufio.NewScanner(resp.Body) scanner := bufio.NewScanner(resp.Body)
// scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) { scanner.Split(func(data []byte, atEOF bool) (advance int, token []byte, err error) {
// if atEOF && len(data) == 0 { if atEOF && len(data) == 0 {
// return 0, nil, nil return 0, nil, nil
// } }
// if i := strings.Index(string(data), "\r\n\r\n"); i >= 0 { if i := strings.Index(string(data), "\n"); i >= 0 {
// return i + 4, data[0:i], nil return i + 1, data[0:i], nil
// } }
// if atEOF { if atEOF {
// return len(data), data, nil return len(data), data, nil
// } }
// return 0, nil, nil return 0, nil, nil
// }) })
dataChan := make(chan string) dataChan := make(chan string)
stopChan := make(chan bool) stopChan := make(chan bool)
go func() { go func() {
for scanner.Scan() { for scanner.Scan() {
data := strings.TrimSpace(scanner.Text()) data := scanner.Text()
// logger.SysLog(fmt.Sprintf("stream response: %s", data)) if len(data) < 6 {
continue
matched := dataRegexp.FindAllStringSubmatch(data, -1)
for _, match := range matched {
data = match[1]
// logger.SysLog(fmt.Sprintf("chunk response: %s", data))
dataChan <- data
} }
if !strings.HasPrefix(data, "data: ") {
continue
}
data = strings.TrimPrefix(data, "data: ")
dataChan <- data
} }
stopChan <- true stopChan <- true
}() }()
common.SetEventStreamHeaders(c) common.SetEventStreamHeaders(c)
var usage model.Usage
var modelName string
var id string
c.Stream(func(w io.Writer) bool { c.Stream(func(w io.Writer) bool {
select { select {
case data := <-dataChan: case data := <-dataChan:
// some implementations may add \r at the end of data // some implementations may add \r at the end of data
data = strings.TrimSuffix(data, "\r") data = strings.TrimSuffix(data, "\r")
var claudeResponse Response var claudeResponse StreamResponse
err := json.Unmarshal([]byte(data), &claudeResponse) err := json.Unmarshal([]byte(data), &claudeResponse)
if err != nil { if err != nil {
logger.SysError("error unmarshalling stream response: " + err.Error()) logger.SysError("error unmarshalling stream response: " + err.Error())
return true return true
} }
response, meta := streamResponseClaude2OpenAI(&claudeResponse)
switch claudeResponse.Type { if meta != nil {
case TypeContentStart, TypePing, TypeMessageDelta: usage.PromptTokens += meta.Usage.InputTokens
return true usage.CompletionTokens += meta.Usage.OutputTokens
case TypeContentStop, TypeMessageStop: modelName = meta.Model
if claudeResponse.Delta.StopReason == "" { id = fmt.Sprintf("chatcmpl-%s", meta.Id)
claudeResponse.Delta.StopReason = "end_turn"
}
case TypeContent:
claudeResponse.Delta.StopReason = "null"
case TypeError:
logger.SysError("error response: " + claudeResponse.Error.Message)
return false
default:
logger.SysError("unknown response type: " + string(data))
return true return true
} }
if response == nil {
responseText += claudeResponse.Delta.Text return true
response := streamResponseClaude2OpenAI(&claudeResponse) }
response.Id = responseId response.Id = id
response.Model = modelName
response.Created = createdTime response.Created = createdTime
jsonStr, err := json.Marshal(response) jsonStr, err := json.Marshal(response)
if err != nil { if err != nil {
@ -169,11 +226,8 @@ func StreamHandler(c *gin.Context, resp *http.Response) (*model.ErrorWithStatusC
return false return false
} }
}) })
err := resp.Body.Close() _ = resp.Body.Close()
if err != nil { return nil, &usage
return openai.ErrorWrapper(err, "close_response_body_failed", http.StatusInternalServerError), ""
}
return nil, responseText
} }
func Handler(c *gin.Context, resp *http.Response, promptTokens int, modelName string) (*model.ErrorWithStatusCode, *model.Usage) { func Handler(c *gin.Context, resp *http.Response, promptTokens int, modelName string) (*model.ErrorWithStatusCode, *model.Usage) {
@ -203,11 +257,10 @@ func Handler(c *gin.Context, resp *http.Response, promptTokens int, modelName st
} }
fullTextResponse := responseClaude2OpenAI(&claudeResponse) fullTextResponse := responseClaude2OpenAI(&claudeResponse)
fullTextResponse.Model = modelName fullTextResponse.Model = modelName
completionTokens := openai.CountTokenText(claudeResponse.Delta.Text, modelName)
usage := model.Usage{ usage := model.Usage{
PromptTokens: promptTokens, PromptTokens: claudeResponse.Usage.InputTokens,
CompletionTokens: completionTokens, CompletionTokens: claudeResponse.Usage.OutputTokens,
TotalTokens: promptTokens + completionTokens, TotalTokens: claudeResponse.Usage.InputTokens + claudeResponse.Usage.OutputTokens,
} }
fullTextResponse.Usage = usage fullTextResponse.Usage = usage
jsonResponse, err := json.Marshal(fullTextResponse) jsonResponse, err := json.Marshal(fullTextResponse)

View File

@ -1,17 +1,44 @@
package anthropic package anthropic
import ( // https://docs.anthropic.com/claude/reference/messages_post
"github.com/songquanpeng/one-api/relay/model"
)
type Metadata struct { type Metadata struct {
UserId string `json:"user_id"` UserId string `json:"user_id"`
} }
type ImageSource struct {
Type string `json:"type"`
MediaType string `json:"media_type"`
Data string `json:"data"`
}
type Content struct {
Type string `json:"type"`
Text string `json:"text,omitempty"`
Source *ImageSource `json:"source,omitempty"`
}
type Message struct {
Role string `json:"role"`
Content []Content `json:"content"`
}
type Request struct { type Request struct {
model.GeneralOpenAIRequest Model string `json:"model"`
// System anthropic messages API use system to represent the system prompt Messages []Message `json:"messages"`
System string `json:"system"` System string `json:"system,omitempty"`
MaxTokens int `json:"max_tokens,omitempty"`
StopSequences []string `json:"stop_sequences,omitempty"`
Stream bool `json:"stream,omitempty"`
Temperature float64 `json:"temperature,omitempty"`
TopP float64 `json:"top_p,omitempty"`
TopK int `json:"top_k,omitempty"`
//Metadata `json:"metadata,omitempty"`
}
type Usage struct {
InputTokens int `json:"input_tokens"`
OutputTokens int `json:"output_tokens"`
} }
type Error struct { type Error struct {
@ -34,15 +61,29 @@ const (
// https://docs.anthropic.com/claude/reference/messages-streaming // https://docs.anthropic.com/claude/reference/messages-streaming
type Response struct { type Response struct {
Type ResponseType `json:"type"` Id string `json:"id"`
Index int `json:"index,omitempty"`
Delta struct {
Type string `json:"type,omitempty"`
Text string `json:"text,omitempty"`
StopReason string `json:"stop_reason,omitempty"`
} `json:"delta,omitempty"`
Error struct {
Type string `json:"type"` Type string `json:"type"`
Message string `json:"message"` Role string `json:"role"`
} `json:"error,omitempty"` Content []Content `json:"content"`
Model string `json:"model"`
StopReason *string `json:"stop_reason"`
StopSequence *string `json:"stop_sequence"`
Usage Usage `json:"usage"`
Error Error `json:"error"`
}
type Delta struct {
Type string `json:"type"`
Text string `json:"text"`
StopReason *string `json:"stop_reason"`
StopSequence *string `json:"stop_sequence"`
}
type StreamResponse struct {
Type string `json:"type"`
Message *Response `json:"message"`
Index int `json:"index"`
ContentBlock *Content `json:"content_block"`
Delta *Delta `json:"delta"`
Usage *Usage `json:"usage"`
} }

View File

@ -14,6 +14,7 @@ func SetApiRouter(router *gin.Engine) {
apiRouter.Use(middleware.GlobalAPIRateLimit()) apiRouter.Use(middleware.GlobalAPIRateLimit())
{ {
apiRouter.GET("/status", controller.GetStatus) apiRouter.GET("/status", controller.GetStatus)
apiRouter.GET("/models", middleware.UserAuth(), controller.DashboardListModels)
apiRouter.GET("/notice", controller.GetNotice) apiRouter.GET("/notice", controller.GetNotice)
apiRouter.GET("/about", controller.GetAbout) apiRouter.GET("/about", controller.GetAbout)
apiRouter.GET("/home_page_content", controller.GetHomePageContent) apiRouter.GET("/home_page_content", controller.GetHomePageContent)

View File

@ -1,7 +1,16 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, Form, Input, Label, Message, Pagination, Popup, Table } from 'semantic-ui-react'; import { Button, Form, Input, Label, Message, Pagination, Popup, Table } from 'semantic-ui-react';
import { Link } from 'react-router-dom'; import { Link } from 'react-router-dom';
import { API, setPromptShown, shouldShowPrompt, showError, showInfo, showSuccess, timestamp2string } from '../helpers'; import {
API,
loadChannelModels,
setPromptShown,
shouldShowPrompt,
showError,
showInfo,
showSuccess,
timestamp2string
} from '../helpers';
import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../constants'; import { CHANNEL_OPTIONS, ITEMS_PER_PAGE } from '../constants';
import { renderGroup, renderNumber } from '../helpers/render'; import { renderGroup, renderNumber } from '../helpers/render';
@ -95,6 +104,7 @@ const ChannelsTable = () => {
.catch((reason) => { .catch((reason) => {
showError(reason); showError(reason);
}); });
loadChannelModels().then();
}, []); }, []);
const manageChannel = async (id, action, idx, value) => { const manageChannel = async (id, action, idx, value) => {

View File

@ -1,11 +1,13 @@
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { toastConstants } from '../constants'; import { toastConstants } from '../constants';
import React from 'react'; import React from 'react';
import { API } from './api';
const HTMLToastContent = ({ htmlContent }) => { const HTMLToastContent = ({ htmlContent }) => {
return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />; return <div dangerouslySetInnerHTML={{ __html: htmlContent }} />;
}; };
export default HTMLToastContent; export default HTMLToastContent;
export function isAdmin() { export function isAdmin() {
let user = localStorage.getItem('user'); let user = localStorage.getItem('user');
if (!user) return false; if (!user) return false;
@ -29,7 +31,7 @@ export function getSystemName() {
export function getLogo() { export function getLogo() {
let logo = localStorage.getItem('logo'); let logo = localStorage.getItem('logo');
if (!logo) return '/logo.png'; if (!logo) return '/logo.png';
return logo return logo;
} }
export function getFooterHTML() { export function getFooterHTML() {
@ -197,3 +199,29 @@ export function shouldShowPrompt(id) {
export function setPromptShown(id) { export function setPromptShown(id) {
localStorage.setItem(`prompt-${id}`, 'true'); localStorage.setItem(`prompt-${id}`, 'true');
} }
let channelModels = undefined;
export async function loadChannelModels() {
const res = await API.get('/api/models');
const { success, data } = res.data;
if (!success) {
return;
}
channelModels = data;
localStorage.setItem('channel_models', JSON.stringify(data));
}
export function getChannelModels(type) {
if (channelModels !== undefined && type in channelModels) {
return channelModels[type];
}
let models = localStorage.getItem('channel_models');
if (!models) {
return [];
}
channelModels = JSON.parse(models);
if (type in channelModels) {
return channelModels[type];
}
return [];
}

View File

@ -1,7 +1,7 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import { Button, Form, Header, Input, Message, Segment } from 'semantic-ui-react'; import { Button, Form, Header, Input, Message, Segment } from 'semantic-ui-react';
import { useNavigate, useParams } from 'react-router-dom'; import { useNavigate, useParams } from 'react-router-dom';
import { API, showError, showInfo, showSuccess, verifyJSON } from '../../helpers'; import { API, getChannelModels, showError, showInfo, showSuccess, verifyJSON } from '../../helpers';
import { CHANNEL_OPTIONS } from '../../constants'; import { CHANNEL_OPTIONS } from '../../constants';
const MODEL_MAPPING_EXAMPLE = { const MODEL_MAPPING_EXAMPLE = {
@ -56,61 +56,13 @@ const EditChannel = () => {
const [customModel, setCustomModel] = useState(''); const [customModel, setCustomModel] = useState('');
const handleInputChange = (e, { name, value }) => { const handleInputChange = (e, { name, value }) => {
setInputs((inputs) => ({ ...inputs, [name]: value })); setInputs((inputs) => ({ ...inputs, [name]: value }));
if (name === 'type' && inputs.models.length === 0) { if (name === 'type') {
let localModels = []; let localModels = getChannelModels(value);
switch (value) { if (inputs.models.length === 0) {
case 14:
localModels = ['claude-instant-1', 'claude-2', 'claude-2.0', 'claude-2.1'];
break;
case 11:
localModels = ['PaLM-2'];
break;
case 15:
localModels = ['ERNIE-Bot', 'ERNIE-Bot-turbo', 'ERNIE-Bot-4', 'Embedding-V1'];
break;
case 17:
localModels = ['qwen-turbo', 'qwen-plus', 'qwen-max', 'qwen-max-longcontext', 'text-embedding-v1'];
let withInternetVersion = [];
for (let i = 0; i < localModels.length; i++) {
if (localModels[i].startsWith('qwen-')) {
withInternetVersion.push(localModels[i] + '-internet');
}
}
localModels = [...localModels, ...withInternetVersion];
break;
case 16:
localModels = ["glm-4", "glm-4v", "glm-3-turbo",'chatglm_turbo', 'chatglm_pro', 'chatglm_std', 'chatglm_lite'];
break;
case 18:
localModels = [
'SparkDesk',
'SparkDesk-v1.1',
'SparkDesk-v2.1',
'SparkDesk-v3.1',
'SparkDesk-v3.5'
];
break;
case 19:
localModels = ['360GPT_S2_V9', 'embedding-bert-512-v1', 'embedding_s1_v1', 'semantic_similarity_s1_v1'];
break;
case 23:
localModels = ['hunyuan'];
break;
case 24:
localModels = ['gemini-pro', 'gemini-pro-vision'];
break;
case 25:
localModels = ['moonshot-v1-8k', 'moonshot-v1-32k', 'moonshot-v1-128k'];
break;
case 26:
localModels = ['Baichuan2-Turbo', 'Baichuan2-Turbo-192k', 'Baichuan-Text-Embedding'];
break;
case 27:
localModels = ['abab5.5s-chat', 'abab5.5-chat', 'abab6-chat'];
break;
}
setInputs((inputs) => ({ ...inputs, models: localModels })); setInputs((inputs) => ({ ...inputs, models: localModels }));
} }
setBasicModels(localModels);
}
}; };
const loadChannel = async () => { const loadChannel = async () => {