feat: initialize project skeleton with Go backend, database, and config
- Go module with standard library + gorilla/sessions + mysql driver - Config: env-based configuration with .env file support - Database: MySQL connection with auto-migration for 6 tables - Seed data: 12 system tags with options, 8 system snippets - Auth: session cookie middleware - Handlers: auth, tags, snippets, builder, claude, dashboard, suggestions, settings - LLM: OpenAI-compatible client with 30s timeout - Docker: Dockerfile + docker-compose.yml - .env.example with all configuration options Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,147 @@
|
||||
package llm
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"prompt-generator/internal/models"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
BaseURL string
|
||||
APIKey string
|
||||
Model string
|
||||
}
|
||||
|
||||
func NewClient(baseURL, apiKey, model string) *Client {
|
||||
return &Client{
|
||||
BaseURL: baseURL,
|
||||
APIKey: apiKey,
|
||||
Model: model,
|
||||
}
|
||||
}
|
||||
|
||||
type chatRequest struct {
|
||||
Model string `json:"model"`
|
||||
Messages []chatMessage `json:"messages"`
|
||||
}
|
||||
|
||||
type chatMessage struct {
|
||||
Role string `json:"role"`
|
||||
Content string `json:"content"`
|
||||
}
|
||||
|
||||
type chatResponse struct {
|
||||
Choices []struct {
|
||||
Message struct {
|
||||
Content string `json:"content"`
|
||||
} `json:"message"`
|
||||
} `json:"choices"`
|
||||
Error *struct {
|
||||
Message string `json:"message"`
|
||||
} `json:"error,omitempty"`
|
||||
}
|
||||
|
||||
func (c *Client) GetSuggestions(currentPrompt string, claudeMD string) ([]models.Suggestion, error) {
|
||||
systemPrompt := `你是一个 Prompt 工程专家。用户正在构建一个用于 Coding Agent 的 Prompt。
|
||||
请分析当前 Prompt,找出缺失的关键约束或可以改进的地方。
|
||||
以 JSON 数组返回建议,每条建议包含:
|
||||
- title: 建议标题
|
||||
- description: 详细说明
|
||||
- constraint_text: 建议注入的约束文本
|
||||
|
||||
只返回 JSON 数组,不要有其他内容。`
|
||||
|
||||
userContent := fmt.Sprintf("当前 Prompt 内容如下:\n---\n%s\n---", currentPrompt)
|
||||
if claudeMD != "" {
|
||||
userContent += fmt.Sprintf("\n\n用户的 CLAUDE.md 内容:\n---\n%s\n---", claudeMD)
|
||||
}
|
||||
|
||||
reqBody := chatRequest{
|
||||
Model: c.Model,
|
||||
Messages: []chatMessage{
|
||||
{Role: "system", Content: systemPrompt},
|
||||
{Role: "user", Content: userContent},
|
||||
},
|
||||
}
|
||||
|
||||
jsonData, err := json.Marshal(reqBody)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("序列化请求失败: %w", err)
|
||||
}
|
||||
|
||||
url := strings.TrimRight(c.BaseURL, "/") + "/chat/completions"
|
||||
req, err := http.NewRequest("POST", url, bytes.NewBuffer(jsonData))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("创建请求失败: %w", err)
|
||||
}
|
||||
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("Authorization", "Bearer "+c.APIKey)
|
||||
|
||||
client := &http.Client{Timeout: 30 * time.Second}
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("调用 LLM API 失败: %w", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("读取响应失败: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("LLM API 返回错误 (%d): %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var chatResp chatResponse
|
||||
if err := json.Unmarshal(body, &chatResp); err != nil {
|
||||
return nil, fmt.Errorf("解析响应失败: %w", err)
|
||||
}
|
||||
|
||||
if chatResp.Error != nil {
|
||||
return nil, fmt.Errorf("LLM 错误: %s", chatResp.Error.Message)
|
||||
}
|
||||
|
||||
if len(chatResp.Choices) == 0 {
|
||||
return nil, fmt.Errorf("LLM 返回空结果")
|
||||
}
|
||||
|
||||
content := chatResp.Choices[0].Message.Content
|
||||
suggestions := parseSuggestions(content)
|
||||
if suggestions == nil {
|
||||
return nil, fmt.Errorf("AI 返回格式异常,请重试")
|
||||
}
|
||||
|
||||
return suggestions, nil
|
||||
}
|
||||
|
||||
func parseSuggestions(data string) []models.Suggestion {
|
||||
// Try to find JSON array in the response
|
||||
start := -1
|
||||
end := -1
|
||||
for i := 0; i < len(data); i++ {
|
||||
if data[i] == '[' && start == -1 {
|
||||
start = i
|
||||
}
|
||||
if data[i] == ']' {
|
||||
end = i + 1
|
||||
}
|
||||
}
|
||||
|
||||
if start == -1 || end == -1 {
|
||||
return nil
|
||||
}
|
||||
|
||||
var suggestions []models.Suggestion
|
||||
if err := json.Unmarshal([]byte(data[start:end]), &suggestions); err != nil {
|
||||
return nil
|
||||
}
|
||||
return suggestions
|
||||
}
|
||||
Reference in New Issue
Block a user