package handler import ( "encoding/json" "net/http" "strings" "github.com/zs/InsightReply/internal/service" ) type AIHandler struct { svc *service.AIService profileSvc *service.ProductProfileService strategySvc *service.CustomStrategyService } func NewAIHandler(svc *service.AIService, profileSvc *service.ProductProfileService, strategySvc *service.CustomStrategyService) *AIHandler { return &AIHandler{ svc: svc, profileSvc: profileSvc, strategySvc: strategySvc, } } func (h *AIHandler) Test(w http.ResponseWriter, r *http.Request) { ctx := r.Context() msg, err := h.svc.TestConnection(ctx) if err != nil { SendError(w, http.StatusInternalServerError, 5000, err.Error()) return } SendSuccess(w, map[string]string{"status": msg}) } func (h *AIHandler) Generate(w http.ResponseWriter, r *http.Request) { var body struct { TweetContent string `json:"tweet_content"` Strategy string `json:"strategy"` Identity string `json:"identity"` Provider string `json:"provider,omitempty"` Model string `json:"model,omitempty"` } if err := json.NewDecoder(r.Body).Decode(&body); err != nil { SendError(w, http.StatusBadRequest, 4001, "Invalid request body") return } if body.TweetContent == "" { SendError(w, http.StatusBadRequest, 4002, "Tweet content is required") return } ctx := r.Context() userID := ctx.Value("userID").(string) // Fetch Product Profile Context var productContext string if profile, err := h.profileSvc.GetProfile(userID); err == nil && profile.IsActive { productContext = "Product Context: " + profile.ProductName if profile.Tagline != "" { productContext += " - " + profile.Tagline } if profile.KeyFeatures != "" && profile.KeyFeatures != "[]" { productContext += ". Key Features: " + profile.KeyFeatures } if profile.CustomContext != "" { productContext += ". Context: " + profile.CustomContext } } // Fetch Custom Strategies Context if strategies, err := h.strategySvc.ListStrategies(userID); err == nil && len(strategies) > 0 { productContext += "\n\nAvailable User Custom Strategies:\n" for _, s := range strategies { productContext += "- " + s.StrategyKey + " (" + s.Label + "): " + s.Description + "\n" } } replyString, err := h.svc.GenerateReply(ctx, body.TweetContent, productContext, body.Identity, body.Provider, body.Model) if err != nil { SendError(w, http.StatusBadGateway, 5002, "Failed to generate AI reply: "+err.Error()) return } // Clean up potential markdown wrappers from LLM output cleanReply := strings.TrimSpace(replyString) cleanReply = strings.TrimPrefix(cleanReply, "```json") cleanReply = strings.TrimPrefix(cleanReply, "```") cleanReply = strings.TrimSuffix(cleanReply, "```") cleanReply = strings.TrimSpace(cleanReply) var replies []map[string]interface{} if err := json.Unmarshal([]byte(cleanReply), &replies); err != nil { // Fallback: return as single string object if parsing totally fails replies = []map[string]interface{}{ { "strategy": "Fallback", "content": replyString, }, } } SendSuccess(w, map[string]interface{}{ "replies": replies, }) }