handlers

package
v0.0.0-...-26e635b Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Dec 22, 2025 License: MIT Imports: 24 Imported by: 0

Documentation

Overview

Package handlers provides HTTP handlers for the GenUI web interface.

Package handlers provides HTTP handlers for the GenUI web interface.

Package handlers provides HTTP request handlers for the web server.

Index

Constants

View Source
const (
	// DefaultMessageHistoryLimit is the maximum number of messages to load.
	DefaultMessageHistoryLimit = 50
	// DefaultSidebarSessionLimit is the maximum number of sessions shown in sidebar.
	DefaultSidebarSessionLimit = 20
)

Pagination limits for the chat page.

View Source
const (
	SessionCookieName = "sid"          // Generic name, doesn't leak tech stack
	CSRFTokenTTL      = 24 * time.Hour // Token validity period
	SessionMaxAge     = 30 * 24 * 3600 // 30 days in seconds
	CSRFClockSkew     = 5 * time.Minute
)

Cookie configuration.

View Source
const SSETimeout = 5 * time.Minute

SSETimeout is the maximum duration for an SSE streaming connection. This prevents zombie goroutines from accumulating if clients disconnect without properly closing the connection.

View Source
const TitleGenerationModel = "googleai/gemini-2.5-flash"

TitleGenerationModel is the model used for title generation. Using a fast model to minimize latency.

View Source
const TitleGenerationTimeout = 5 * time.Second

TitleGenerationTimeout is the maximum duration for AI title generation. Keep it short to avoid blocking the SSE stream too long.

View Source
const TitleInputMaxRunes = 500

TitleInputMaxRunes limits the user message length sent to the AI model for title generation, reducing latency and cost.

View Source
const TitleMaxLength = 50

TitleMaxLength is the maximum length for auto-generated session titles.

Variables

View Source
var (
	ErrSessionCookieNotFound = errors.New("session cookie not found")
	ErrSessionInvalid        = errors.New("session ID invalid")
	ErrCSRFRequired          = errors.New("CSRF token required")
	ErrCSRFInvalid           = errors.New("CSRF token invalid")
	ErrCSRFExpired           = errors.New("CSRF token expired")
	ErrCSRFMalformed         = errors.New("CSRF token malformed")
)

Sentinel errors for session/CSRF operations. Note: ErrSessionCookieNotFound is distinct from session.ErrSessionNotFound: - ErrSessionCookieNotFound: HTTP cookie missing from request (HTTP layer) - session.ErrSessionNotFound: session not in database (persistence layer)

Functions

func IsHTMX

func IsHTMX(r *http.Request) bool

IsHTMX returns true if the request was made by HTMX. Use this instead of directly checking r.Header.Get("HX-Request") == "true".

func IsPreSessionToken

func IsPreSessionToken(token string) bool

IsPreSessionToken checks if a token is a pre-session token. Used to determine which validation method to use.

Types

type Chat

type Chat struct {
	// contains filtered or unexported fields
}

Chat handles chat-related HTTP requests. If flow is nil, the handler operates in simulation mode (returns canned responses). This allows development and testing without full Genkit initialization.

func NewChat

func NewChat(cfg ChatConfig) (*Chat, error)

NewChat creates a new Chat handler. Returns error if required dependencies are nil. flow is optional - if nil, simulation mode is used. genkit is optional - if nil, AI title generation falls back to truncation.

func (*Chat) Send

func (h *Chat) Send(w http.ResponseWriter, r *http.Request)

Send handles POST /genui/chat/send (HTMX form submission). Supports lazy session creation. When pre-session CSRF token is received, creates a new session and returns OOB swaps to update the hidden form fields with new session_id and csrf_token.

func (*Chat) Stream

func (h *Chat) Stream(w http.ResponseWriter, r *http.Request)

Stream handles GET /genui/stream?msgId=X&session_id=Y (SSE endpoint). Each assistant message creates its own SSE connection. Query is retrieved from database to avoid URL length limits.

type ChatConfig

type ChatConfig struct {
	Logger      *slog.Logger
	Genkit      *genkit.Genkit                                 // Optional: nil disables AI title generation
	Flow        *chat.Flow                                     // Optional: nil enables simulation mode
	Sessions    *Sessions                                      // Session management
	SSEWriterFn func(w http.ResponseWriter) (SSEWriter, error) // Optional: nil uses default sse.NewWriter
}

ChatConfig contains configuration for the Chat handler.

type Health

type Health struct{}

Health handles health check endpoints for Docker/Kubernetes probes.

func NewHealth

func NewHealth() *Health

NewHealth creates a health check handler.

func (*Health) RegisterRoutes

func (*Health) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers health check routes on the given mux.

type Pages

type Pages struct {
	// contains filtered or unexported fields
}

Pages handles page rendering requests.

func NewPages

func NewPages(cfg PagesConfig) (*Pages, error)

NewPages creates a new Pages handler. Returns error if required dependencies are nil.

func (*Pages) Chat

func (h *Pages) Chat(w http.ResponseWriter, r *http.Request)

Chat renders the main chat page with message history. Supports lazy session creation (pre-session state for fresh visitors). Session ID is retrieved from cookie if present; if not, page renders in pre-session state.

Query Parameters: - session_id: Switch to an existing session (used by sidebar session links) - new: Create a new empty session (used by "New Chat" button)

func (*Pages) RegisterRoutes

func (h *Pages) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers page routes.

type PagesConfig

type PagesConfig struct {
	Logger   *slog.Logger
	Sessions *Sessions
}

PagesConfig contains configuration for the Pages handler.

type SSEToolEmitter

type SSEToolEmitter struct {
	// contains filtered or unexported fields
}

SSEToolEmitter implements ToolEventEmitter using SSE writer. Per architecture-master: Per-request binding for tool event streaming. UI presentation logic handled here, not in tools layer.

Usage:

  1. Created in streamWithFlow when starting SSE stream
  2. Stored in context via tools.ContextWithEmitter()
  3. Retrieved by wrapped tools via tools.EmitterFromContext()

func NewSSEToolEmitter

func NewSSEToolEmitter(writer *sse.Writer, messageID string) *SSEToolEmitter

NewSSEToolEmitter creates a new tool event emitter bound to an SSE writer. messageID is used for targeting the correct indicator div (per htmx-master).

func (*SSEToolEmitter) OnToolComplete

func (e *SSEToolEmitter) OnToolComplete(name string)

OnToolComplete sends a tool complete event via SSE.

func (*SSEToolEmitter) OnToolError

func (e *SSEToolEmitter) OnToolError(name string)

OnToolError sends a tool error event via SSE.

func (*SSEToolEmitter) OnToolStart

func (e *SSEToolEmitter) OnToolStart(name string)

OnToolStart sends a tool start event via SSE. Looks up UI display info from tool_display.go. Per qa-master: Escapes messageID to prevent XSS injection.

type SSEWriter

type SSEWriter interface {
	WriteChunkRaw(msgID, htmlContent string) error
	WriteDone(ctx context.Context, msgID string, comp templ.Component) error
	WriteError(msgID, code, message string) error
	WriteSidebarRefresh(sessionID, title string) error // For title auto-generation
}

SSEWriter defines the interface for SSE streaming operations. This interface enables dependency injection for testing.

type Sessions

type Sessions struct {
	// contains filtered or unexported fields
}

Sessions handles HTTP session cookies and CSRF token operations. Named as plural noun (Go stdlib style: http.Cookies, not CookieManager). Uses composition with session.Store for database persistence.

func NewSessions

func NewSessions(store *session.Store, hmacSecret []byte, isDev bool) *Sessions

NewSessions creates a Sessions handler with the given store and HMAC secret. The secret must be at least 32 bytes for HMAC-SHA256 security. isDev should be true for local development (HTTP) to ensure cookies work without HTTPS.

func (*Sessions) CheckCSRF

func (s *Sessions) CheckCSRF(sessionID uuid.UUID, token string) error

CheckCSRF verifies the token signature and checks expiration. Returns nil on success, or a specific error describing the failure.

func (*Sessions) CheckPreSessionCSRF

func (s *Sessions) CheckPreSessionCSRF(token string) error

CheckPreSessionCSRF verifies a pre-session CSRF token. Returns nil on success, or a specific error describing the failure.

func (*Sessions) Create

func (s *Sessions) Create(w http.ResponseWriter, r *http.Request)

Create handles POST /genui/sessions - creates new session and redirects. Pure HTMX: Uses HX-Redirect header for client-side navigation.

func (*Sessions) Delete

func (s *Sessions) Delete(w http.ResponseWriter, r *http.Request)

Delete handles DELETE /genui/sessions/:id - deletes session and triggers sidebar refresh. Returns JavaScript that: 1. Closes the @tailwindplus/elements dialog (calls hide() on el-dialog parent) 2. Dispatches sidebar-refresh event to body (htmx sidebars listen for this) 3. Redirects to /genui if deleting current session (via HX-Redirect header)

func (*Sessions) GetOrCreate

func (s *Sessions) GetOrCreate(w http.ResponseWriter, r *http.Request) (uuid.UUID, error)

GetOrCreate retrieves session ID from cookie/query or creates a new session. Session selection priority: 1. Query parameter "session_id" - for switching to existing sessions 2. Cookie - for resuming the current session 3. Create new - only when no existing session is found On success, sets/refreshes the session cookie and returns the session UUID.

func (*Sessions) ID

func (*Sessions) ID(r *http.Request) (uuid.UUID, error)

ID extracts session ID from cookie without creating new session. Returns ErrSessionCookieNotFound if cookie is missing, ErrSessionInvalid if malformed.

func (*Sessions) List

func (s *Sessions) List(w http.ResponseWriter, r *http.Request)

List handles GET /genui/sessions - returns HTML session list for sidebar. Pure HTMX: Returns HTML fragment, not JSON. Returns ONLY the inner content (session items) without wrapper div. The sidebar uses hx-swap="innerHTML" which replaces the content inside the <ul>. Uses ListSessionsWithMessages to only show sessions with messages or titles.

func (*Sessions) NewCSRFToken

func (s *Sessions) NewCSRFToken(sessionID uuid.UUID) string

NewCSRFToken creates an HMAC-based token: "timestamp:signature". The token is bound to the session ID and has a limited lifetime.

func (*Sessions) NewPreSessionCSRFToken

func (s *Sessions) NewPreSessionCSRFToken() string

NewPreSessionCSRFToken creates an HMAC-based token for pre-session state. Used when user hasn't interacted yet (lazy session creation). Format: "pre:nonce:timestamp:signature" The token is NOT bound to a session ID - uses random nonce instead.

func (*Sessions) RegisterRoutes

func (s *Sessions) RegisterRoutes(mux *http.ServeMux)

RegisterRoutes registers session HTTP routes on the given mux.

func (*Sessions) SetSessionCookie

func (s *Sessions) SetSessionCookie(w http.ResponseWriter, sessionID uuid.UUID)

SetSessionCookie sets the session cookie for the given session ID. This is exposed for handlers that need to update the cookie after session switching.

func (*Sessions) Store

func (s *Sessions) Store() *session.Store

Store returns the underlying session store for direct access. Use sparingly - prefer using Sessions methods where possible.