From e037276387473fadbf834854f611cfbed84b80e9 Mon Sep 17 00:00:00 2001 From: Zhang Minghan Date: Thu, 14 Mar 2024 18:03:28 +0800 Subject: [PATCH] feat: support for channel configuration proxy (#101) --- adapter/adapter.go | 1 + adapter/azure/chat.go | 3 +- adapter/azure/image.go | 4 +- adapter/baichuan/chat.go | 3 +- adapter/claude/chat.go | 31 ++++---- adapter/claude/types.go | 16 ++-- adapter/common/types.go | 2 + adapter/dashscope/chat.go | 1 + adapter/midjourney/api.go | 7 +- adapter/midjourney/chat.go | 2 +- adapter/midjourney/handler.go | 12 +-- adapter/openai/chat.go | 3 +- adapter/openai/image.go | 4 +- adapter/palm2/chat.go | 4 +- adapter/zhinao/chat.go | 2 + adapter/zhipuai/chat.go | 1 + app/src/admin/channel.ts | 29 ++++++- app/src/admin/colors.ts | 4 + app/src/admin/datasets/charge.ts | 1 + app/src/assets/main.less | 4 +- app/src/assets/pages/chat.less | 1 + app/src/assets/pages/share-manager.less | 2 +- app/src/components/admin/ChannelSettings.tsx | 20 +++++ .../admin/assemblies/ChannelEditor.tsx | 66 +++++++++++++++- .../admin/assemblies/ChannelTable.tsx | 1 + app/src/components/markdown/Label.tsx | 2 +- app/src/components/ui/textarea.tsx | 6 +- app/src/dialogs/ShareManagementDialog.tsx | 14 ++-- app/src/resources/i18n/cn.json | 6 +- app/src/resources/i18n/en.json | 6 +- app/src/resources/i18n/ja.json | 6 +- app/src/resources/i18n/ru.json | 6 +- channel/channel.go | 5 ++ channel/types.go | 37 +++++---- globals/constant.go | 7 ++ globals/interface.go | 1 + globals/types.go | 5 ++ utils/net.go | 75 +++++++++++++++---- utils/scanner.go | 4 +- 39 files changed, 311 insertions(+), 93 deletions(-) diff --git a/adapter/adapter.go b/adapter/adapter.go index 9dd10ee6..831c6933 100644 --- a/adapter/adapter.go +++ b/adapter/adapter.go @@ -42,6 +42,7 @@ var channelFactories = map[string]adaptercommon.FactoryCreator{ func createChatRequest(conf globals.ChannelConfig, props *adaptercommon.ChatProps, hook globals.Hook) error { props.Model = conf.GetModelReflect(props.OriginalModel) + props.Proxy = conf.GetProxy() factoryType := conf.GetType() if factory, ok := channelFactories[factoryType]; ok { diff --git a/adapter/azure/chat.go b/adapter/azure/chat.go index 86082ab4..bc94a7c2 100644 --- a/adapter/azure/chat.go +++ b/adapter/azure/chat.go @@ -66,6 +66,7 @@ func (c *ChatInstance) CreateChatRequest(props *adaptercommon.ChatProps) (string c.GetChatEndpoint(props), c.GetHeader(), c.GetChatBody(props, false), + props.Proxy, ) if err != nil || res == nil { @@ -108,7 +109,7 @@ func (c *ChatInstance) CreateStreamChatRequest(props *adaptercommon.ChatProps, c } return callback(partial) }, - }) + }, props.Proxy) if err != nil { if form := processChatErrorResponse(err.Body); form != nil { diff --git a/adapter/azure/image.go b/adapter/azure/image.go index 74689a05..ec3efa09 100644 --- a/adapter/azure/image.go +++ b/adapter/azure/image.go @@ -12,6 +12,7 @@ type ImageProps struct { Model string Prompt string Size ImageSize + Proxy globals.ProxyConfig } func (c *ChatInstance) GetImageEndpoint(model string) string { @@ -31,7 +32,7 @@ func (c *ChatInstance) CreateImageRequest(props ImageProps) (string, error) { ImageSize512, ), N: 1, - }) + }, props.Proxy) if err != nil || res == nil { return "", fmt.Errorf("openai error: %s", err.Error()) } @@ -51,6 +52,7 @@ func (c *ChatInstance) CreateImage(props *adaptercommon.ChatProps) (string, erro url, err := c.CreateImageRequest(ImageProps{ Model: props.Model, Prompt: c.GetLatestPrompt(props), + Proxy: props.Proxy, }) if err != nil { if strings.Contains(err.Error(), "safety") { diff --git a/adapter/baichuan/chat.go b/adapter/baichuan/chat.go index 7c78db31..8a865ca8 100644 --- a/adapter/baichuan/chat.go +++ b/adapter/baichuan/chat.go @@ -48,6 +48,7 @@ func (c *ChatInstance) CreateChatRequest(props *adaptercommon.ChatProps) (string c.GetChatEndpoint(), c.GetHeader(), c.GetChatBody(props, false), + props.Proxy, ) if err != nil || res == nil { @@ -77,7 +78,7 @@ func (c *ChatInstance) CreateStreamChatRequest(props *adaptercommon.ChatProps, c } return callback(partial) }, - }) + }, props.Proxy) if err != nil { if form := processChatErrorResponse(err.Body); form != nil { diff --git a/adapter/claude/chat.go b/adapter/claude/chat.go index 10a5a3a8..24c7b58f 100644 --- a/adapter/claude/chat.go +++ b/adapter/claude/chat.go @@ -11,18 +11,19 @@ import ( const defaultTokens = 2500 func (c *ChatInstance) GetChatEndpoint() string { - return fmt.Sprintf("%s/v1/complete", c.GetEndpoint()) + return fmt.Sprintf("%s/v1/messages", c.GetEndpoint()) } func (c *ChatInstance) GetChatHeaders() map[string]string { return map[string]string{ - "content-type": "application/json", - "accept": "application/json", - "x-api-key": c.GetApiKey(), + "content-type": "application/json", + "anthropic-version": "2023-06-01", + "x-api-key": c.GetApiKey(), } } -func (c *ChatInstance) ConvertMessage(message []globals.Message) string { +// ConvertCompletionMessage converts the completion message to anthropic complete format (deprecated) +func (c *ChatInstance) ConvertCompletionMessage(message []globals.Message) string { mapper := map[string]string{ globals.System: "Assistant", globals.User: "Human", @@ -54,19 +55,19 @@ func (c *ChatInstance) GetTokens(props *adaptercommon.ChatProps) int { func (c *ChatInstance) GetChatBody(props *adaptercommon.ChatProps, stream bool) *ChatBody { return &ChatBody{ - Prompt: c.ConvertMessage(props.Message), - MaxTokensToSample: c.GetTokens(props), - Model: props.Model, - Stream: stream, - Temperature: props.Temperature, - TopP: props.TopP, - TopK: props.TopK, + Messages: props.Message, + MaxTokens: c.GetTokens(props), + Model: props.Model, + Stream: stream, + Temperature: props.Temperature, + TopP: props.TopP, + TopK: props.TopK, } } // CreateChatRequest is the request for anthropic claude func (c *ChatInstance) CreateChatRequest(props *adaptercommon.ChatProps) (string, error) { - data, err := utils.Post(c.GetChatEndpoint(), c.GetChatHeaders(), c.GetChatBody(props, false)) + data, err := utils.Post(c.GetChatEndpoint(), c.GetChatHeaders(), c.GetChatBody(props, false), props.Proxy) if err != nil { return "", fmt.Errorf("claude error: %s", err.Error()) } @@ -126,5 +127,7 @@ func (c *ChatInstance) CreateStreamChatRequest(props *adaptercommon.ChatProps, h } return nil - }) + }, + props.Proxy, + ) } diff --git a/adapter/claude/types.go b/adapter/claude/types.go index af79f20d..78538c09 100644 --- a/adapter/claude/types.go +++ b/adapter/claude/types.go @@ -1,14 +1,16 @@ package claude +import "chat/globals" + // ChatBody is the request body for anthropic claude type ChatBody struct { - Prompt string `json:"prompt"` - MaxTokensToSample int `json:"max_tokens_to_sample"` - Model string `json:"model"` - Stream bool `json:"stream"` - Temperature *float32 `json:"temperature,omitempty"` - TopP *float32 `json:"top_p,omitempty"` - TopK *int `json:"top_k,omitempty"` + Messages []globals.Message `json:"messages"` + MaxTokens int `json:"max_tokens"` + Model string `json:"model"` + Stream bool `json:"stream"` + Temperature *float32 `json:"temperature,omitempty"` + TopP *float32 `json:"top_p,omitempty"` + TopK *int `json:"top_k,omitempty"` } // ChatResponse is the native http request and stream response for anthropic claude diff --git a/adapter/common/types.go b/adapter/common/types.go index a0f8014e..3f8723a0 100644 --- a/adapter/common/types.go +++ b/adapter/common/types.go @@ -9,6 +9,8 @@ type RequestProps struct { MaxRetries *int Current int Group string + + Proxy globals.ProxyConfig } type ChatProps struct { diff --git a/adapter/dashscope/chat.go b/adapter/dashscope/chat.go index c47f94dc..218dbffb 100644 --- a/adapter/dashscope/chat.go +++ b/adapter/dashscope/chat.go @@ -127,5 +127,6 @@ func (c *ChatInstance) CreateStreamChatRequest(props *adaptercommon.ChatProps, c return nil }, + props.Proxy, ) } diff --git a/adapter/midjourney/api.go b/adapter/midjourney/api.go index bdd136cc..87adaf44 100644 --- a/adapter/midjourney/api.go +++ b/adapter/midjourney/api.go @@ -1,6 +1,7 @@ package midjourney import ( + "chat/globals" "chat/utils" "fmt" ) @@ -29,11 +30,12 @@ func (c *ChatInstance) GetChangeRequest(action string, task string, index *int) } } -func (c *ChatInstance) CreateImagineRequest(prompt string) (*CommonResponse, error) { +func (c *ChatInstance) CreateImagineRequest(proxy globals.ProxyConfig, prompt string) (*CommonResponse, error) { content, err := utils.PostRaw( c.GetImagineEndpoint(), c.GetMidjourneyHeaders(), c.GetImagineRequest(prompt), + proxy, ) if err != nil { @@ -47,11 +49,12 @@ func (c *ChatInstance) CreateImagineRequest(prompt string) (*CommonResponse, err } } -func (c *ChatInstance) CreateChangeRequest(action string, task string, index *int) (*CommonResponse, error) { +func (c *ChatInstance) CreateChangeRequest(proxy globals.ProxyConfig, action string, task string, index *int) (*CommonResponse, error) { res, err := utils.Post( c.GetChangeEndpoint(), c.GetMidjourneyHeaders(), c.GetChangeRequest(action, task, index), + proxy, ) if err != nil { diff --git a/adapter/midjourney/chat.go b/adapter/midjourney/chat.go index d87424c0..ef4e5a3c 100644 --- a/adapter/midjourney/chat.go +++ b/adapter/midjourney/chat.go @@ -95,7 +95,7 @@ func (c *ChatInstance) CreateStreamChatRequest(props *adaptercommon.ChatProps, c var begin bool - form, err := c.CreateStreamTask(action, prompt, func(form *StorageForm, progress int) error { + form, err := c.CreateStreamTask(props, action, prompt, func(form *StorageForm, progress int) error { if progress == -1 { // ping event return callback(&globals.Chunk{Content: ""}) diff --git a/adapter/midjourney/handler.go b/adapter/midjourney/handler.go index 26d6aecd..c2a5d798 100644 --- a/adapter/midjourney/handler.go +++ b/adapter/midjourney/handler.go @@ -1,6 +1,8 @@ package midjourney import ( + adaptercommon "chat/adapter/common" + "chat/globals" "chat/utils" "fmt" "strings" @@ -66,21 +68,21 @@ func (c *ChatInstance) ExtractCommand(input string) (task string, index *int) { return } -func (c *ChatInstance) CreateRequest(action string, prompt string) (*CommonResponse, error) { +func (c *ChatInstance) CreateRequest(proxy globals.ProxyConfig, action string, prompt string) (*CommonResponse, error) { switch action { case ImagineCommand: - return c.CreateImagineRequest(prompt) + return c.CreateImagineRequest(proxy, prompt) case VariationCommand, UpscaleCommand, RerollCommand: task, index := c.ExtractCommand(prompt) - return c.CreateChangeRequest(c.GetAction(action), task, index) + return c.CreateChangeRequest(proxy, c.GetAction(action), task, index) default: return nil, fmt.Errorf("unknown action: %s", action) } } -func (c *ChatInstance) CreateStreamTask(action string, prompt string, hook func(form *StorageForm, progress int) error) (*StorageForm, error) { - res, err := c.CreateRequest(action, prompt) +func (c *ChatInstance) CreateStreamTask(props *adaptercommon.ChatProps, action string, prompt string, hook func(form *StorageForm, progress int) error) (*StorageForm, error) { + res, err := c.CreateRequest(props.Proxy, action, prompt) if err != nil { return nil, err } diff --git a/adapter/openai/chat.go b/adapter/openai/chat.go index bcc0f388..abf558ab 100644 --- a/adapter/openai/chat.go +++ b/adapter/openai/chat.go @@ -69,6 +69,7 @@ func (c *ChatInstance) CreateChatRequest(props *adaptercommon.ChatProps) (string c.GetChatEndpoint(props), c.GetHeader(), c.GetChatBody(props, false), + props.Proxy, ) if err != nil || res == nil { @@ -120,7 +121,7 @@ func (c *ChatInstance) CreateStreamChatRequest(props *adaptercommon.ChatProps, c } return callback(partial) }, - }) + }, props.Proxy) if err != nil { if form := processChatErrorResponse(err.Body); form != nil { diff --git a/adapter/openai/image.go b/adapter/openai/image.go index d77dc048..93c0eddb 100644 --- a/adapter/openai/image.go +++ b/adapter/openai/image.go @@ -12,6 +12,7 @@ type ImageProps struct { Model string Prompt string Size ImageSize + Proxy globals.ProxyConfig } func (c *ChatInstance) GetImageEndpoint() string { @@ -31,7 +32,7 @@ func (c *ChatInstance) CreateImageRequest(props ImageProps) (string, error) { ImageSize512, ), N: 1, - }) + }, props.Proxy) if err != nil || res == nil { return "", fmt.Errorf(err.Error()) } @@ -51,6 +52,7 @@ func (c *ChatInstance) CreateImage(props *adaptercommon.ChatProps) (string, erro original, err := c.CreateImageRequest(ImageProps{ Model: props.Model, Prompt: c.GetLatestPrompt(props), + Proxy: props.Proxy, }) if err != nil { if strings.Contains(err.Error(), "safety") { diff --git a/adapter/palm2/chat.go b/adapter/palm2/chat.go index 3e839df2..09255d50 100644 --- a/adapter/palm2/chat.go +++ b/adapter/palm2/chat.go @@ -93,7 +93,7 @@ func (c *ChatInstance) CreateChatRequest(props *adaptercommon.ChatProps) (string if props.Model == globals.ChatBison001 { data, err := utils.Post(uri, map[string]string{ "Content-Type": "application/json", - }, c.GetPalm2ChatBody(props)) + }, c.GetPalm2ChatBody(props), props.Proxy) if err != nil { return "", fmt.Errorf("palm2 error: %s", err.Error()) @@ -103,7 +103,7 @@ func (c *ChatInstance) CreateChatRequest(props *adaptercommon.ChatProps) (string data, err := utils.Post(uri, map[string]string{ "Content-Type": "application/json", - }, c.GetGeminiChatBody(props)) + }, c.GetGeminiChatBody(props), props.Proxy) if err != nil { return "", fmt.Errorf("gemini error: %s", err.Error()) diff --git a/adapter/zhinao/chat.go b/adapter/zhinao/chat.go index 0ca297c8..27d36b7b 100644 --- a/adapter/zhinao/chat.go +++ b/adapter/zhinao/chat.go @@ -51,6 +51,7 @@ func (c *ChatInstance) CreateChatRequest(props *adaptercommon.ChatProps) (string c.GetChatEndpoint(), c.GetHeader(), c.GetChatBody(props, false), + props.Proxy, ) if err != nil || res == nil { @@ -100,6 +101,7 @@ func (c *ChatInstance) CreateStreamChatRequest(props *adaptercommon.ChatProps, c } return nil }, + props.Proxy, ) if err != nil { diff --git a/adapter/zhipuai/chat.go b/adapter/zhipuai/chat.go index 62747b4c..35ccc50d 100644 --- a/adapter/zhipuai/chat.go +++ b/adapter/zhipuai/chat.go @@ -69,5 +69,6 @@ func (c *ChatInstance) CreateStreamChatRequest(props *adaptercommon.ChatProps, h data = strings.TrimPrefix(data, "data:") return hook(&globals.Chunk{Content: data}) }, + props.Proxy, ) } diff --git a/app/src/admin/channel.ts b/app/src/admin/channel.ts index 720595ec..88aae07f 100644 --- a/app/src/admin/channel.ts +++ b/app/src/admin/channel.ts @@ -20,6 +20,24 @@ export type Channel = { mapper: string; state: boolean; group?: string[]; + proxy?: { + proxy: string; + proxy_type: number; + }; +}; + +export enum proxyType { + NoneProxy = 0, + HttpProxy = 1, + HttpsProxy = 2, + Socks5Proxy = 3, +} + +export const ProxyTypes: Record = { + [proxyType.NoneProxy]: "None Proxy", + [proxyType.HttpProxy]: "HTTP Proxy", + [proxyType.HttpsProxy]: "HTTPS Proxy", + [proxyType.Socks5Proxy]: "SOCKS5 Proxy", }; export type ChannelInfo = { @@ -133,7 +151,16 @@ export const ChannelInfos: Record = { claude: { endpoint: "https://api.anthropic.com", format: "", - models: ["claude-instant-1", "claude-2", "claude-2.1"], + description: + "> Anthropic Claude 密钥格式为 **x-api-key**,Anthropic 对请求 IP 地域有限制,可能出现 **Request not allowed** 的错误,请尝试更换 IP 或者使用代理。\n", + models: [ + "claude-instant-1.2", + "claude-2", + "claude-2.1", + "claude-3-opus-20240229", + "claude-3-sonnet-20240229", + "claude-3-haiku-20240307", + ], }, slack: { endpoint: "your-channel", diff --git a/app/src/admin/colors.ts b/app/src/admin/colors.ts index a3acac24..63c52ed3 100644 --- a/app/src/admin/colors.ts +++ b/app/src/admin/colors.ts @@ -40,6 +40,10 @@ export const modelColorMapper: Record = { "claude-2": "orange-400", "claude-2.1": "orange-400", "claude-2-100k": "orange-400", + "claude-instant-1.2": "orange-400", + "claude-3-opus-20240229": "orange-400", + "claude-3-sonnet-20240229": "orange-400", + "claude-3-haiku-20240307": "orange-400", "spark-desk-v1.5": "blue-400", "spark-desk-v2": "blue-400", diff --git a/app/src/admin/datasets/charge.ts b/app/src/admin/datasets/charge.ts index de0acb04..d978e0de 100644 --- a/app/src/admin/datasets/charge.ts +++ b/app/src/admin/datasets/charge.ts @@ -88,6 +88,7 @@ export const pricing: PricingDataset = [ "claude-1.2", "claude-1.3", "claude-instant", + "claude-instant-1.2", "claude-slack", ], input: 0.0008, diff --git a/app/src/assets/main.less b/app/src/assets/main.less index 18a7ce52..f134e647 100644 --- a/app/src/assets/main.less +++ b/app/src/assets/main.less @@ -91,7 +91,7 @@ strong { .flex-dialog { border-radius: var(--radius) !important; - max-height: calc(95vh - 2rem) !important; + max-height: calc(95% - 2rem) !important; overflow-x: hidden; overflow-y: auto; scrollbar-width: none; @@ -128,7 +128,7 @@ strong { .fixed-dialog { border-radius: var(--radius) !important; - max-height: calc(95vh - 2rem) !important; + max-height: calc(95% - 2rem) !important; min-height: 60vh; overflow-x: hidden; overflow-y: auto; diff --git a/app/src/assets/pages/chat.less b/app/src/assets/pages/chat.less index a6454395..ea64574b 100644 --- a/app/src/assets/pages/chat.less +++ b/app/src/assets/pages/chat.less @@ -237,6 +237,7 @@ border: 1px solid var(--assistant-border); border-radius: var(--radius); margin: 0.25rem 0; + white-space: nowrap; .grow { min-width: 0.75rem; diff --git a/app/src/assets/pages/share-manager.less b/app/src/assets/pages/share-manager.less index d3feb866..960b0a21 100644 --- a/app/src/assets/pages/share-manager.less +++ b/app/src/assets/pages/share-manager.less @@ -1,4 +1,4 @@ .share-table { - max-width: 85vw; + width: 100%; height: max-content; } diff --git a/app/src/components/admin/ChannelSettings.tsx b/app/src/components/admin/ChannelSettings.tsx index 281b5176..0008d1b4 100644 --- a/app/src/components/admin/ChannelSettings.tsx +++ b/app/src/components/admin/ChannelSettings.tsx @@ -16,6 +16,10 @@ const initialState: Channel = { mapper: "", state: true, group: [], + proxy: { + proxy: "", + proxy_type: 0, + }, }; function reducer(state: Channel, action: any): Channel { @@ -77,6 +81,22 @@ function reducer(state: Channel, action: any): Channel { }; case "set-group": return { ...state, group: action.value }; + case "set-proxy": + return { + ...state, + proxy: { + proxy: action.value as string, + proxy_type: state?.proxy?.proxy_type || 0, + }, + }; + case "set-proxy-type": + return { + ...state, + proxy: { + proxy: state?.proxy?.proxy || "", + proxy_type: action.value as number, + }, + }; case "set": return { ...state, ...action.value }; default: diff --git a/app/src/components/admin/assemblies/ChannelEditor.tsx b/app/src/components/admin/assemblies/ChannelEditor.tsx index 8d1571b5..1115043d 100644 --- a/app/src/components/admin/assemblies/ChannelEditor.tsx +++ b/app/src/components/admin/assemblies/ChannelEditor.tsx @@ -13,9 +13,11 @@ import { channelGroups, ChannelTypes, getChannelInfo, + proxyType, + ProxyTypes, } from "@/admin/channel.ts"; import { CommonResponse, toastState } from "@/api/common.ts"; -import { Textarea } from "@/components/ui/textarea.tsx"; +import { FlexibleTextarea, Textarea } from "@/components/ui/textarea.tsx"; import { NumberInput } from "@/components/ui/number-input.tsx"; import { Button } from "@/components/ui/button.tsx"; import { useTranslation } from "react-i18next"; @@ -44,6 +46,7 @@ import { useEffectAsync } from "@/utils/hook.ts"; import Paragraph, { ParagraphDescription, ParagraphItem, + ParagraphSpace, } from "@/components/Paragraph.tsx"; import { MultiCombobox } from "@/components/ui/multi-combobox.tsx"; import { useChannelModels } from "@/admin/hook.tsx"; @@ -116,6 +119,14 @@ function handler(data: Channel): Channel { data.group = data.group ? data.group.filter((group) => group.trim() !== "") : []; + + if ( + data.proxy && + data.proxy.proxy.trim() === "" && + data.proxy.proxy_type !== 0 + ) { + data.proxy.proxy_type = 0; + } return data; } @@ -361,7 +372,8 @@ function ChannelEditor({ {t("admin.channels.mapper")} -