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,240 @@
|
||||
package handlers
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"prompt-generator/internal/db"
|
||||
"prompt-generator/internal/models"
|
||||
)
|
||||
|
||||
func GetBuilderSessions(w http.ResponseWriter, r *http.Request) {
|
||||
rows, err := db.DB.Query("SELECT id, title, project_name, final_prompt, claude_session_id, created_at, updated_at FROM builder_sessions ORDER BY updated_at DESC")
|
||||
if err != nil {
|
||||
fail(w, 500, "查询会话列表失败")
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var sessions []models.BuilderSession
|
||||
for rows.Next() {
|
||||
var s models.BuilderSession
|
||||
if err := rows.Scan(&s.ID, &s.Title, &s.ProjectName, &s.FinalPrompt, &s.ClaudeSessionID, &s.CreatedAt, &s.UpdatedAt); err != nil {
|
||||
continue
|
||||
}
|
||||
sessions = append(sessions, s)
|
||||
}
|
||||
if sessions == nil {
|
||||
sessions = []models.BuilderSession{}
|
||||
}
|
||||
success(w, sessions)
|
||||
}
|
||||
|
||||
func CreateBuilderSession(w http.ResponseWriter, r *http.Request) {
|
||||
var req struct {
|
||||
Title string `json:"title"`
|
||||
ProjectName string `json:"project_name"`
|
||||
FinalPrompt string `json:"final_prompt"`
|
||||
ClaudeSessionID string `json:"claude_session_id"`
|
||||
TagOptionIDs []struct {
|
||||
TagID int64 `json:"tag_id"`
|
||||
TagOptionID int64 `json:"tag_option_id"`
|
||||
} `json:"tag_option_ids"`
|
||||
SnippetIDs []struct {
|
||||
SnippetID int64 `json:"snippet_id"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
} `json:"snippet_ids"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
fail(w, 400, "请求格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
var claudeID *string
|
||||
if req.ClaudeSessionID != "" {
|
||||
claudeID = &req.ClaudeSessionID
|
||||
}
|
||||
|
||||
res, err := db.DB.Exec("INSERT INTO builder_sessions (title, project_name, final_prompt, claude_session_id) VALUES (?, ?, ?, ?)",
|
||||
req.Title, req.ProjectName, req.FinalPrompt, claudeID)
|
||||
if err != nil {
|
||||
fail(w, 500, "创建会话失败")
|
||||
return
|
||||
}
|
||||
|
||||
sessionID, _ := res.LastInsertId()
|
||||
|
||||
// Insert tag associations
|
||||
for _, t := range req.TagOptionIDs {
|
||||
db.DB.Exec("INSERT INTO builder_session_tags (builder_session_id, tag_id, tag_option_id) VALUES (?, ?, ?)",
|
||||
sessionID, t.TagID, t.TagOptionID)
|
||||
}
|
||||
|
||||
// Insert snippet associations
|
||||
for _, s := range req.SnippetIDs {
|
||||
db.DB.Exec("INSERT INTO builder_session_snippets (builder_session_id, snippet_id, sort_order) VALUES (?, ?, ?)",
|
||||
sessionID, s.SnippetID, s.SortOrder)
|
||||
}
|
||||
|
||||
success(w, map[string]int64{"id": sessionID})
|
||||
}
|
||||
|
||||
func GetBuilderSession(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(w, 400, "无效的 ID")
|
||||
return
|
||||
}
|
||||
|
||||
var s models.BuilderSession
|
||||
err = db.DB.QueryRow("SELECT id, title, project_name, final_prompt, claude_session_id, created_at, updated_at FROM builder_sessions WHERE id=?", id).
|
||||
Scan(&s.ID, &s.Title, &s.ProjectName, &s.FinalPrompt, &s.ClaudeSessionID, &s.CreatedAt, &s.UpdatedAt)
|
||||
if err != nil {
|
||||
fail(w, 404, "会话不存在")
|
||||
return
|
||||
}
|
||||
|
||||
// Load tags
|
||||
tagRows, err := db.DB.Query("SELECT id, builder_session_id, tag_id, tag_option_id FROM builder_session_tags WHERE builder_session_id=?", id)
|
||||
if err == nil {
|
||||
for tagRows.Next() {
|
||||
var t models.SessionTag
|
||||
if err := tagRows.Scan(&t.ID, &t.BuilderSessionID, &t.TagID, &t.TagOptionID); err == nil {
|
||||
s.Tags = append(s.Tags, t)
|
||||
}
|
||||
}
|
||||
tagRows.Close()
|
||||
}
|
||||
|
||||
// Load snippets
|
||||
snippetRows, err := db.DB.Query("SELECT id, builder_session_id, snippet_id, sort_order FROM builder_session_snippets WHERE builder_session_id=? ORDER BY sort_order", id)
|
||||
if err == nil {
|
||||
for snippetRows.Next() {
|
||||
var sn models.SessionSnippet
|
||||
if err := snippetRows.Scan(&sn.ID, &sn.BuilderSessionID, &sn.SnippetID, &sn.SortOrder); err == nil {
|
||||
s.Snippets = append(s.Snippets, sn)
|
||||
}
|
||||
}
|
||||
snippetRows.Close()
|
||||
}
|
||||
|
||||
if s.Tags == nil {
|
||||
s.Tags = []models.SessionTag{}
|
||||
}
|
||||
if s.Snippets == nil {
|
||||
s.Snippets = []models.SessionSnippet{}
|
||||
}
|
||||
|
||||
success(w, s)
|
||||
}
|
||||
|
||||
func UpdateBuilderSession(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(w, 400, "无效的 ID")
|
||||
return
|
||||
}
|
||||
|
||||
var req struct {
|
||||
Title string `json:"title"`
|
||||
ProjectName string `json:"project_name"`
|
||||
FinalPrompt string `json:"final_prompt"`
|
||||
ClaudeSessionID string `json:"claude_session_id"`
|
||||
TagOptionIDs []struct {
|
||||
TagID int64 `json:"tag_id"`
|
||||
TagOptionID int64 `json:"tag_option_id"`
|
||||
} `json:"tag_option_ids"`
|
||||
SnippetIDs []struct {
|
||||
SnippetID int64 `json:"snippet_id"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
} `json:"snippet_ids"`
|
||||
}
|
||||
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
||||
fail(w, 400, "请求格式错误")
|
||||
return
|
||||
}
|
||||
|
||||
var claudeID *string
|
||||
if req.ClaudeSessionID != "" {
|
||||
claudeID = &req.ClaudeSessionID
|
||||
}
|
||||
|
||||
_, err = db.DB.Exec("UPDATE builder_sessions SET title=?, project_name=?, final_prompt=?, claude_session_id=? WHERE id=?",
|
||||
req.Title, req.ProjectName, req.FinalPrompt, claudeID, id)
|
||||
if err != nil {
|
||||
fail(w, 500, "更新会话失败")
|
||||
return
|
||||
}
|
||||
|
||||
// Replace tags
|
||||
db.DB.Exec("DELETE FROM builder_session_tags WHERE builder_session_id=?", id)
|
||||
for _, t := range req.TagOptionIDs {
|
||||
db.DB.Exec("INSERT INTO builder_session_tags (builder_session_id, tag_id, tag_option_id) VALUES (?, ?, ?)",
|
||||
id, t.TagID, t.TagOptionID)
|
||||
}
|
||||
|
||||
// Replace snippets
|
||||
db.DB.Exec("DELETE FROM builder_session_snippets WHERE builder_session_id=?", id)
|
||||
for _, s := range req.SnippetIDs {
|
||||
db.DB.Exec("INSERT INTO builder_session_snippets (builder_session_id, snippet_id, sort_order) VALUES (?, ?, ?)",
|
||||
id, s.SnippetID, s.SortOrder)
|
||||
}
|
||||
|
||||
success(w, nil)
|
||||
}
|
||||
|
||||
func DeleteBuilderSession(w http.ResponseWriter, r *http.Request) {
|
||||
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
||||
if err != nil {
|
||||
fail(w, 400, "无效的 ID")
|
||||
return
|
||||
}
|
||||
|
||||
db.DB.Exec("DELETE FROM builder_session_tags WHERE builder_session_id=?", id)
|
||||
db.DB.Exec("DELETE FROM builder_session_snippets WHERE builder_session_id=?", id)
|
||||
db.DB.Exec("DELETE FROM builder_sessions WHERE id=?", id)
|
||||
success(w, nil)
|
||||
}
|
||||
|
||||
// AssemblePrompt 组装最终 Prompt
|
||||
func AssemblePrompt(projectName string, customContent string, tagOptions []models.TagOption, snippets []models.Snippet, suggestions []string) string {
|
||||
var parts []string
|
||||
|
||||
// Project context
|
||||
if projectName != "" {
|
||||
parts = append(parts, fmt.Sprintf("## 项目上下文\n\n项目名称: %s", projectName))
|
||||
}
|
||||
|
||||
// Custom content
|
||||
if customContent != "" {
|
||||
parts = append(parts, fmt.Sprintf("## 用户自定义内容\n\n%s", customContent))
|
||||
}
|
||||
|
||||
// Tag constraints
|
||||
if len(tagOptions) > 0 {
|
||||
var constraints []string
|
||||
for _, opt := range tagOptions {
|
||||
constraints = append(constraints, fmt.Sprintf("- %s", opt.ConstraintText))
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("## 标签约束\n\n%s", strings.Join(constraints, "\n")))
|
||||
}
|
||||
|
||||
// Snippets
|
||||
if len(snippets) > 0 {
|
||||
var snippetParts []string
|
||||
for _, s := range snippets {
|
||||
snippetParts = append(snippetParts, fmt.Sprintf("--- 片段: %s ---\n%s", s.Name, s.Content))
|
||||
}
|
||||
parts = append(parts, fmt.Sprintf("## 片段内容\n\n%s", strings.Join(snippetParts, "\n\n")))
|
||||
}
|
||||
|
||||
// Suggestions
|
||||
if len(suggestions) > 0 {
|
||||
parts = append(parts, fmt.Sprintf("## 智能建议补充\n\n%s", strings.Join(suggestions, "\n")))
|
||||
}
|
||||
|
||||
return strings.Join(parts, "\n\n")
|
||||
}
|
||||
Reference in New Issue
Block a user