Files
MyGoNavi/internal/ai/provider/helper.go
Syngnat a5fdfefa2d 🐛 fix(ai/volcengine): 修复火山引擎兼容路径并拆分双预设
- OpenAI 兼容 URL 归一化改为保留已有 v3 和 v4 版本段,避免火山与智谱地址被错误补 /v1
- 对误填 /chat/completions 和 /models 的地址先回退到 base URL,再拼接目标端点
- 模型列表与连通性检测复用统一端点解析逻辑,修复火山 Coding Plan 等兼容服务请求
- AI 设置页拆分火山方舟与火山 Coding Plan 两个预设,并按完整路径精确匹配回显
- 修正模型下拉默认值行为,未选模型时保持占位态,避免误用动态列表首项
- 补充 provider 与 service 回归测试,并新增需求追踪文档
2026-03-27 12:04:55 +08:00

98 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"
}