| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261 |
- package controllers
- import (
- "ems-backend/models"
- "ems-backend/services"
- "encoding/json"
- "errors"
- "fmt"
- "io"
- "net/http"
- "strconv"
- "strings"
- "time"
- "github.com/gin-gonic/gin"
- "gorm.io/gorm"
- )
- // GetAIConfig 获取AI配置
- func GetAIConfig(c *gin.Context) {
- configs := make(map[string]string)
- keys := []string{"ai_api_url", "ai_api_key", "ai_model", "ai_alert_threshold", "ai_auto_ack"}
-
- for _, k := range keys {
- var config models.SysConfig
- if err := models.DB.Where("config_key = ?", k).First(&config).Error; err == nil {
- configs[k] = config.ConfigValue
- } else {
- // 默认值
- if k == "ai_alert_threshold" {
- configs[k] = "200"
- } else if k == "ai_auto_ack" {
- configs[k] = "false"
- } else if k == "ai_model" {
- configs[k] = "gpt-3.5-turbo"
- } else {
- configs[k] = ""
- }
- }
- }
- c.JSON(http.StatusOK, configs)
- }
- // UpdateAIConfig 更新AI配置
- func UpdateAIConfig(c *gin.Context) {
- // 使用 interface{} 接收,防止前端传数字导致 BindJSON 失败
- var input map[string]interface{}
- if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": "Invalid JSON format: " + err.Error()})
- return
- }
- tx := models.DB.Begin()
- defer func() {
- if r := recover(); r != nil {
- tx.Rollback()
- }
- }()
- for k, v := range input {
- // 强制转换为字符串
- valStr := fmt.Sprintf("%v", v)
- var config models.SysConfig
- err := tx.Where("config_key = ?", k).First(&config).Error
-
- if errors.Is(err, gorm.ErrRecordNotFound) {
- config = models.SysConfig{ConfigKey: k, ConfigValue: valStr, ConfigType: "Y"}
- if err := tx.Create(&config).Error; err != nil {
- tx.Rollback()
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to create config " + k})
- return
- }
- } else if err != nil {
- tx.Rollback()
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Database error checking " + k})
- return
- } else {
- config.ConfigValue = valStr
- if err := tx.Save(&config).Error; err != nil {
- tx.Rollback()
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update config " + k})
- return
- }
- }
- }
- if err := tx.Commit().Error; err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to commit transaction"})
- return
- }
- c.JSON(http.StatusOK, gin.H{"message": "配置已更新"})
- }
- // TestAIConnection 测试AI连接
- func TestAIConnection(c *gin.Context) {
- var input struct {
- APIUrl string `json:"ai_api_url"`
- APIKey string `json:"ai_api_key"`
- Model string `json:"ai_model"`
- }
- if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
- if input.APIUrl == "" || input.APIKey == "" {
- c.JSON(http.StatusBadRequest, gin.H{"error": "API URL和Key不能为空"})
- return
- }
- model := input.Model
- if model == "" {
- model = "gpt-3.5-turbo"
- }
- // 智能修正 URL
- chatUrl := services.NormalizeChatUrl(input.APIUrl)
- // 简单的 Ping 请求 (OpenAI 格式)
- if _, err := services.FetchAIResponse(chatUrl, input.APIKey, model, "Hello, are you there? Reply with 'Yes'."); err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "连接失败: " + err.Error()})
- return
- }
- c.JSON(http.StatusOK, gin.H{"message": "连接成功"})
- }
- // GetRemoteModels 获取远程模型列表
- func GetRemoteModels(c *gin.Context) {
- var input struct {
- APIUrl string `json:"ai_api_url"`
- APIKey string `json:"ai_api_key"`
- }
- if err := c.ShouldBindJSON(&input); err != nil {
- c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
- return
- }
- if input.APIUrl == "" || input.APIKey == "" {
- c.JSON(http.StatusBadRequest, gin.H{"error": "API URL和Key不能为空"})
- return
- }
- // 推断 Models API 地址
- modelsUrl := normalizeModelsUrl(input.APIUrl)
- req, err := http.NewRequest("GET", modelsUrl, nil)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Request creation failed: " + err.Error()})
- return
- }
-
- req.Header.Set("Authorization", "Bearer "+input.APIKey)
- client := &http.Client{Timeout: 10 * time.Second}
-
- resp, err := client.Do(req)
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to fetch models: " + err.Error()})
- return
- }
- defer resp.Body.Close()
-
- bodyBytes, _ := io.ReadAll(resp.Body)
- if resp.StatusCode != 200 {
- c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("API Error (%d): %s", resp.StatusCode, string(bodyBytes))})
- return
- }
-
- // 解析 OpenAI 格式的模型列表
- var result struct {
- Data []struct {
- ID string `json:"id"`
- } `json:"data"`
- }
-
- if err := json.Unmarshal(bodyBytes, &result); err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to parse models response: " + err.Error()})
- return
- }
-
- var models []string
- for _, m := range result.Data {
- models = append(models, m.ID)
- }
-
- c.JSON(http.StatusOK, gin.H{"models": models})
- }
- // GenerateAIReport 生成AI报表
- func GenerateAIReport(c *gin.Context) {
- // 1. 检查当前实时告警数量
- var count int64
- var alarms []models.AlarmLog
- models.DB.Model(&models.AlarmLog{}).Where("status = ?", "ACTIVE").Count(&count)
- // 获取部分告警内容用于生成 Prompt
- models.DB.Model(&models.AlarmLog{}).Where("status = ?", "ACTIVE").Limit(20).Order("start_time desc").Find(&alarms)
-
- // 2. 调用 Service 生成报表
- report, err := services.GenerateAIReportInternal(alarms, "实时告警分析报告 - " + time.Now().Format("2006-01-02 15:04:05"), "ALARM_ANALYSIS")
- if err != nil {
- c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
- return
- }
- c.JSON(http.StatusOK, report)
- }
- // GetAIReports 获取报表列表
- func GetAIReports(c *gin.Context) {
- page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
- pageSize, _ := strconv.Atoi(c.DefaultQuery("pageSize", "10"))
- keyword := c.Query("keyword")
- query := models.DB.Model(&models.AIAnalysisReport{})
- if keyword != "" {
- query = query.Where("title LIKE ?", "%"+keyword+"%")
- }
- var total int64
- query.Count(&total)
- var reports []models.AIAnalysisReport
- offset := (page - 1) * pageSize
- query.Order("created_at desc").Limit(pageSize).Offset(offset).Find(&reports)
- c.JSON(http.StatusOK, gin.H{"data": reports, "total": total})
- }
- // GetAIReportDetail 获取报表详情
- func GetAIReportDetail(c *gin.Context) {
- id := c.Param("id")
- var report models.AIAnalysisReport
- if err := models.DB.Where("id = ?", id).First(&report).Error; err != nil {
- c.JSON(http.StatusNotFound, gin.H{"error": "Report not found"})
- return
- }
- c.JSON(http.StatusOK, report)
- }
- // 内部辅助函数
- // normalizeModelsUrl 规范化 Models 接口地址
- func normalizeModelsUrl(inputUrl string) string {
- inputUrl = strings.TrimSpace(inputUrl)
- // 如果包含 /chat/completions,替换为 /models
- if strings.Contains(inputUrl, "/chat/completions") {
- return strings.Replace(inputUrl, "/chat/completions", "/models", 1)
- }
- // 如果是 /models 结尾,直接用
- if strings.HasSuffix(inputUrl, "/models") {
- return inputUrl
- }
- // 如果是 /v1 结尾
- if strings.HasSuffix(inputUrl, "/v1") {
- return strings.TrimRight(inputUrl, "/") + "/models"
- }
- // 默认追加
- return strings.TrimRight(inputUrl, "/") + "/models"
- }
|