mirror of
https://github.com/Syngnat/GoNavi.git
synced 2026-06-05 05:59:43 +08:00
- OpenAI 兼容 URL 归一化改为保留已有 v3 和 v4 版本段,避免火山与智谱地址被错误补 /v1 - 对误填 /chat/completions 和 /models 的地址先回退到 base URL,再拼接目标端点 - 模型列表与连通性检测复用统一端点解析逻辑,修复火山 Coding Plan 等兼容服务请求 - AI 设置页拆分火山方舟与火山 Coding Plan 两个预设,并按完整路径精确匹配回显 - 修正模型下拉默认值行为,未选模型时保持占位态,避免误用动态列表首项 - 补充 provider 与 service 回归测试,并新增需求追踪文档
98 lines
3.0 KiB
Go
98 lines
3.0 KiB
Go
package provider
|
||
|
||
import (
|
||
"fmt"
|
||
"net/url"
|
||
"regexp"
|
||
"strings"
|
||
)
|
||
|
||
var openAICompatibleVersionSuffixPattern = regexp.MustCompile(`(?i)(^|/)v\d+$`)
|
||
|
||
// ParseDataURI 解析前端传递的 Data URI,返回 mimeType 和去掉前缀的 rawBase64
|
||
func ParseDataURI(dataURI string) (mimeType, rawBase64 string, err error) {
|
||
if !strings.HasPrefix(dataURI, "data:") {
|
||
// 如果前端漏了前缀,默认容错当做 jpeg 处理
|
||
return "image/jpeg", dataURI, nil
|
||
}
|
||
parts := strings.SplitN(dataURI, ",", 2)
|
||
if len(parts) != 2 {
|
||
return "", "", fmt.Errorf("invalid data URI format")
|
||
}
|
||
meta := strings.TrimPrefix(parts[0], "data:")
|
||
metaParts := strings.Split(meta, ";")
|
||
mimeType = metaParts[0]
|
||
if mimeType == "" {
|
||
mimeType = "image/jpeg" // fallback
|
||
}
|
||
rawBase64 = parts[1]
|
||
return mimeType, rawBase64, nil
|
||
}
|
||
|
||
// NormalizeOpenAICompatibleBaseURL 统一归一化 OpenAI 兼容服务的 base URL。
|
||
func NormalizeOpenAICompatibleBaseURL(raw string) string {
|
||
trimmed := strings.TrimSpace(raw)
|
||
if trimmed == "" {
|
||
return defaultOpenAIBaseURL
|
||
}
|
||
|
||
parsed, err := url.Parse(trimmed)
|
||
if err != nil || parsed.Scheme == "" || parsed.Host == "" {
|
||
return normalizeOpenAICompatibleBaseURLString(trimmed)
|
||
}
|
||
|
||
parsed.RawQuery = ""
|
||
parsed.Fragment = ""
|
||
parsed.Path = normalizeOpenAICompatiblePath(parsed.Path)
|
||
return strings.TrimRight(parsed.String(), "/")
|
||
}
|
||
|
||
// ResolveOpenAICompatibleEndpoint 基于归一化 base URL 拼接 OpenAI 兼容接口路径。
|
||
func ResolveOpenAICompatibleEndpoint(baseURL string, endpoint string) string {
|
||
normalizedBaseURL := NormalizeOpenAICompatibleBaseURL(baseURL)
|
||
normalizedEndpoint := strings.TrimLeft(strings.TrimSpace(endpoint), "/")
|
||
if normalizedEndpoint == "" {
|
||
return normalizedBaseURL
|
||
}
|
||
return normalizedBaseURL + "/" + normalizedEndpoint
|
||
}
|
||
|
||
func normalizeOpenAICompatibleBaseURLString(raw string) string {
|
||
normalized := strings.TrimRight(strings.TrimSpace(raw), "/")
|
||
if normalized == "" {
|
||
return defaultOpenAIBaseURL
|
||
}
|
||
|
||
lower := strings.ToLower(normalized)
|
||
switch {
|
||
case strings.HasSuffix(lower, "/chat/completions"):
|
||
normalized = normalized[:len(normalized)-len("/chat/completions")]
|
||
case strings.HasSuffix(lower, "/models"):
|
||
normalized = normalized[:len(normalized)-len("/models")]
|
||
}
|
||
normalized = strings.TrimRight(normalized, "/")
|
||
if openAICompatibleVersionSuffixPattern.MatchString(normalized) {
|
||
return normalized
|
||
}
|
||
return normalized + "/v1"
|
||
}
|
||
|
||
func normalizeOpenAICompatiblePath(path string) string {
|
||
normalized := strings.TrimRight(strings.TrimSpace(path), "/")
|
||
lower := strings.ToLower(normalized)
|
||
switch {
|
||
case strings.HasSuffix(lower, "/chat/completions"):
|
||
normalized = normalized[:len(normalized)-len("/chat/completions")]
|
||
case strings.HasSuffix(lower, "/models"):
|
||
normalized = normalized[:len(normalized)-len("/models")]
|
||
}
|
||
normalized = strings.TrimRight(normalized, "/")
|
||
if openAICompatibleVersionSuffixPattern.MatchString(normalized) {
|
||
return normalized
|
||
}
|
||
if normalized == "" {
|
||
return "/v1"
|
||
}
|
||
return normalized + "/v1"
|
||
}
|