86 lines
3.1 KiB
Go
86 lines
3.1 KiB
Go
package handlers
|
|
|
|
import (
|
|
"net/http"
|
|
|
|
"git.threesix.ai/jordan/persona-community-2/pkg/app"
|
|
"git.threesix.ai/jordan/persona-community-2/pkg/auth"
|
|
"git.threesix.ai/jordan/persona-community-2/pkg/httperror"
|
|
"git.threesix.ai/jordan/persona-community-2/pkg/httpresponse"
|
|
"git.threesix.ai/jordan/persona-community-2/pkg/logging"
|
|
"git.threesix.ai/jordan/persona-community-2/pkg/queue"
|
|
)
|
|
|
|
// Persona handles HTTP requests for persona generation.
|
|
// All generation is async: validate request, enqueue job, return 202 with job ID.
|
|
// Results are delivered via SSE events to the user's `user:<userId>` channel:
|
|
//
|
|
// - persona_spec_started: LLM pipeline started
|
|
// - persona_spec_complete: Persona profile generated
|
|
// - persona_image_started: Starting a specific image position
|
|
// - persona_image_progress: Image position complete with URL
|
|
// - persona_image_complete: All 20 images generated
|
|
// - persona_video_started: Starting a video motion type
|
|
// - persona_video_complete: Video complete with URL
|
|
// - persona_failed: Generation failed (check error field)
|
|
type Persona struct {
|
|
queue queue.Producer
|
|
jobReader queue.JobReader
|
|
logger *logging.Logger
|
|
}
|
|
|
|
// NewPersona creates a new Persona handler with injected dependencies.
|
|
func NewPersona(q queue.Producer, jr queue.JobReader, logger *logging.Logger) *Persona {
|
|
return &Persona{
|
|
queue: q,
|
|
jobReader: jr,
|
|
logger: logger.WithComponent("PersonaHandler"),
|
|
}
|
|
}
|
|
|
|
// GeneratePersonaRequest is the request body for persona generation.
|
|
type GeneratePersonaRequest struct {
|
|
// Description is a natural-language persona concept (required).
|
|
// Example: "mysterious woman with dark hair who loves poetry"
|
|
Description string `json:"description" validate:"required,min=3,max=1000"`
|
|
|
|
// Gender is the gender identity: "woman", "man", or "non_binary" (required).
|
|
Gender string `json:"gender" validate:"required,oneof=woman man non_binary"`
|
|
|
|
// Name is an optional name override for the generated persona.
|
|
Name string `json:"name"`
|
|
}
|
|
|
|
// GeneratePersona queues a persona generation job.
|
|
// Returns immediately with job ID. Full lifecycle results come via SSE.
|
|
//
|
|
// Subscribe to SSE channel `user:<userId>` at /api/persona-api/events before calling.
|
|
// Poll job status at GET /generate/jobs/{id} as a fallback to SSE.
|
|
func (h *Persona) GeneratePersona(w http.ResponseWriter, r *http.Request) error {
|
|
var req GeneratePersonaRequest
|
|
if err := app.BindAndValidate(r, &req); err != nil {
|
|
return err
|
|
}
|
|
|
|
user := auth.GetUser(r.Context())
|
|
if user == nil {
|
|
return httperror.Unauthorized("authentication required")
|
|
}
|
|
|
|
jobID, err := h.queue.Enqueue(r.Context(), "persona_generate", map[string]any{
|
|
"description": req.Description,
|
|
"gender": req.Gender,
|
|
"name": req.Name,
|
|
"userID": user.ID,
|
|
})
|
|
if err != nil {
|
|
h.logger.Error("failed to enqueue persona job", "error", err)
|
|
return httperror.Internal("failed to queue persona generation")
|
|
}
|
|
|
|
h.logger.Info("persona generation queued", "jobId", jobID, "userID", user.ID)
|
|
|
|
httpresponse.Accepted(w, r, GenerateAccepted{JobID: jobID})
|
|
return nil
|
|
}
|