Files
oci-portal/internal/api/aiadmin.go
T

226 lines
5.6 KiB
Go

package api
import (
"net/http"
"strconv"
"github.com/gin-gonic/gin"
"oci-portal/internal/service"
)
// aiAdminHandler 处理面板侧 AI 网关管理接口(secured 组,自动进系统日志)。
type aiAdminHandler struct {
gw *service.AiGatewayService
}
// ---- 密钥 ----
func (h *aiAdminHandler) listKeys(c *gin.Context) {
keys, err := h.gw.Keys(c.Request.Context())
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"items": keys})
}
// createKey 生成密钥;明文仅在本响应返回一次。
func (h *aiAdminHandler) createKey(c *gin.Context) {
var req struct {
Name string `json:"name" binding:"required"`
Value string `json:"value"`
Group string `json:"group"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
plaintext, key, err := h.gw.CreateKey(c.Request.Context(), req.Name, req.Value, req.Group)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, gin.H{"key": plaintext, "item": key})
}
func (h *aiAdminHandler) updateKey(c *gin.Context) {
id, ok := aiPathID(c)
if !ok {
return
}
var req struct {
Name string `json:"name"`
Enabled *bool `json:"enabled"`
Group *string `json:"group"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.gw.UpdateKey(c.Request.Context(), id, req.Name, req.Enabled, req.Group); err != nil {
respondError(c, err)
return
}
c.Status(http.StatusNoContent)
}
func (h *aiAdminHandler) deleteKey(c *gin.Context) {
id, ok := aiPathID(c)
if !ok {
return
}
if err := h.gw.DeleteKey(c.Request.Context(), id); err != nil {
respondError(c, err)
return
}
c.Status(http.StatusNoContent)
}
// updateKeyContentLog 设置密钥内容日志窗口(红线例外:显式限时开启,0 关闭)。
func (h *aiAdminHandler) updateKeyContentLog(c *gin.Context) {
id, ok := aiPathID(c)
if !ok {
return
}
var req struct {
Hours int `json:"hours"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
key, err := h.gw.UpdateKeyContentLog(c.Request.Context(), id, req.Hours)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, key)
}
// listContentLogs 分页查询内容日志(可选 keyId / callLogId 过滤)。
func (h *aiAdminHandler) listContentLogs(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "20"))
keyID, _ := strconv.Atoi(c.DefaultQuery("keyId", "0"))
callLogID, _ := strconv.Atoi(c.DefaultQuery("callLogId", "0"))
items, total, err := h.gw.ContentLogs(c.Request.Context(), uint(keyID), uint(callLogID), page, size)
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"items": items, "total": total})
}
// ---- 渠道 ----
func (h *aiAdminHandler) listChannels(c *gin.Context) {
chs, err := h.gw.Channels(c.Request.Context())
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"items": chs})
}
func (h *aiAdminHandler) createChannel(c *gin.Context) {
var req service.ChannelInput
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
ch, err := h.gw.CreateChannel(c.Request.Context(), req)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusCreated, ch)
}
func (h *aiAdminHandler) updateChannel(c *gin.Context) {
id, ok := aiPathID(c)
if !ok {
return
}
var req service.ChannelInput
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
if err := h.gw.UpdateChannel(c.Request.Context(), id, req); err != nil {
respondError(c, err)
return
}
c.Status(http.StatusNoContent)
}
func (h *aiAdminHandler) deleteChannel(c *gin.Context) {
id, ok := aiPathID(c)
if !ok {
return
}
if err := h.gw.DeleteChannel(c.Request.Context(), id); err != nil {
respondError(c, err)
return
}
c.Status(http.StatusNoContent)
}
// probeChannel 触发探测:服务可见性 + 模型同步 + 配额试调,返回更新后的渠道。
func (h *aiAdminHandler) probeChannel(c *gin.Context) {
id, ok := aiPathID(c)
if !ok {
return
}
ch, err := h.gw.ProbeChannel(c.Request.Context(), id)
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, ch)
}
func (h *aiAdminHandler) syncChannelModels(c *gin.Context) {
id, ok := aiPathID(c)
if !ok {
return
}
models, err := h.gw.SyncModels(c.Request.Context(), id)
if err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"items": models})
}
// ---- 聚合模型与调用日志 ----
func (h *aiAdminHandler) gatewayModels(c *gin.Context) {
list, err := h.gw.GatewayModels(c.Request.Context(), "")
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"items": list.Data})
}
func (h *aiAdminHandler) listLogs(c *gin.Context) {
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
size, _ := strconv.Atoi(c.DefaultQuery("size", "50"))
items, total, err := h.gw.CallLogs(c.Request.Context(), page, size)
if err != nil {
respondError(c, err)
return
}
c.JSON(http.StatusOK, gin.H{"items": items, "total": total})
}
func aiPathID(c *gin.Context) (uint, bool) {
id, err := strconv.ParseUint(c.Param("id"), 10, 64)
if err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": "invalid id"})
return 0, false
}
return uint(id), true
}