From 2ffdf738bdb8f3e0a08b6c558cf4a4c01b71710b Mon Sep 17 00:00:00 2001 From: Zhaokun Zhang Date: Sat, 20 Sep 2025 11:09:28 +0800 Subject: [PATCH 01/11] fix: address copy functionality and code logic issues for #1828 - utils.jsx: Replace input with textarea in copy function to preserve line breaks in multi-line content, preventing formatting loss mentioned in #1828 - api.js: Fix duplicate 'group' property in buildApiPayload to resolve syntax issues - MarkdownRenderer.jsx: Refactor code text extraction using textContent for accurate copying Closes #1828 Signed-off-by: Zhaokun Zhang --- .../common/markdown/MarkdownRenderer.jsx | 4 ++-- web/src/helpers/api.js | 15 ++++++++------- web/src/helpers/utils.jsx | 18 +++++++++++------- 3 files changed, 21 insertions(+), 16 deletions(-) diff --git a/web/src/components/common/markdown/MarkdownRenderer.jsx b/web/src/components/common/markdown/MarkdownRenderer.jsx index f1283a64..05419f8c 100644 --- a/web/src/components/common/markdown/MarkdownRenderer.jsx +++ b/web/src/components/common/markdown/MarkdownRenderer.jsx @@ -181,8 +181,8 @@ export function PreCode(props) { e.preventDefault(); e.stopPropagation(); if (ref.current) { - const code = - ref.current.querySelector('code')?.innerText ?? ''; + const codeElement = ref.current.querySelector('code'); + const code = codeElement?.textContent ?? ''; copy(code).then((success) => { if (success) { Toast.success(t('代码已复制到剪贴板')); diff --git a/web/src/helpers/api.js b/web/src/helpers/api.js index b7092fe7..bc389b2e 100644 --- a/web/src/helpers/api.js +++ b/web/src/helpers/api.js @@ -118,7 +118,6 @@ export const buildApiPayload = ( model: inputs.model, group: inputs.group, messages: processedMessages, - group: inputs.group, stream: inputs.stream, }; @@ -132,13 +131,15 @@ export const buildApiPayload = ( seed: 'seed', }; + Object.entries(parameterMappings).forEach(([key, param]) => { - if ( - parameterEnabled[key] && - inputs[param] !== undefined && - inputs[param] !== null - ) { - payload[param] = inputs[param]; + const enabled = parameterEnabled[key]; + const value = inputs[param]; + const hasValue = value !== undefined && value !== null; + + + if (enabled && hasValue) { + payload[param] = value; } }); diff --git a/web/src/helpers/utils.jsx b/web/src/helpers/utils.jsx index e446ea69..bcd13230 100644 --- a/web/src/helpers/utils.jsx +++ b/web/src/helpers/utils.jsx @@ -75,13 +75,17 @@ export async function copy(text) { await navigator.clipboard.writeText(text); } catch (e) { try { - // 构建input 执行 复制命令 - var _input = window.document.createElement('input'); - _input.value = text; - window.document.body.appendChild(_input); - _input.select(); - window.document.execCommand('Copy'); - window.document.body.removeChild(_input); + // 构建 textarea 执行复制命令,保留多行文本格式 + const textarea = window.document.createElement('textarea'); + textarea.value = text; + textarea.setAttribute('readonly', ''); + textarea.style.position = 'fixed'; + textarea.style.left = '-9999px'; + textarea.style.top = '-9999px'; + window.document.body.appendChild(textarea); + textarea.select(); + window.document.execCommand('copy'); + window.document.body.removeChild(textarea); } catch (e) { okay = false; console.error(e); From c89c8a7396d2ef15ca2c65509307826bbb867dbe Mon Sep 17 00:00:00 2001 From: Seefs Date: Sat, 27 Sep 2025 00:15:28 +0800 Subject: [PATCH 02/11] fix: add missing fields to Gemini request --- dto/gemini.go | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/dto/gemini.go b/dto/gemini.go index 5df67ba0..ad2ddb8b 100644 --- a/dto/gemini.go +++ b/dto/gemini.go @@ -14,7 +14,30 @@ type GeminiChatRequest struct { SafetySettings []GeminiChatSafetySettings `json:"safetySettings,omitempty"` GenerationConfig GeminiChatGenerationConfig `json:"generationConfig,omitempty"` Tools json.RawMessage `json:"tools,omitempty"` + ToolConfig *ToolConfig `json:"toolConfig,omitempty"` SystemInstructions *GeminiChatContent `json:"systemInstruction,omitempty"` + CachedContent string `json:"cachedContent,omitempty"` +} + +type ToolConfig struct { + FunctionCallingConfig *FunctionCallingConfig `json:"functionCallingConfig,omitempty"` + RetrievalConfig *RetrievalConfig `json:"retrievalConfig,omitempty"` +} + +type FunctionCallingConfig struct { + Mode FunctionCallingConfigMode `json:"mode,omitempty"` + AllowedFunctionNames []string `json:"allowedFunctionNames,omitempty"` +} +type FunctionCallingConfigMode string + +type RetrievalConfig struct { + LatLng *LatLng `json:"latLng,omitempty"` + LanguageCode string `json:"languageCode,omitempty"` +} + +type LatLng struct { + Latitude *float64 `json:"latitude,omitempty"` + Longitude *float64 `json:"longitude,omitempty"` } func (r *GeminiChatRequest) GetTokenCountMeta() *types.TokenCountMeta { @@ -239,12 +262,20 @@ type GeminiChatGenerationConfig struct { StopSequences []string `json:"stopSequences,omitempty"` ResponseMimeType string `json:"responseMimeType,omitempty"` ResponseSchema any `json:"responseSchema,omitempty"` + ResponseJsonSchema any `json:"responseJsonSchema,omitempty"` + PresencePenalty *float32 `json:"presencePenalty,omitempty"` + FrequencyPenalty *float32 `json:"frequencyPenalty,omitempty"` + ResponseLogprobs bool `json:"responseLogprobs,omitempty"` + Logprobs *int32 `json:"logprobs,omitempty"` + MediaResolution MediaResolution `json:"mediaResolution,omitempty"` Seed int64 `json:"seed,omitempty"` ResponseModalities []string `json:"responseModalities,omitempty"` ThinkingConfig *GeminiThinkingConfig `json:"thinkingConfig,omitempty"` SpeechConfig json.RawMessage `json:"speechConfig,omitempty"` // RawMessage to allow flexible speech config } +type MediaResolution string + type GeminiChatCandidate struct { Content GeminiChatContent `json:"content"` FinishReason *string `json:"finishReason"` From 391d4514c0653ad75afaf922034ba3e83d718559 Mon Sep 17 00:00:00 2001 From: Seefs Date: Sat, 27 Sep 2025 00:24:29 +0800 Subject: [PATCH 03/11] fix: jsonRaw --- dto/gemini.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/dto/gemini.go b/dto/gemini.go index ad2ddb8b..b1f7b9a4 100644 --- a/dto/gemini.go +++ b/dto/gemini.go @@ -261,8 +261,8 @@ type GeminiChatGenerationConfig struct { CandidateCount int `json:"candidateCount,omitempty"` StopSequences []string `json:"stopSequences,omitempty"` ResponseMimeType string `json:"responseMimeType,omitempty"` - ResponseSchema any `json:"responseSchema,omitempty"` - ResponseJsonSchema any `json:"responseJsonSchema,omitempty"` + ResponseSchema json.RawMessage `json:"responseSchema,omitempty"` + ResponseJsonSchema json.RawMessage `json:"responseJsonSchema,omitempty"` PresencePenalty *float32 `json:"presencePenalty,omitempty"` FrequencyPenalty *float32 `json:"frequencyPenalty,omitempty"` ResponseLogprobs bool `json:"responseLogprobs,omitempty"` From f4d95bf1c405d1d70615b3a001d957519523fc95 Mon Sep 17 00:00:00 2001 From: Seefs Date: Sat, 27 Sep 2025 00:33:05 +0800 Subject: [PATCH 04/11] fix: jsonRaw --- dto/gemini.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dto/gemini.go b/dto/gemini.go index b1f7b9a4..bc05c6aa 100644 --- a/dto/gemini.go +++ b/dto/gemini.go @@ -261,7 +261,7 @@ type GeminiChatGenerationConfig struct { CandidateCount int `json:"candidateCount,omitempty"` StopSequences []string `json:"stopSequences,omitempty"` ResponseMimeType string `json:"responseMimeType,omitempty"` - ResponseSchema json.RawMessage `json:"responseSchema,omitempty"` + ResponseSchema any `json:"responseSchema,omitempty"` ResponseJsonSchema json.RawMessage `json:"responseJsonSchema,omitempty"` PresencePenalty *float32 `json:"presencePenalty,omitempty"` FrequencyPenalty *float32 `json:"frequencyPenalty,omitempty"` From 4f05c8eafb8363e90f9810335a26519cad6e2017 Mon Sep 17 00:00:00 2001 From: RedwindA Date: Sat, 27 Sep 2025 01:19:09 +0800 Subject: [PATCH 05/11] =?UTF-8?q?feat:=20=E4=BB=85=E4=B8=BA=E9=80=82?= =?UTF-8?q?=E5=BD=93=E7=9A=84=E6=B8=A0=E9=81=93=E6=B8=B2=E6=9F=93=E8=8E=B7?= =?UTF-8?q?=E5=8F=96=E6=A8=A1=E5=9E=8B=E5=88=97=E8=A1=A8=E6=8C=89=E9=92=AE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../channels/modals/EditChannelModal.jsx | 36 +++++++++++++++---- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/web/src/components/table/channels/modals/EditChannelModal.jsx b/web/src/components/table/channels/modals/EditChannelModal.jsx index c0a21624..967bf88a 100644 --- a/web/src/components/table/channels/modals/EditChannelModal.jsx +++ b/web/src/components/table/channels/modals/EditChannelModal.jsx @@ -85,6 +85,26 @@ const REGION_EXAMPLE = { 'claude-3-5-sonnet-20240620': 'europe-west1', }; +// 支持并且已适配通过接口获取模型列表的渠道类型 +const MODEL_FETCHABLE_TYPES = new Set([ + 1, + 4, + 14, + 34, + 17, + 26, + 24, + 47, + 25, + 20, + 23, + 31, + 35, + 40, + 42, + 48, +]); + function type2secretPrompt(type) { // inputs.type === 15 ? '按照如下格式输入:APIKey|SecretKey' : (inputs.type === 18 ? '按照如下格式输入:APPID|APISecret|APIKey' : '请输入渠道对应的鉴权密钥') switch (type) { @@ -1872,13 +1892,15 @@ const EditChannelModal = (props) => { > {t('填入所有模型')} - + {MODEL_FETCHABLE_TYPES.has(inputs.type) && ( + + )} )} + handleDeleteKey(record.index)} + okType={'danger'} + position={'topRight'} + > + + ), }, diff --git a/web/src/i18n/locales/en.json b/web/src/i18n/locales/en.json index bceb5f08..b0469895 100644 --- a/web/src/i18n/locales/en.json +++ b/web/src/i18n/locales/en.json @@ -1889,6 +1889,10 @@ "确定要删除所有已自动禁用的密钥吗?": "Are you sure you want to delete all automatically disabled keys?", "此操作不可撤销,将永久删除已自动禁用的密钥": "This operation cannot be undone, and all automatically disabled keys will be permanently deleted.", "删除自动禁用密钥": "Delete auto disabled keys", + "确定要删除此密钥吗?": "Are you sure you want to delete this key?", + "此操作不可撤销,将永久删除该密钥": "This operation cannot be undone, and the key will be permanently deleted.", + "密钥已删除": "Key has been deleted", + "删除密钥失败": "Failed to delete key", "图标": "Icon", "模型图标": "Model icon", "请输入图标名称": "Please enter the icon name", From 406be515dbd1bad2ea03fe262dda7ccdbfa116f3 Mon Sep 17 00:00:00 2001 From: CaIon Date: Sat, 27 Sep 2025 15:04:06 +0800 Subject: [PATCH 08/11] feat: rename output binaries to new-api for consistency across platforms --- .github/workflows/linux-release.yml | 4 ++-- .github/workflows/macos-release.yml | 2 +- .github/workflows/windows-release.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux-release.yml b/.github/workflows/linux-release.yml index c87fcfce..953845ff 100644 --- a/.github/workflows/linux-release.yml +++ b/.github/workflows/linux-release.yml @@ -38,13 +38,13 @@ jobs: - name: Build Backend (amd64) run: | go mod download - go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o one-api + go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o new-api - name: Build Backend (arm64) run: | sudo apt-get update DEBIAN_FRONTEND=noninteractive sudo apt-get install -y gcc-aarch64-linux-gnu - CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o one-api-arm64 + CC=aarch64-linux-gnu-gcc CGO_ENABLED=1 GOOS=linux GOARCH=arm64 go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)' -extldflags '-static'" -o new-api-arm64 - name: Release uses: softprops/action-gh-release@v1 diff --git a/.github/workflows/macos-release.yml b/.github/workflows/macos-release.yml index 1bc786ac..efaaa107 100644 --- a/.github/workflows/macos-release.yml +++ b/.github/workflows/macos-release.yml @@ -39,7 +39,7 @@ jobs: - name: Build Backend run: | go mod download - go build -ldflags "-X 'one-api/common.Version=$(git describe --tags)'" -o one-api-macos + go build -ldflags "-X 'one-api/common.Version=$(git describe --tags)'" -o new-api-macos - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml index de3d83d5..1f4f63c8 100644 --- a/.github/workflows/windows-release.yml +++ b/.github/workflows/windows-release.yml @@ -41,7 +41,7 @@ jobs: - name: Build Backend run: | go mod download - go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)'" -o one-api.exe + go build -ldflags "-s -w -X 'one-api/common.Version=$(git describe --tags)'" -o new-api.exe - name: Release uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') From e9e9708d1e1390a533c4dd4a7e9c7d4ed454378d Mon Sep 17 00:00:00 2001 From: CaIon Date: Sat, 27 Sep 2025 15:24:40 +0800 Subject: [PATCH 09/11] feat: update release configuration to use new-api binaries for consistency --- .github/workflows/linux-release.yml | 4 ++-- .github/workflows/macos-release.yml | 2 +- .github/workflows/windows-release.yml | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/.github/workflows/linux-release.yml b/.github/workflows/linux-release.yml index 953845ff..3e3ddc53 100644 --- a/.github/workflows/linux-release.yml +++ b/.github/workflows/linux-release.yml @@ -51,8 +51,8 @@ jobs: if: startsWith(github.ref, 'refs/tags/') with: files: | - one-api - one-api-arm64 + new-api + new-api-arm64 draft: true generate_release_notes: true env: diff --git a/.github/workflows/macos-release.yml b/.github/workflows/macos-release.yml index efaaa107..8eaf2d67 100644 --- a/.github/workflows/macos-release.yml +++ b/.github/workflows/macos-release.yml @@ -44,7 +44,7 @@ jobs: uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: - files: one-api-macos + files: new-api-macos draft: true generate_release_notes: true env: diff --git a/.github/workflows/windows-release.yml b/.github/workflows/windows-release.yml index 1f4f63c8..30e864f3 100644 --- a/.github/workflows/windows-release.yml +++ b/.github/workflows/windows-release.yml @@ -46,7 +46,7 @@ jobs: uses: softprops/action-gh-release@v1 if: startsWith(github.ref, 'refs/tags/') with: - files: one-api.exe + files: new-api.exe draft: true generate_release_notes: true env: From 476cf104955e1e9f8c1e0867a7844fd8c6b43ff8 Mon Sep 17 00:00:00 2001 From: CaIon Date: Sat, 27 Sep 2025 16:19:58 +0800 Subject: [PATCH 10/11] feat: add startup logging with network IPs and container detection --- common/sys_log.go | 35 ++++++++++++++++++++++- common/utils.go | 72 +++++++++++++++++++++++++++++++++++++++++++++++ main.go | 8 +++++- 3 files changed, 113 insertions(+), 2 deletions(-) diff --git a/common/sys_log.go b/common/sys_log.go index 478015f0..c3e736da 100644 --- a/common/sys_log.go +++ b/common/sys_log.go @@ -2,9 +2,10 @@ package common import ( "fmt" - "github.com/gin-gonic/gin" "os" "time" + + "github.com/gin-gonic/gin" ) func SysLog(s string) { @@ -22,3 +23,35 @@ func FatalLog(v ...any) { _, _ = fmt.Fprintf(gin.DefaultErrorWriter, "[FATAL] %v | %v \n", t.Format("2006/01/02 - 15:04:05"), v) os.Exit(1) } + +func LogStartupSuccess(startTime time.Time, port string) { + + duration := time.Since(startTime) + durationMs := duration.Milliseconds() + + // Get network IPs + networkIps := GetNetworkIps() + + // Print blank line for spacing + fmt.Fprintf(gin.DefaultWriter, "\n") + + // Print the main success message + fmt.Fprintf(gin.DefaultWriter, " \033[32m%s %s\033[0m ready in %d ms\n", SystemName, Version, durationMs) + fmt.Fprintf(gin.DefaultWriter, "\n") + + // Skip fancy startup message in container environments + if IsRunningInContainer() { + return + } + + // Print local URL + fmt.Fprintf(gin.DefaultWriter, " ➜ \033[1mLocal:\033[0m http://localhost:%s/\n", port) + + // Print network URLs + for _, ip := range networkIps { + fmt.Fprintf(gin.DefaultWriter, " ➜ \033[1mNetwork:\033[0m http://%s:%s/\n", ip, port) + } + + // Print blank line for spacing + fmt.Fprintf(gin.DefaultWriter, "\n") +} diff --git a/common/utils.go b/common/utils.go index 883abfd1..21f72ec6 100644 --- a/common/utils.go +++ b/common/utils.go @@ -68,6 +68,78 @@ func GetIp() (ip string) { return } +func GetNetworkIps() []string { + var networkIps []string + ips, err := net.InterfaceAddrs() + if err != nil { + log.Println(err) + return networkIps + } + + for _, a := range ips { + if ipNet, ok := a.(*net.IPNet); ok && !ipNet.IP.IsLoopback() { + if ipNet.IP.To4() != nil { + ip := ipNet.IP.String() + // Include common private network ranges + if strings.HasPrefix(ip, "10.") || + strings.HasPrefix(ip, "172.") || + strings.HasPrefix(ip, "192.168.") { + networkIps = append(networkIps, ip) + } + } + } + } + return networkIps +} + +// IsRunningInContainer detects if the application is running inside a container +func IsRunningInContainer() bool { + // Method 1: Check for .dockerenv file (Docker containers) + if _, err := os.Stat("/.dockerenv"); err == nil { + return true + } + + // Method 2: Check cgroup for container indicators + if data, err := os.ReadFile("/proc/1/cgroup"); err == nil { + content := string(data) + if strings.Contains(content, "docker") || + strings.Contains(content, "containerd") || + strings.Contains(content, "kubepods") || + strings.Contains(content, "/lxc/") { + return true + } + } + + // Method 3: Check environment variables commonly set by container runtimes + containerEnvVars := []string{ + "KUBERNETES_SERVICE_HOST", + "DOCKER_CONTAINER", + "container", + } + + for _, envVar := range containerEnvVars { + if os.Getenv(envVar) != "" { + return true + } + } + + // Method 4: Check if init process is not the traditional init + if data, err := os.ReadFile("/proc/1/comm"); err == nil { + comm := strings.TrimSpace(string(data)) + // In containers, process 1 is often not "init" or "systemd" + if comm != "init" && comm != "systemd" { + // Additional check: if it's a common container entrypoint + if strings.Contains(comm, "docker") || + strings.Contains(comm, "containerd") || + strings.Contains(comm, "runc") { + return true + } + } + } + + return false +} + var sizeKB = 1024 var sizeMB = sizeKB * 1024 var sizeGB = sizeMB * 1024 diff --git a/main.go b/main.go index 0caf5361..b1421f9e 100644 --- a/main.go +++ b/main.go @@ -16,6 +16,7 @@ import ( "one-api/setting/ratio_setting" "os" "strconv" + "time" "github.com/bytedance/gopkg/util/gopool" "github.com/gin-contrib/sessions" @@ -33,6 +34,7 @@ var buildFS embed.FS var indexPage []byte func main() { + startTime := time.Now() err := InitResources() if err != nil { @@ -150,6 +152,10 @@ func main() { if port == "" { port = strconv.Itoa(*common.Port) } + + // Log startup success message + common.LogStartupSuccess(startTime, port) + err = server.Run(":" + port) if err != nil { common.FatalLog("failed to start HTTP server: " + err.Error()) @@ -204,4 +210,4 @@ func InitResources() error { return err } return nil -} \ No newline at end of file +} From 143a2def249d1f4c0efcec68edd6148c990cb53e Mon Sep 17 00:00:00 2001 From: CaIon Date: Sat, 27 Sep 2025 16:30:24 +0800 Subject: [PATCH 11/11] feat: add startup logging with network IPs and container detection --- common/sys_log.go | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/common/sys_log.go b/common/sys_log.go index c3e736da..b29adc3e 100644 --- a/common/sys_log.go +++ b/common/sys_log.go @@ -40,13 +40,11 @@ func LogStartupSuccess(startTime time.Time, port string) { fmt.Fprintf(gin.DefaultWriter, "\n") // Skip fancy startup message in container environments - if IsRunningInContainer() { - return + if !IsRunningInContainer() { + // Print local URL + fmt.Fprintf(gin.DefaultWriter, " ➜ \033[1mLocal:\033[0m http://localhost:%s/\n", port) } - // Print local URL - fmt.Fprintf(gin.DefaultWriter, " ➜ \033[1mLocal:\033[0m http://localhost:%s/\n", port) - // Print network URLs for _, ip := range networkIps { fmt.Fprintf(gin.DefaultWriter, " ➜ \033[1mNetwork:\033[0m http://%s:%s/\n", ip, port)