feat: structure view in dashboard and settings persistence
- Add GET /api/claude/sessions/{session_id}/builder API for structure view
- Load builder session tags and snippets by Claude session ID
- Implement structure view in dashboard: shows tag selections and snippet details
- Add LoadSettings to persist LLM config changes across restarts
- Settings are now loaded from database on startup
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -109,6 +109,36 @@ func AutoMigrate() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// LoadSettings loads persisted settings from the database into config
|
||||
func LoadSettings(cfg *config.Config) {
|
||||
// Create settings table if not exists
|
||||
DB.Exec(`CREATE TABLE IF NOT EXISTS settings (
|
||||
key_name varchar(64) NOT NULL PRIMARY KEY,
|
||||
value text NOT NULL
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`)
|
||||
|
||||
rows, err := DB.Query("SELECT key_name, value FROM settings")
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var key, value string
|
||||
if err := rows.Scan(&key, &value); err != nil {
|
||||
continue
|
||||
}
|
||||
switch key {
|
||||
case "llm_api_base_url":
|
||||
cfg.LLMAPIBaseURL = value
|
||||
case "llm_api_key":
|
||||
cfg.LLMAPIKey = value
|
||||
case "llm_model_name":
|
||||
cfg.LLMModelName = value
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// EnsurePromptsTable creates the prompts table if it doesn't exist (for dev environments)
|
||||
func EnsurePromptsTable() {
|
||||
_, err := DB.Exec(`CREATE TABLE IF NOT EXISTS prompts (
|
||||
|
||||
@@ -4,6 +4,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"prompt-generator/internal/db"
|
||||
"prompt-generator/internal/models"
|
||||
)
|
||||
|
||||
func GetClaudeSessions(w http.ResponseWriter, r *http.Request) {
|
||||
@@ -47,6 +48,81 @@ func GetClaudeSessions(w http.ResponseWriter, r *http.Request) {
|
||||
success(w, sessions)
|
||||
}
|
||||
|
||||
// GetBuilderSessionByClaudeSession looks up a builder session by claude_session_id
|
||||
func GetBuilderSessionByClaudeSession(w http.ResponseWriter, r *http.Request) {
|
||||
sessionID := r.PathValue("session_id")
|
||||
if sessionID == "" {
|
||||
fail(w, 400, "session_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 claude_session_id=?", sessionID).
|
||||
Scan(&s.ID, &s.Title, &s.ProjectName, &s.FinalPrompt, &s.ClaudeSessionID, &s.CreatedAt, &s.UpdatedAt)
|
||||
if err != nil {
|
||||
// Not found is not an error
|
||||
success(w, nil)
|
||||
return
|
||||
}
|
||||
|
||||
// Load tags with details
|
||||
tags := []map[string]interface{}{}
|
||||
tagRows, err := db.DB.Query(`
|
||||
SELECT bst.tag_id, bst.tag_option_id, t.name, to2.label, to2.constraint_text
|
||||
FROM builder_session_tags bst
|
||||
JOIN tags t ON t.id = bst.tag_id
|
||||
JOIN tag_options to2 ON to2.id = bst.tag_option_id
|
||||
WHERE bst.builder_session_id=?`, s.ID)
|
||||
if err == nil {
|
||||
for tagRows.Next() {
|
||||
var tagID, optionID int64
|
||||
var tagName, optionLabel, constraintText string
|
||||
if err := tagRows.Scan(&tagID, &optionID, &tagName, &optionLabel, &constraintText); err == nil {
|
||||
tags = append(tags, map[string]interface{}{
|
||||
"tag_id": tagID,
|
||||
"tag_option_id": optionID,
|
||||
"tag_name": tagName,
|
||||
"option_label": optionLabel,
|
||||
"constraint_text": constraintText,
|
||||
})
|
||||
}
|
||||
}
|
||||
tagRows.Close()
|
||||
}
|
||||
|
||||
// Load snippets with details
|
||||
snippets := []map[string]interface{}{}
|
||||
snippetRows, err := db.DB.Query(`
|
||||
SELECT bss.snippet_id, s.name, s.content, s.category, bss.sort_order
|
||||
FROM builder_session_snippets bss
|
||||
JOIN snippets s ON s.id = bss.snippet_id
|
||||
WHERE bss.builder_session_id=?
|
||||
ORDER BY bss.sort_order`, s.ID)
|
||||
if err == nil {
|
||||
for snippetRows.Next() {
|
||||
var snippetID int64
|
||||
var name, content, category string
|
||||
var sortOrder int
|
||||
if err := snippetRows.Scan(&snippetID, &name, &content, &category, &sortOrder); err == nil {
|
||||
snippets = append(snippets, map[string]interface{}{
|
||||
"snippet_id": snippetID,
|
||||
"name": name,
|
||||
"content": content,
|
||||
"category": category,
|
||||
"sort_order": sortOrder,
|
||||
})
|
||||
}
|
||||
}
|
||||
snippetRows.Close()
|
||||
}
|
||||
|
||||
success(w, map[string]interface{}{
|
||||
"session": s,
|
||||
"tags": tags,
|
||||
"snippets": snippets,
|
||||
})
|
||||
}
|
||||
|
||||
func GetClaudePrompts(w http.ResponseWriter, r *http.Request) {
|
||||
sessionID := r.PathValue("session_id")
|
||||
if sessionID == "" {
|
||||
|
||||
Reference in New Issue
Block a user