渠道: 分协议 Base URL + 模型抽屉/远程拉取/操作图标
- 渠道支持分协议 base_url(chat/responses/messages 各一), 网关按协议选 base 直通 (如智谱三种格式不同 base, 一个渠道即可), UpstreamURL 按 proto 拼接 - 渠道模型改为下方抽屉: 当前绑定列表(内联改上游/解除)、从接口拉取(remote 预览+勾选添加)、手动添加 - 新增 /channels/:id/models/remote 预览接口; 操作按钮加 Phosphor 图标 - 修复 formats jsonb 更新未序列化问题; 手机端渠道卡片化 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,14 +1,67 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/gin-gonic/gin"
|
||||
"github.com/openteam/server/internal/pkg/resp"
|
||||
"github.com/openteam/server/internal/store"
|
||||
)
|
||||
|
||||
// AdminChannelRemoteModels GET /api/v1/admin/channels/:id/models/remote
|
||||
// 拉取渠道接口的模型列表(仅预览,不绑定)。
|
||||
func (h *Handler) AdminChannelRemoteModels(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
if err != nil {
|
||||
resp.Fail(c, http.StatusBadRequest, "invalid channel id")
|
||||
return
|
||||
}
|
||||
var ch store.Channel
|
||||
if err := h.a.DB.First(&ch, id).Error; err != nil {
|
||||
resp.Fail(c, http.StatusNotFound, "channel not found")
|
||||
return
|
||||
}
|
||||
key, err := h.a.Enc.Decrypt(ch.APIKeyEnc)
|
||||
if err != nil {
|
||||
resp.Fail(c, http.StatusInternalServerError, "failed to decrypt channel key")
|
||||
return
|
||||
}
|
||||
client := &http.Client{Timeout: 15 * time.Second}
|
||||
req, _ := http.NewRequest(http.MethodGet, ch.UpstreamURL("", "/models"), nil)
|
||||
req.Header.Set("Authorization", "Bearer "+key)
|
||||
req.Header.Set("Accept", "application/json")
|
||||
resp2, err := client.Do(req)
|
||||
if err != nil {
|
||||
resp.Fail(c, http.StatusBadGateway, "failed to reach channel: "+err.Error())
|
||||
return
|
||||
}
|
||||
defer resp2.Body.Close()
|
||||
if resp2.StatusCode != http.StatusOK {
|
||||
resp.Fail(c, http.StatusBadGateway, "channel returned http "+strconv.Itoa(resp2.StatusCode))
|
||||
return
|
||||
}
|
||||
var list struct {
|
||||
Data []struct {
|
||||
ID string `json:"id"`
|
||||
} `json:"data"`
|
||||
}
|
||||
if err := json.NewDecoder(resp2.Body).Decode(&list); err != nil {
|
||||
resp.Fail(c, http.StatusBadGateway, "failed to parse model list")
|
||||
return
|
||||
}
|
||||
items := make([]string, 0, len(list.Data))
|
||||
for _, m := range list.Data {
|
||||
if strings.TrimSpace(m.ID) != "" {
|
||||
items = append(items, strings.TrimSpace(m.ID))
|
||||
}
|
||||
}
|
||||
resp.OK(c, gin.H{"items": items})
|
||||
}
|
||||
|
||||
// AdminChannelModels GET /api/v1/admin/channels/:id/models — 渠道的模型绑定列表(含上游映射名)。
|
||||
func (h *Handler) AdminChannelModels(c *gin.Context) {
|
||||
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
|
||||
|
||||
Reference in New Issue
Block a user