mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-07-20 10:51:09 +02:00
Add files via upload
This commit is contained in:
@@ -491,7 +491,7 @@ func appendAttachmentsToMessage(msg string, attachments []ChatAttachment, savedP
|
||||
}
|
||||
var b strings.Builder
|
||||
b.WriteString(msg)
|
||||
b.WriteString("\n\n[用户上传的文件已保存到以下路径(请按需读取文件内容,而不是依赖内联内容)]\n")
|
||||
b.WriteString("\n\n[用户上传的文件]\n")
|
||||
for i, a := range attachments {
|
||||
if i < len(savedPaths) && savedPaths[i] != "" {
|
||||
b.WriteString(fmt.Sprintf("- %s: %s\n", a.FileName, savedPaths[i]))
|
||||
|
||||
@@ -237,6 +237,7 @@ func (h *ConfigHandler) ApplyWechatRobotBinding(wc config.RobotWechatConfig) err
|
||||
// GetConfigResponse 获取配置响应
|
||||
type GetConfigResponse struct {
|
||||
OpenAI config.OpenAIConfig `json:"openai"`
|
||||
Vision config.VisionConfig `json:"vision"`
|
||||
FOFA config.FofaConfig `json:"fofa"`
|
||||
MCP config.MCPConfig `json:"mcp"`
|
||||
Tools []ToolConfigInfo `json:"tools"`
|
||||
@@ -333,6 +334,7 @@ func (h *ConfigHandler) GetConfig(c *gin.Context) {
|
||||
|
||||
c.JSON(http.StatusOK, GetConfigResponse{
|
||||
OpenAI: h.config.OpenAI,
|
||||
Vision: h.config.Vision,
|
||||
FOFA: h.config.FOFA,
|
||||
MCP: h.config.MCP,
|
||||
Tools: tools,
|
||||
@@ -638,6 +640,7 @@ func (h *ConfigHandler) GetTools(c *gin.Context) {
|
||||
// UpdateConfigRequest 更新配置请求
|
||||
type UpdateConfigRequest struct {
|
||||
OpenAI *config.OpenAIConfig `json:"openai,omitempty"`
|
||||
Vision *config.VisionConfig `json:"vision,omitempty"`
|
||||
FOFA *config.FofaConfig `json:"fofa,omitempty"`
|
||||
MCP *config.MCPConfig `json:"mcp,omitempty"`
|
||||
Tools []ToolEnableStatus `json:"tools,omitempty"`
|
||||
@@ -707,6 +710,14 @@ func (h *ConfigHandler) UpdateConfig(c *gin.Context) {
|
||||
)
|
||||
}
|
||||
|
||||
if req.Vision != nil {
|
||||
h.config.Vision = *req.Vision
|
||||
h.logger.Info("更新 Vision 配置",
|
||||
zap.Bool("enabled", h.config.Vision.Enabled),
|
||||
zap.String("model", h.config.Vision.Model),
|
||||
)
|
||||
}
|
||||
|
||||
// 更新FOFA配置
|
||||
if req.FOFA != nil {
|
||||
h.config.FOFA = *req.FOFA
|
||||
@@ -1031,6 +1042,99 @@ func (h *ConfigHandler) TestOpenAI(c *gin.Context) {
|
||||
})
|
||||
}
|
||||
|
||||
// TestVisionRequest 测试 Vision 模型连接;vision.api_key/base_url 留空时可传 openai 段作回退。
|
||||
type TestVisionRequest struct {
|
||||
Vision config.VisionConfig `json:"vision"`
|
||||
OpenAI config.OpenAIConfig `json:"openai,omitempty"`
|
||||
}
|
||||
|
||||
// TestVision 测试视觉模型 API 连接(最小 chat completion)。
|
||||
func (h *ConfigHandler) TestVision(c *gin.Context) {
|
||||
var req TestVisionRequest
|
||||
if err := c.ShouldBindJSON(&req); err != nil {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "无效的请求参数: " + err.Error()})
|
||||
return
|
||||
}
|
||||
oa := req.Vision.OpenAICfgEffective(req.OpenAI)
|
||||
if strings.TrimSpace(oa.APIKey) == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "API Key 不能为空(可填写 vision.api_key 或 openai.api_key)"})
|
||||
return
|
||||
}
|
||||
if strings.TrimSpace(oa.Model) == "" {
|
||||
c.JSON(http.StatusBadRequest, gin.H{"error": "视觉模型不能为空"})
|
||||
return
|
||||
}
|
||||
|
||||
baseURL := strings.TrimSuffix(strings.TrimSpace(oa.BaseURL), "/")
|
||||
if baseURL == "" {
|
||||
if strings.EqualFold(strings.TrimSpace(oa.Provider), "claude") {
|
||||
baseURL = "https://api.anthropic.com"
|
||||
} else {
|
||||
baseURL = "https://api.openai.com/v1"
|
||||
}
|
||||
}
|
||||
|
||||
payload := map[string]interface{}{
|
||||
"model": oa.Model,
|
||||
"messages": []map[string]string{
|
||||
{"role": "user", "content": "Hi"},
|
||||
},
|
||||
"max_completion_tokens": 5,
|
||||
}
|
||||
|
||||
tmpCfg := &config.OpenAIConfig{
|
||||
Provider: oa.Provider,
|
||||
BaseURL: baseURL,
|
||||
APIKey: strings.TrimSpace(oa.APIKey),
|
||||
Model: oa.Model,
|
||||
}
|
||||
client := openai.NewClient(tmpCfg, nil, h.logger)
|
||||
|
||||
ctx, cancel := context.WithTimeout(c.Request.Context(), 30*time.Second)
|
||||
defer cancel()
|
||||
|
||||
start := time.Now()
|
||||
var chatResp struct {
|
||||
Model string `json:"model"`
|
||||
Choices []struct {
|
||||
Message struct {
|
||||
Content string `json:"content"`
|
||||
} `json:"message"`
|
||||
} `json:"choices"`
|
||||
}
|
||||
err := client.ChatCompletion(ctx, payload, &chatResp)
|
||||
latency := time.Since(start)
|
||||
|
||||
if err != nil {
|
||||
if apiErr, ok := err.(*openai.APIError); ok {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"error": fmt.Sprintf("API 返回错误 (HTTP %d): %s", apiErr.StatusCode, apiErr.Body),
|
||||
"status_code": apiErr.StatusCode,
|
||||
})
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"error": "连接失败: " + err.Error(),
|
||||
})
|
||||
return
|
||||
}
|
||||
if len(chatResp.Choices) == 0 {
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": false,
|
||||
"error": "API 响应缺少 choices 字段,请检查 Base URL 与视觉模型名称",
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{
|
||||
"success": true,
|
||||
"model": chatResp.Model,
|
||||
"latency_ms": latency.Milliseconds(),
|
||||
})
|
||||
}
|
||||
|
||||
// ApplyConfig 应用配置(重新加载并重启相关服务)
|
||||
func (h *ConfigHandler) ApplyConfig(c *gin.Context) {
|
||||
// 先检查是否需要动态初始化知识库(在锁外执行,避免阻塞其他请求)
|
||||
@@ -1286,6 +1390,7 @@ func (h *ConfigHandler) saveConfig() error {
|
||||
updateAgentConfig(root, h.config.Agent)
|
||||
updateMCPConfig(root, h.config.MCP)
|
||||
updateOpenAIConfig(root, h.config.OpenAI)
|
||||
updateVisionConfig(root, h.config.Vision)
|
||||
updateFOFAConfig(root, h.config.FOFA)
|
||||
updateKnowledgeConfig(root, h.config.Knowledge)
|
||||
updateC2Config(root, h.config.C2)
|
||||
@@ -1406,6 +1511,48 @@ func updateMCPConfig(doc *yaml.Node, cfg config.MCPConfig) {
|
||||
setIntInMap(mcpNode, "port", cfg.Port)
|
||||
}
|
||||
|
||||
func updateVisionConfig(doc *yaml.Node, cfg config.VisionConfig) {
|
||||
root := doc.Content[0]
|
||||
visionNode := ensureMap(root, "vision")
|
||||
setBoolInMap(visionNode, "enabled", cfg.Enabled)
|
||||
if strings.TrimSpace(cfg.APIKey) != "" {
|
||||
setStringInMap(visionNode, "api_key", cfg.APIKey)
|
||||
} else {
|
||||
setStringInMap(visionNode, "api_key", "")
|
||||
}
|
||||
if strings.TrimSpace(cfg.BaseURL) != "" {
|
||||
setStringInMap(visionNode, "base_url", cfg.BaseURL)
|
||||
} else {
|
||||
setStringInMap(visionNode, "base_url", "")
|
||||
}
|
||||
setStringInMap(visionNode, "model", cfg.Model)
|
||||
if strings.TrimSpace(cfg.Provider) != "" {
|
||||
setStringInMap(visionNode, "provider", cfg.Provider)
|
||||
}
|
||||
if cfg.TimeoutSeconds > 0 {
|
||||
setIntInMap(visionNode, "timeout_seconds", cfg.TimeoutSeconds)
|
||||
}
|
||||
if cfg.MaxImageBytes > 0 {
|
||||
setIntInMap(visionNode, "max_image_bytes", int(cfg.MaxImageBytes))
|
||||
}
|
||||
if cfg.MaxDimension > 0 {
|
||||
setIntInMap(visionNode, "max_dimension", cfg.MaxDimension)
|
||||
}
|
||||
if cfg.JPEGQuality > 0 {
|
||||
setIntInMap(visionNode, "jpeg_quality", cfg.JPEGQuality)
|
||||
}
|
||||
if cfg.MaxPayloadBytes > 0 {
|
||||
setIntInMap(visionNode, "max_payload_bytes", int(cfg.MaxPayloadBytes))
|
||||
}
|
||||
setIntInMap(visionNode, "skip_preprocess_below_bytes", int(cfg.SkipPreprocessBelowBytes))
|
||||
if strings.TrimSpace(cfg.Detail) != "" {
|
||||
setStringInMap(visionNode, "detail", cfg.Detail)
|
||||
}
|
||||
if len(cfg.AllowedRoots) > 0 {
|
||||
setStringSliceInMap(visionNode, "allowed_roots", cfg.AllowedRoots)
|
||||
}
|
||||
}
|
||||
|
||||
func updateOpenAIConfig(doc *yaml.Node, cfg config.OpenAIConfig) {
|
||||
root := doc.Content[0]
|
||||
openaiNode := ensureMap(root, "openai")
|
||||
|
||||
@@ -778,11 +778,55 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
|
||||
},
|
||||
"ConfigResponse": map[string]interface{}{
|
||||
"type": "object",
|
||||
"description": "配置信息",
|
||||
"description": "配置信息(含 openai、vision、multi_agent 等)",
|
||||
"properties": map[string]interface{}{
|
||||
"vision": map[string]interface{}{
|
||||
"$ref": "#/components/schemas/VisionConfig",
|
||||
},
|
||||
},
|
||||
},
|
||||
"UpdateConfigRequest": map[string]interface{}{
|
||||
"type": "object",
|
||||
"description": "更新配置请求",
|
||||
"properties": map[string]interface{}{
|
||||
"vision": map[string]interface{}{
|
||||
"$ref": "#/components/schemas/VisionConfig",
|
||||
},
|
||||
},
|
||||
},
|
||||
"VisionConfig": map[string]interface{}{
|
||||
"type": "object",
|
||||
"description": "视觉分析(analyze_image MCP 工具);enabled 且 model 非空时注册工具",
|
||||
"properties": map[string]interface{}{
|
||||
"enabled": map[string]interface{}{"type": "boolean", "description": "是否启用 analyze_image"},
|
||||
"model": map[string]interface{}{"type": "string", "description": "视觉模型名(必填)", "example": "qwen-vl-max"},
|
||||
"api_key": map[string]interface{}{"type": "string", "description": "API Key;留空复用 openai.api_key"},
|
||||
"base_url": map[string]interface{}{"type": "string", "description": "Base URL;留空复用 openai.base_url"},
|
||||
"provider": map[string]interface{}{"type": "string", "description": "提供商;留空复用 openai.provider"},
|
||||
"timeout_seconds": map[string]interface{}{"type": "integer", "description": "VL 调用超时(秒)"},
|
||||
"max_image_bytes": map[string]interface{}{"type": "integer", "description": "原始文件大小上限(字节)"},
|
||||
"max_dimension": map[string]interface{}{"type": "integer", "description": "长边缩放像素"},
|
||||
"jpeg_quality": map[string]interface{}{"type": "integer", "description": "JPEG 质量 60-100"},
|
||||
"max_payload_bytes": map[string]interface{}{"type": "integer", "description": "送 API 体积上限(字节)"},
|
||||
"skip_preprocess_below_bytes": map[string]interface{}{"type": "integer", "description": "低于该字节且尺寸合规时可原图直传;0=始终压缩"},
|
||||
"detail": map[string]interface{}{"type": "string", "enum": []string{"low", "high", "auto"}, "description": "OpenAI 兼容 image detail"},
|
||||
"allowed_roots": map[string]interface{}{"type": "array", "items": map[string]interface{}{"type": "string"}, "description": "额外允许读取的绝对路径根"},
|
||||
},
|
||||
},
|
||||
"AnalyzeImageToolCall": map[string]interface{}{
|
||||
"type": "object",
|
||||
"description": "内置 MCP 工具 analyze_image:分析服务器本地图片,返回纯文本(验证码/UI/报错等)",
|
||||
"properties": map[string]interface{}{
|
||||
"path": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "图片路径(cwd、chat_uploads、result_storage_dir 或 allowed_roots 下)",
|
||||
},
|
||||
"question": map[string]interface{}{
|
||||
"type": "string",
|
||||
"description": "可选:重点问题;验证码建议「只输出验证码字符」",
|
||||
},
|
||||
},
|
||||
"required": []string{"path"},
|
||||
},
|
||||
"ExternalMCPConfig": map[string]interface{}{
|
||||
"type": "object",
|
||||
@@ -4900,6 +4944,52 @@ func (h *OpenAPIHandler) GetOpenAPISpec(c *gin.Context) {
|
||||
},
|
||||
|
||||
// ==================== 配置管理 - 缺失端点 ====================
|
||||
"/api/config/test-vision": map[string]interface{}{
|
||||
"post": map[string]interface{}{
|
||||
"tags": []string{"配置管理"},
|
||||
"summary": "测试视觉模型连接",
|
||||
"description": "测试 Vision 模型 API 是否可用。vision.api_key/base_url 留空时可传 openai 段作回退。",
|
||||
"operationId": "testVision",
|
||||
"requestBody": map[string]interface{}{
|
||||
"required": true,
|
||||
"content": map[string]interface{}{
|
||||
"application/json": map[string]interface{}{
|
||||
"schema": map[string]interface{}{
|
||||
"type": "object",
|
||||
"required": []string{"vision"},
|
||||
"properties": map[string]interface{}{
|
||||
"vision": map[string]interface{}{"$ref": "#/components/schemas/VisionConfig"},
|
||||
"openai": map[string]interface{}{
|
||||
"type": "object",
|
||||
"description": "主 LLM 配置(vision 字段留空时用于 API Key/Base URL 回退)",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"responses": map[string]interface{}{
|
||||
"200": map[string]interface{}{
|
||||
"description": "测试结果",
|
||||
"content": map[string]interface{}{
|
||||
"application/json": map[string]interface{}{
|
||||
"schema": map[string]interface{}{
|
||||
"type": "object",
|
||||
"properties": map[string]interface{}{
|
||||
"success": map[string]interface{}{"type": "boolean"},
|
||||
"error": map[string]interface{}{"type": "string"},
|
||||
"model": map[string]interface{}{"type": "string"},
|
||||
"latency_ms": map[string]interface{}{"type": "number"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"400": map[string]interface{}{"description": "参数错误"},
|
||||
"401": map[string]interface{}{"description": "未授权"},
|
||||
},
|
||||
},
|
||||
},
|
||||
"/api/config/test-openai": map[string]interface{}{
|
||||
"post": map[string]interface{}{
|
||||
"tags": []string{"配置管理"},
|
||||
|
||||
Reference in New Issue
Block a user