Changeset 0.9.0 (#50)

This commit is contained in:
2026-02-23 01:57:28 +00:00
parent 15be26c516
commit 8264aa6016
94 changed files with 9812 additions and 8574 deletions

View File

@@ -1,77 +1,69 @@
package handlers
import (
"log"
"net/http"
"git.gobha.me/xcaliber/chat-switchboard/database"
"github.com/gin-gonic/gin"
"git.gobha.me/xcaliber/chat-switchboard/models"
"git.gobha.me/xcaliber/chat-switchboard/store"
)
// GetModelPreferences returns the user's hidden model list.
// GET /api/v1/models/preferences
func GetModelPreferences(c *gin.Context) {
// ModelPrefsHandler handles user model preference endpoints.
type ModelPrefsHandler struct {
stores store.Stores
}
func NewModelPrefsHandler(s store.Stores) *ModelPrefsHandler {
return &ModelPrefsHandler{stores: s}
}
// GetPreferences returns the user's model preferences.
func (h *ModelPrefsHandler) GetPreferences(c *gin.Context) {
userID := getUserID(c)
rows, err := database.DB.Query(`
SELECT model_id, hidden FROM user_model_preferences
WHERE user_id = $1
`, userID)
prefs, err := h.stores.UserSettings.GetForUser(c.Request.Context(), userID)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to query preferences"})
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get preferences"})
return
}
defer rows.Close()
type pref struct {
ModelID string `json:"model_id"`
Hidden bool `json:"hidden"`
}
prefs := make([]pref, 0)
for rows.Next() {
var p pref
if err := rows.Scan(&p.ModelID, &p.Hidden); err != nil {
continue
}
prefs = append(prefs, p)
}
c.JSON(http.StatusOK, gin.H{"preferences": prefs})
}
// SetModelPreference sets hidden state for a single model.
// PUT /api/v1/models/preferences
func SetModelPreference(c *gin.Context) {
// SetPreference upserts a single model preference.
func (h *ModelPrefsHandler) SetPreference(c *gin.Context) {
userID := getUserID(c)
var req struct {
ModelID string `json:"model_id" binding:"required"`
Hidden bool `json:"hidden"`
ModelID string `json:"model_id" binding:"required"`
Hidden *bool `json:"hidden,omitempty"`
PreferredTemperature *float64 `json:"preferred_temperature,omitempty"`
PreferredMaxTokens *int `json:"preferred_max_tokens,omitempty"`
SortOrder *int `json:"sort_order,omitempty"`
}
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
return
}
_, err := database.DB.Exec(`
INSERT INTO user_model_preferences (user_id, model_id, hidden, updated_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (user_id, model_id)
DO UPDATE SET hidden = EXCLUDED.hidden, updated_at = NOW()
`, userID, req.ModelID, req.Hidden)
if err != nil {
log.Printf("[WARN] Failed to save model preference for user %s, model %s: %v", userID, req.ModelID, err)
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to save preference: " + err.Error()})
patch := models.UserModelSettingPatch{
Hidden: req.Hidden,
PreferredTemperature: req.PreferredTemperature,
PreferredMaxTokens: req.PreferredMaxTokens,
SortOrder: req.SortOrder,
}
if err := h.stores.UserSettings.Set(c.Request.Context(), userID, req.ModelID, patch); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to set preference"})
return
}
c.JSON(http.StatusOK, gin.H{"model_id": req.ModelID, "hidden": req.Hidden})
c.JSON(http.StatusOK, gin.H{"message": "preference updated"})
}
// BulkSetModelPreferences sets hidden state for multiple models at once.
// POST /api/v1/models/preferences/bulk
func BulkSetModelPreferences(c *gin.Context) {
// BulkSetPreferences sets hidden state for multiple models at once.
func (h *ModelPrefsHandler) BulkSetPreferences(c *gin.Context) {
userID := getUserID(c)
var req struct {
@@ -83,43 +75,10 @@ func BulkSetModelPreferences(c *gin.Context) {
return
}
if len(req.ModelIDs) == 0 {
c.JSON(http.StatusOK, gin.H{"updated": 0})
if err := h.stores.UserSettings.BulkSetHidden(c.Request.Context(), userID, req.ModelIDs, req.Hidden); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to bulk update"})
return
}
tx, err := database.DB.Begin()
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to begin transaction"})
return
}
defer tx.Rollback()
stmt, err := tx.Prepare(`
INSERT INTO user_model_preferences (user_id, model_id, hidden, updated_at)
VALUES ($1, $2, $3, NOW())
ON CONFLICT (user_id, model_id)
DO UPDATE SET hidden = EXCLUDED.hidden, updated_at = NOW()
`)
if err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to prepare statement"})
return
}
defer stmt.Close()
updated := 0
for _, modelID := range req.ModelIDs {
if _, err := stmt.Exec(userID, modelID, req.Hidden); err != nil {
log.Printf("[WARN] Failed to save preference for model %s: %v", modelID, err)
continue
}
updated++
}
if err := tx.Commit(); err != nil {
c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to commit"})
return
}
c.JSON(http.StatusOK, gin.H{"updated": updated})
c.JSON(http.StatusOK, gin.H{"message": "preferences updated", "count": len(req.ModelIDs)})
}