2fa6f0df62
- analytics.go: UsagePeriodFilter, UsageSummary, TimeSeries, ProvidersUsage, ClientsUsage, AnalyticsBreakdown, DetailedUsage - models_config.go: handleGetModels, handleUpdateModel - Fix all import blocks with missing closing parens - Remove leftover fmt.Printf warnings in server.go
242 lines
6.4 KiB
Go
242 lines
6.4 KiB
Go
package server
|
|
|
|
import (
|
|
"database/sql"
|
|
"fmt"
|
|
"net/http"
|
|
"os"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/google/uuid"
|
|
"golang.org/x/crypto/bcrypt"
|
|
"gophergate/internal/db"
|
|
"gophergate/internal/models"
|
|
"gophergate/internal/utils"
|
|
"log/slog"
|
|
|
|
"github.com/shirou/gopsutil/v3/cpu"
|
|
)
|
|
|
|
func (s *Server) handleGetModels(c *gin.Context) {
|
|
usedOnly := c.Query("used_only") == "true"
|
|
|
|
// Registry provider normalized name -> Proxy-internal provider ID
|
|
allowedRegistryProviders := map[string]string{
|
|
"openai": "openai",
|
|
"google": "gemini",
|
|
"deepseek": "deepseek",
|
|
"xai": "grok",
|
|
"ollama": "ollama",
|
|
}
|
|
|
|
// Merge registry models with DB overrides
|
|
var dbModels []db.ModelConfig
|
|
_ = s.database.Select(&dbModels, "SELECT * FROM model_configs")
|
|
|
|
dbMap := make(map[string]db.ModelConfig)
|
|
for _, m := range dbModels {
|
|
dbMap[m.ID] = m
|
|
}
|
|
|
|
// Fetch specific (model, provider) combinations that have been used
|
|
type modelProvider struct {
|
|
Model string `db:"model"`
|
|
Provider string `db:"provider"`
|
|
}
|
|
usedPairs := make(map[string]bool)
|
|
if usedOnly {
|
|
var pairs []modelProvider
|
|
err := s.database.Select(&pairs, "SELECT DISTINCT model, provider FROM llm_requests WHERE status = 'success'")
|
|
if err == nil {
|
|
for _, p := range pairs {
|
|
usedPairs[fmt.Sprintf("%s:%s", p.Model, p.Provider)] = true
|
|
}
|
|
}
|
|
}
|
|
|
|
var result []gin.H
|
|
s.registryMu.RLock()
|
|
if s.registry != nil {
|
|
for pID, pInfo := range s.registry.Providers {
|
|
proxyProvider, allowed := allowedRegistryProviders[pID]
|
|
if !allowed {
|
|
continue
|
|
}
|
|
|
|
for mID, mMeta := range pInfo.Models {
|
|
if usedOnly && !usedPairs[fmt.Sprintf("%s:%s", mID, proxyProvider)] {
|
|
continue
|
|
}
|
|
|
|
enabled := true
|
|
promptCost := 0.0
|
|
completionCost := 0.0
|
|
var cacheReadCost *float64
|
|
var cacheWriteCost *float64
|
|
var mapping *string
|
|
contextLimit := uint32(0)
|
|
|
|
if mMeta.Cost != nil {
|
|
promptCost = mMeta.Cost.Input
|
|
completionCost = mMeta.Cost.Output
|
|
cacheReadCost = mMeta.Cost.CacheRead
|
|
cacheWriteCost = mMeta.Cost.CacheWrite
|
|
}
|
|
if mMeta.Limit != nil {
|
|
contextLimit = mMeta.Limit.Context
|
|
}
|
|
|
|
// Override from DB
|
|
if dbCfg, ok := dbMap[mID]; ok {
|
|
enabled = dbCfg.Enabled
|
|
if dbCfg.PromptCostPerM != nil {
|
|
promptCost = *dbCfg.PromptCostPerM
|
|
}
|
|
if dbCfg.CompletionCostPerM != nil {
|
|
completionCost = *dbCfg.CompletionCostPerM
|
|
}
|
|
if dbCfg.CacheReadCostPerM != nil {
|
|
cacheReadCost = dbCfg.CacheReadCostPerM
|
|
}
|
|
if dbCfg.CacheWriteCostPerM != nil {
|
|
cacheWriteCost = dbCfg.CacheWriteCostPerM
|
|
}
|
|
mapping = dbCfg.Mapping
|
|
}
|
|
|
|
result = append(result, gin.H{
|
|
"id": mID,
|
|
"name": mMeta.Name,
|
|
"provider": proxyProvider,
|
|
"enabled": enabled,
|
|
"prompt_cost": promptCost,
|
|
"completion_cost": completionCost,
|
|
"cache_read_cost": cacheReadCost,
|
|
"cache_write_cost": cacheWriteCost,
|
|
"context_limit": contextLimit,
|
|
"mapping": mapping,
|
|
"tool_call": mMeta.ToolCall != nil && *mMeta.ToolCall,
|
|
"reasoning": mMeta.Reasoning != nil && *mMeta.Reasoning,
|
|
"modalities": mMeta.Modalities,
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
// Add configured Ollama models if they aren't in registry
|
|
if s.cfg.Providers.Ollama.Enabled {
|
|
for _, mID := range s.cfg.Providers.Ollama.Models {
|
|
// Check if already added from registry
|
|
exists := false
|
|
for _, r := range result {
|
|
if r["id"] == mID {
|
|
exists = true
|
|
break
|
|
}
|
|
}
|
|
if exists {
|
|
continue
|
|
}
|
|
|
|
if usedOnly && !usedPairs[fmt.Sprintf("%s:ollama", mID)] {
|
|
continue
|
|
}
|
|
|
|
enabled := true
|
|
promptCost := 0.0
|
|
completionCost := 0.0
|
|
var cacheReadCost *float64
|
|
var cacheWriteCost *float64
|
|
var mapping *string
|
|
contextLimit := uint32(0)
|
|
|
|
// Override from DB
|
|
if dbCfg, ok := dbMap[mID]; ok {
|
|
enabled = dbCfg.Enabled
|
|
if dbCfg.PromptCostPerM != nil {
|
|
promptCost = *dbCfg.PromptCostPerM
|
|
}
|
|
if dbCfg.CompletionCostPerM != nil {
|
|
completionCost = *dbCfg.CompletionCostPerM
|
|
}
|
|
if dbCfg.CacheReadCostPerM != nil {
|
|
cacheReadCost = dbCfg.CacheReadCostPerM
|
|
}
|
|
if dbCfg.CacheWriteCostPerM != nil {
|
|
cacheWriteCost = dbCfg.CacheWriteCostPerM
|
|
}
|
|
mapping = dbCfg.Mapping
|
|
}
|
|
|
|
result = append(result, gin.H{
|
|
"id": mID,
|
|
"name": mID,
|
|
"provider": "ollama",
|
|
"enabled": enabled,
|
|
"prompt_cost": promptCost,
|
|
"completion_cost": completionCost,
|
|
"cache_read_cost": cacheReadCost,
|
|
"cache_write_cost": cacheWriteCost,
|
|
"context_limit": contextLimit,
|
|
"modalities": gin.H{"input": []string{"text"}, "output": []string{"text"}},
|
|
"tool_call": false,
|
|
"reasoning": false,
|
|
"mapping": mapping,
|
|
})
|
|
}
|
|
}
|
|
|
|
c.JSON(http.StatusOK, SuccessResponse(result))
|
|
}
|
|
|
|
func (s *Server) handleUpdateModel(c *gin.Context) {
|
|
id := c.Param("id")
|
|
var req struct {
|
|
Enabled bool `json:"enabled"`
|
|
PromptCost float64 `json:"prompt_cost"`
|
|
CompletionCost float64 `json:"completion_cost"`
|
|
CacheReadCost *float64 `json:"cache_read_cost"`
|
|
CacheWriteCost *float64 `json:"cache_write_cost"`
|
|
Mapping *string `json:"mapping"`
|
|
}
|
|
if err := c.ShouldBindJSON(&req); err != nil {
|
|
c.JSON(http.StatusBadRequest, ErrorResponse("Invalid request"))
|
|
return
|
|
}
|
|
|
|
// Find provider for this model
|
|
providerID := "unknown"
|
|
s.registryMu.RLock()
|
|
if s.registry != nil {
|
|
for pID, pInfo := range s.registry.Providers {
|
|
if _, ok := pInfo.Models[id]; ok {
|
|
providerID = pID
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
_, err := s.database.Exec(`
|
|
INSERT INTO model_configs (id, provider_id, enabled, prompt_cost_per_m, completion_cost_per_m, cache_read_cost_per_m, cache_write_cost_per_m, mapping)
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
ON CONFLICT(id) DO UPDATE SET
|
|
enabled = excluded.enabled,
|
|
prompt_cost_per_m = excluded.prompt_cost_per_m,
|
|
completion_cost_per_m = excluded.completion_cost_per_m,
|
|
cache_read_cost_per_m = excluded.cache_read_cost_per_m,
|
|
cache_write_cost_per_m = excluded.cache_write_cost_per_m,
|
|
mapping = excluded.mapping,
|
|
updated_at = CURRENT_TIMESTAMP
|
|
`, id, providerID, req.Enabled, req.PromptCost, req.CompletionCost, req.CacheReadCost, req.CacheWriteCost, req.Mapping)
|
|
|
|
if err != nil {
|
|
c.JSON(http.StatusInternalServerError, ErrorResponse(err.Error()))
|
|
return
|
|
}
|
|
|
|
c.JSON(http.StatusOK, SuccessResponse(gin.H{"message": "Model updated"}))
|
|
}
|
|
|