* feat(codex): support custom base URL * fix(codex): require HTTPS for custom base URL * docs: document changed subscription functions * fix(groups): 展示并区分详情中的默认与自定义地址 * fix(groups): 统一上游地址配置交互与图标位置 * fix(subscription): 绑定额度重置操作的上游地址 * fix(gateway): 隔离旧上游请求的健康状态更新 --------- Co-authored-by: tbphp <tangb7420@gmail.com>
95 lines
2.5 KiB
Go
95 lines
2.5 KiB
Go
package control
|
|
|
|
import (
|
|
"context"
|
|
"net/url"
|
|
"strings"
|
|
"unicode/utf8"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"gpt-load/internal/channel"
|
|
app_errors "gpt-load/internal/platform/errors"
|
|
"gpt-load/internal/platform/response"
|
|
)
|
|
|
|
const channelMaxQueryRunes = 200
|
|
|
|
type ChannelListResponse struct {
|
|
Items []ChannelListItem `json:"items"`
|
|
Total int `json:"total"`
|
|
}
|
|
|
|
type ChannelListItem struct {
|
|
channel.Descriptor
|
|
DefaultBaseURL string `json:"default_base_url"`
|
|
}
|
|
|
|
type channelDefaultBaseURLProvider interface {
|
|
DefaultBaseURL(channel.ID) (string, bool, error)
|
|
}
|
|
|
|
func (s *Service) ListChannels(ctx context.Context, query string) (ChannelListResponse, error) {
|
|
if ctx == nil {
|
|
ctx = context.Background()
|
|
}
|
|
if err := ctx.Err(); err != nil {
|
|
return ChannelListResponse{}, err
|
|
}
|
|
if s == nil || s.channelRegistry == nil {
|
|
return ChannelListResponse{}, app_errors.ErrInternalServer
|
|
}
|
|
descriptors := s.channelRegistry.Search(query)
|
|
items := make([]ChannelListItem, 0, len(descriptors))
|
|
for _, descriptor := range descriptors {
|
|
item := ChannelListItem{Descriptor: descriptor}
|
|
if len(descriptor.DefaultBaseURLs) > 0 {
|
|
item.DefaultBaseURL = descriptor.DefaultBaseURLs[0]
|
|
} else if s.channelDefaultBaseURLs != nil {
|
|
baseURL, unique, err := s.channelDefaultBaseURLs.DefaultBaseURL(descriptor.ID)
|
|
if err != nil {
|
|
return ChannelListResponse{}, err
|
|
}
|
|
if unique {
|
|
item.DefaultBaseURL = baseURL
|
|
}
|
|
}
|
|
items = append(items, item)
|
|
}
|
|
return ChannelListResponse{Items: items, Total: len(items)}, nil
|
|
}
|
|
|
|
func parseChannelQuery(rawQuery string, forceQuery bool) (string, *app_errors.APIError) {
|
|
if forceQuery && rawQuery == "" {
|
|
return "", app_errors.ErrBadRequest
|
|
}
|
|
values, err := url.ParseQuery(rawQuery)
|
|
if err != nil {
|
|
return "", app_errors.ErrBadRequest
|
|
}
|
|
for key, entries := range values {
|
|
if key != "q" || len(entries) != 1 {
|
|
return "", app_errors.ErrBadRequest
|
|
}
|
|
}
|
|
query := strings.TrimSpace(values.Get("q"))
|
|
if utf8.RuneCountInString(query) > channelMaxQueryRunes {
|
|
return "", app_errors.ErrBadRequest
|
|
}
|
|
return query, nil
|
|
}
|
|
|
|
func (s *Server) handleListChannels(c *gin.Context) {
|
|
query, apiErr := parseChannelQuery(c.Request.URL.RawQuery, c.Request.URL.ForceQuery)
|
|
if apiErr != nil {
|
|
writeServiceError(c, "list_channels", apiErr)
|
|
return
|
|
}
|
|
result, err := s.service.ListChannels(c.Request.Context(), query)
|
|
if err != nil {
|
|
writeServiceError(c, "list_channels", err)
|
|
return
|
|
}
|
|
response.SuccessI18n(c, "common.success", result)
|
|
}
|