Files
sundynix-agentix/sundynix-gateway/internal/handler/prompt.go
T
Blizzard 5736ad145e feat(prompts): v2 DB 控制面热切换 —— 版本留存 + 不重启即生效
在 v1(注册表+文件覆盖)上加 DB 管理层与热切换,镜像 model-config 控制面:
- store: sundynix_prompt 表(key/version/content/active) + ActivePrompts/ListPrompts/
  CreateVersion/Activate/Deactivate
- 控制面: ServePrompts/RequestActivePrompts(+Retry)/PublishPromptsUpdated/SubscribePromptsUpdated;
  prompts.ApplyOverrides 整体替换覆盖集(DB 激活集为权威)
- gateway API: GET/POST /api/v1/prompts、version/activate/deactivate;激活/撤销即广播
- dispatcher/mcp-go: 启动拉激活集 + 订阅热更新(不重启)
- live: 建版本→激活→mcp-go 图谱抽取 2→0→回滚 2(全程不重启);deactivate 回退代码默认;版本可回溯

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-30 14:59:52 +08:00

82 lines
3.0 KiB
Go

package handler
import (
"net/http"
"github.com/gin-gonic/gin"
"github.com/sundynix/sundynix-shared/prompts"
)
// PromptList: GET /api/v1/prompts —— 列出受管 prompt 的全部版本 + 可配键(管理端浏览/对比/回滚)。
func (h *Handler) PromptList(c *gin.Context) {
rows, err := h.db.ListPrompts(c.Request.Context())
if err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
return
}
out := make([]gin.H, 0, len(rows))
for _, r := range rows {
out = append(out, gin.H{"key": r.Key, "version": r.Version, "active": r.Active, "note": r.Note, "content": r.Content})
}
c.JSON(http.StatusOK, gin.H{"keys": prompts.Known, "versions": out})
}
// PromptCreateVersion: POST /api/v1/prompts/version {key, content, note} —— 为某 key 建新版本(不自动激活)。
func (h *Handler) PromptCreateVersion(c *gin.Context) {
var body struct {
Key string `json:"key"`
Content string `json:"content"`
Note string `json:"note"`
}
if err := c.ShouldBindJSON(&body); err != nil || body.Key == "" || body.Content == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "key/content required"})
return
}
v, err := h.db.CreatePromptVersion(c.Request.Context(), body.Key, body.Content, body.Note)
if err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"key": body.Key, "version": v})
}
// PromptActivate: POST /api/v1/prompts/activate {key, version} —— 激活某版本 → 经控制面热下发各服务(不重启即生效)。
func (h *Handler) PromptActivate(c *gin.Context) {
var body struct {
Key string `json:"key"`
Version int `json:"version"`
}
if err := c.ShouldBindJSON(&body); err != nil || body.Key == "" || body.Version <= 0 {
c.JSON(http.StatusBadRequest, gin.H{"error": "key/version required"})
return
}
if err := h.db.ActivatePrompt(c.Request.Context(), body.Key, body.Version); err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
return
}
// 广播最新激活集 → dispatcher/mcp-go 热更新覆盖。
if err := h.bus.PublishPromptsUpdated(h.db.ActivePrompts(c.Request.Context())); err != nil {
c.JSON(http.StatusOK, gin.H{"key": body.Key, "version": body.Version, "warn": "已激活但广播失败: " + err.Error()})
return
}
c.JSON(http.StatusOK, gin.H{"key": body.Key, "version": body.Version, "activated": true})
}
// PromptDeactivate: POST /api/v1/prompts/deactivate {key} —— 撤销某 key 激活,回退代码内置默认(热下发)。
func (h *Handler) PromptDeactivate(c *gin.Context) {
var body struct {
Key string `json:"key"`
}
if err := c.ShouldBindJSON(&body); err != nil || body.Key == "" {
c.JSON(http.StatusBadRequest, gin.H{"error": "key required"})
return
}
if err := h.db.DeactivatePrompt(c.Request.Context(), body.Key); err != nil {
c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()})
return
}
_ = h.bus.PublishPromptsUpdated(h.db.ActivePrompts(c.Request.Context()))
c.JSON(http.StatusOK, gin.H{"key": body.Key, "deactivated": true})
}