package ai import ( "bytes" "encoding/json" "errors" "fmt" "io" "net/http" "os" "strings" "time" ) type Client struct { BaseURL string APIKey string Model string Timeout time.Duration } type chatMessage struct { Role string `json:"role"` Content string `json:"content"` } type chatRequest struct { Model string `json:"model"` Messages []chatMessage `json:"messages"` Temperature float64 `json:"temperature"` } type chatResponse struct { Choices []struct { Message chatMessage `json:"message"` } `json:"choices"` Error *struct { Message string `json:"message"` } `json:"error"` } func (c *Client) Suggest(userInput string) (string, error) { return c.chat(userInput) } func (c *Client) Explain(result string) (string, error) { return "", nil } func commandGuide() string { b, err := os.ReadFile("docs/ai_command_guide.md") if err != nil { return "" } return strings.TrimSpace(string(b)) } func (c *Client) chat(userInput string) (string, error) { if strings.TrimSpace(c.BaseURL) == "" || strings.TrimSpace(c.APIKey) == "" || strings.TrimSpace(c.Model) == "" { return "", errors.New("ai config missing") } base := strings.TrimRight(c.BaseURL, "/") url := base + "/chat/completions" sys := "你是命令翻译器。把用户的自然语言转换成系统支持的标准命令。只输出一行命令,不要解释。若无法确定,输出 FAIL。\n\n可用命令知识库:\n" + commandGuide() + "\n\n规则:严格按命令格式输出。缺少关键参数时输出 FAIL。不要猜测 zone_id/record_id/backup_id。" req := chatRequest{ Model: c.Model, Messages: []chatMessage{ {Role: "system", Content: sys}, {Role: "user", Content: userInput}, }, Temperature: 0, } body, _ := json.Marshal(req) client := &http.Client{Timeout: c.Timeout} httpReq, _ := http.NewRequest("POST", url, bytes.NewReader(body)) httpReq.Header.Set("Content-Type", "application/json") httpReq.Header.Set("Authorization", "Bearer "+c.APIKey) resp, err := client.Do(httpReq) if err != nil { return "", err } defer resp.Body.Close() b, _ := io.ReadAll(io.LimitReader(resp.Body, 1<<20)) if resp.StatusCode < 200 || resp.StatusCode >= 300 { if resp.StatusCode == 429 { return "", fmt.Errorf("ai rate limited") } return "", fmt.Errorf("ai http %d", resp.StatusCode) } var out chatResponse if err := json.Unmarshal(b, &out); err != nil { return "", err } if out.Error != nil && out.Error.Message != "" { return "", errors.New(out.Error.Message) } if len(out.Choices) == 0 { return "", errors.New("empty ai response") } return strings.TrimSpace(out.Choices[0].Message.Content), nil }