475 lines
17 KiB
Go
475 lines
17 KiB
Go
package webdemo
|
|
|
|
import (
|
|
"encoding/json"
|
|
"fmt"
|
|
"math"
|
|
"net/http"
|
|
"sort"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
)
|
|
|
|
func (input *fakeInput) ServeHTTP(response http.ResponseWriter, request *http.Request) {
|
|
input.mu.Lock()
|
|
defer input.mu.Unlock()
|
|
|
|
response.Header().Set("Content-Type", "application/json; charset=utf-8")
|
|
path := strings.TrimPrefix(request.URL.Path, managementBase)
|
|
if request.URL.Path == resourceUI {
|
|
path = "/" + strings.TrimSpace(request.URL.Query().Get("view"))
|
|
if path == "/usage" {
|
|
query := request.URL.Query()
|
|
query.Set("page", "1")
|
|
query.Set("page_size", "50")
|
|
request.URL.RawQuery = query.Encode()
|
|
}
|
|
}
|
|
var payload any
|
|
var err error
|
|
switch {
|
|
case request.Method == http.MethodGet && path == "/usage":
|
|
payload = input.usagePage(request)
|
|
case request.Method == http.MethodGet && path == "/usage-summary":
|
|
payload = input.dashboard()
|
|
case request.Method == http.MethodGet && path == "/keys":
|
|
payload = input.listKeys(request.URL.Query().Get("include_archived") == "1")
|
|
case request.Method == http.MethodPost && path == "/keys":
|
|
payload, err = input.createKey(request)
|
|
case request.Method == http.MethodPatch && path == "/keys":
|
|
payload, err = input.updateKey(request)
|
|
case request.Method == http.MethodDelete && path == "/keys":
|
|
payload, err = input.archiveKey(request)
|
|
case request.Method == http.MethodGet && path == "/key-stats":
|
|
payload = input.keyStats(request.URL.Query().Get("id"))
|
|
case request.Method == http.MethodGet && path == "/upstreams":
|
|
payload = map[string]any{"accounts": input.upstreams}
|
|
case request.Method == http.MethodGet && path == "/model-suggestions":
|
|
payload = map[string]any{"models": input.models}
|
|
case request.Method == http.MethodGet && path == "/billing-ledger":
|
|
payload = input.billingLedger(request)
|
|
case request.Method == http.MethodGet && path == "/events":
|
|
payload = map[string]any{"events": input.events}
|
|
case request.Method == http.MethodPost && path == "/billing-reset":
|
|
payload, err = input.resetBilling(request)
|
|
case request.Method == http.MethodGet && path == "/prices":
|
|
payload = map[string]any{"prices": input.prices}
|
|
case request.Method == http.MethodPut && path == "/prices":
|
|
payload, err = input.putPrice(request)
|
|
case request.Method == http.MethodDelete && path == "/prices":
|
|
payload, err = input.deletePrice(request)
|
|
case request.Method == http.MethodGet && path == "/price-catalog":
|
|
payload = input.searchCatalog(request.URL.Query().Get("q"))
|
|
case request.Method == http.MethodPost && path == "/prices/import":
|
|
payload, err = input.importPrice(request)
|
|
case request.Method == http.MethodPost && path == "/price-catalog/refresh":
|
|
payload = map[string]any{"catalog": input.catalogInfo(), "changes": []any{}}
|
|
case request.Method == http.MethodPost && path == "/price-catalog/apply":
|
|
payload = map[string]any{"updated": 0}
|
|
default:
|
|
writeJSON(response, http.StatusNotFound, map[string]any{"error": map[string]string{"code": "not_found", "message": request.Method + " " + path}})
|
|
return
|
|
}
|
|
if err != nil {
|
|
writeJSON(response, http.StatusBadRequest, map[string]any{"error": map[string]string{"code": "fake_input_error", "message": err.Error()}})
|
|
return
|
|
}
|
|
writeJSON(response, http.StatusOK, payload)
|
|
}
|
|
|
|
func writeJSON(response http.ResponseWriter, status int, payload any) {
|
|
response.WriteHeader(status)
|
|
_ = json.NewEncoder(response).Encode(payload)
|
|
}
|
|
|
|
func decodeBody(request *http.Request, target any) error {
|
|
decoder := json.NewDecoder(request.Body)
|
|
decoder.UseNumber()
|
|
return decoder.Decode(target)
|
|
}
|
|
|
|
func (input *fakeInput) listKeys(includeArchived bool) map[string]any {
|
|
keys := make([]fakeKey, 0, len(input.keys))
|
|
for _, key := range input.keys {
|
|
if includeArchived || key.Status != "archived" {
|
|
keys = append(keys, key)
|
|
}
|
|
}
|
|
return map[string]any{"keys": keys}
|
|
}
|
|
|
|
func (input *fakeInput) createKey(request *http.Request) (fakeKey, error) {
|
|
var value struct {
|
|
Name string `json:"name"`
|
|
Secret string `json:"secret"`
|
|
RouteMode string `json:"route_mode"`
|
|
UpstreamAccountID string `json:"upstream_account_id"`
|
|
AllModels bool `json:"all_models"`
|
|
ShowInStats *bool `json:"show_in_stats"`
|
|
Models []string `json:"models"`
|
|
Billing map[string]any `json:"billing"`
|
|
}
|
|
if err := decodeBody(request, &value); err != nil {
|
|
return fakeKey{}, err
|
|
}
|
|
if strings.TrimSpace(value.Name) == "" {
|
|
return fakeKey{}, fmt.Errorf("name is required")
|
|
}
|
|
if value.Secret == "" {
|
|
value.Secret = fmt.Sprintf("demo-generated-%06d", len(input.keys)+1)
|
|
}
|
|
showInStats := true
|
|
if value.ShowInStats != nil {
|
|
showInStats = *value.ShowInStats
|
|
}
|
|
key := fakeKey{ID: fmt.Sprintf("key_demo_%d", len(input.keys)+1), Name: value.Name, Secret: value.Secret, MaskedSecret: maskFakeSecret(value.Secret), Status: "active", RouteMode: value.RouteMode, UpstreamAccountID: value.UpstreamAccountID, AllModels: value.AllModels, ShowInStats: showInStats, Models: value.Models, Billing: normalizeFakeBilling(value.Billing, nil)}
|
|
input.keys = append(input.keys, key)
|
|
return key, nil
|
|
}
|
|
|
|
func (input *fakeInput) updateKey(request *http.Request) (fakeKey, error) {
|
|
var value map[string]any
|
|
if err := decodeBody(request, &value); err != nil {
|
|
return fakeKey{}, err
|
|
}
|
|
index := input.keyIndex(fmt.Sprint(value["id"]))
|
|
if index < 0 {
|
|
return fakeKey{}, fmt.Errorf("key not found")
|
|
}
|
|
key := input.keys[index]
|
|
if name := strings.TrimSpace(fmt.Sprint(value["name"])); name != "" {
|
|
key.Name = name
|
|
}
|
|
if status := strings.TrimSpace(fmt.Sprint(value["status"])); status != "" {
|
|
key.Status = status
|
|
}
|
|
if routeMode := strings.TrimSpace(fmt.Sprint(value["route_mode"])); routeMode != "" {
|
|
key.RouteMode = routeMode
|
|
}
|
|
key.UpstreamAccountID = stringValue(value["upstream_account_id"])
|
|
if allModels, ok := value["all_models"].(bool); ok {
|
|
key.AllModels = allModels
|
|
}
|
|
if showInStats, ok := value["show_in_stats"].(bool); ok {
|
|
key.ShowInStats = showInStats
|
|
}
|
|
if models, ok := value["models"].([]any); ok {
|
|
key.Models = make([]string, 0, len(models))
|
|
for _, model := range models {
|
|
key.Models = append(key.Models, fmt.Sprint(model))
|
|
}
|
|
}
|
|
if billing, ok := value["billing"].(map[string]any); ok {
|
|
key.Billing = normalizeFakeBilling(billing, key.Billing)
|
|
}
|
|
input.keys[index] = key
|
|
return key, nil
|
|
}
|
|
|
|
func (input *fakeInput) archiveKey(request *http.Request) (map[string]any, error) {
|
|
var value map[string]any
|
|
if err := decodeBody(request, &value); err != nil {
|
|
return nil, err
|
|
}
|
|
index := input.keyIndex(fmt.Sprint(value["id"]))
|
|
if index < 0 {
|
|
return nil, fmt.Errorf("key not found")
|
|
}
|
|
input.keys[index].Status = "archived"
|
|
return map[string]any{"archived": true}, nil
|
|
}
|
|
|
|
func (input *fakeInput) keyIndex(id string) int {
|
|
for index := range input.keys {
|
|
if input.keys[index].ID == id {
|
|
return index
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
func maskFakeSecret(value string) string {
|
|
if len(value) < 6 {
|
|
return "******"
|
|
}
|
|
return value[:2] + "******" + value[len(value)-4:]
|
|
}
|
|
|
|
func normalizeFakeBilling(value, previous map[string]any) map[string]any {
|
|
result := map[string]any{"quota_usd": "0", "spent_usd": "0", "lifetime_spent_usd": "0", "balance_usd": "0", "reset_period": "none", "next_reset_at": nil, "max_concurrency": 4, "active_requests": 0, "cycle_started_at": time.Now()}
|
|
for key, item := range previous {
|
|
result[key] = item
|
|
}
|
|
for key, item := range value {
|
|
result[key] = item
|
|
}
|
|
quota := toFloat(result["quota_usd"])
|
|
spent := toFloat(result["spent_usd"])
|
|
result["quota_usd"] = fmt.Sprint(result["quota_usd"])
|
|
result["balance_usd"] = fmt.Sprintf("%.6f", quota-spent)
|
|
return result
|
|
}
|
|
|
|
func stringValue(value any) string {
|
|
if value == nil {
|
|
return ""
|
|
}
|
|
return fmt.Sprint(value)
|
|
}
|
|
|
|
func (input *fakeInput) usagePage(request *http.Request) map[string]any {
|
|
query := request.URL.Query()
|
|
records := make([]fakeUsage, 0, len(input.usage))
|
|
for _, record := range input.usage {
|
|
if query.Get("key_id") != "" && query.Get("key_id") != record.ManagedKeyID || query.Get("model") != "" && !strings.Contains(strings.ToLower(record.Model), strings.ToLower(query.Get("model"))) || query.Get("result") != "" && query.Get("result") != usageResult(record) || query.Get("auth_id") != "" && query.Get("auth_id") != record.AuthID || query.Get("endpoint") != "" && query.Get("endpoint") != endpointName(record.Endpoint) || query.Get("request_id") != "" && query.Get("request_id") != record.RequestID {
|
|
continue
|
|
}
|
|
if from := parseTime(query.Get("from")); !from.IsZero() && record.RequestedAt.Before(from) {
|
|
continue
|
|
}
|
|
if to := parseTime(query.Get("to")); !to.IsZero() && !record.RequestedAt.Before(to) {
|
|
continue
|
|
}
|
|
records = append(records, record)
|
|
}
|
|
pageSize := boundedInt(query.Get("page_size"), 100, 1, 100)
|
|
totalPages := int(math.Ceil(float64(len(records)) / float64(pageSize)))
|
|
page := boundedInt(query.Get("page"), 1, 1, max(1, totalPages))
|
|
start := min((page-1)*pageSize, len(records))
|
|
end := min(start+pageSize, len(records))
|
|
previous, next := "", ""
|
|
if page > 1 {
|
|
previous = "fake-previous"
|
|
}
|
|
if page < totalPages {
|
|
next = "fake-next"
|
|
}
|
|
return map[string]any{"records": records[start:end], "pagination": map[string]any{"page": page, "page_size": pageSize, "total": len(records), "total_pages": totalPages, "previous_cursor": previous, "next_cursor": next}}
|
|
}
|
|
|
|
func usageResult(record fakeUsage) string {
|
|
if record.Outcome == "canceled" || record.Outcome == "rejected" || record.Outcome == "failed" {
|
|
return record.Outcome
|
|
}
|
|
return "succeeded"
|
|
}
|
|
|
|
func endpointName(value string) string {
|
|
if strings.HasSuffix(value, "/responses/compact") {
|
|
return "compact"
|
|
}
|
|
if strings.HasSuffix(value, "/chat/completions") {
|
|
return "chat"
|
|
}
|
|
return "responses"
|
|
}
|
|
|
|
func parseTime(value string) time.Time {
|
|
parsed, _ := time.Parse(time.RFC3339, value)
|
|
return parsed
|
|
}
|
|
|
|
func boundedInt(value string, fallback, minimum, maximum int) int {
|
|
parsed, err := strconv.Atoi(value)
|
|
if err != nil {
|
|
return fallback
|
|
}
|
|
return min(max(parsed, minimum), maximum)
|
|
}
|
|
|
|
func summarize(records []fakeUsage) map[string]any {
|
|
var inputTokens, outputTokens, totalTokens int64
|
|
var cost float64
|
|
for _, record := range records {
|
|
inputTokens += record.InputTokens
|
|
outputTokens += record.OutputTokens
|
|
totalTokens += record.TotalTokens
|
|
if record.CostAvailable {
|
|
cost += record.CostUSD
|
|
}
|
|
}
|
|
return map[string]any{"requests": len(records), "input_tokens": inputTokens, "output_tokens": outputTokens, "total_tokens": totalTokens, "cost_usd": cost, "cost_micros": int64(math.Round(cost * 1_000_000))}
|
|
}
|
|
|
|
func (input *fakeInput) dashboard() map[string]any {
|
|
today := startOfDay(input.now)
|
|
todayRecords := filterUsage(input.usage, func(record fakeUsage) bool { return !record.RequestedAt.Before(today) })
|
|
users := make([]map[string]any, 0, len(input.keys))
|
|
for _, key := range input.keys {
|
|
if key.Status == "archived" || !key.ShowInStats {
|
|
continue
|
|
}
|
|
records := filterUsage(input.usage, func(record fakeUsage) bool { return record.ManagedKeyID == key.ID })
|
|
current := filterUsage(records, func(record fakeUsage) bool { return !record.RequestedAt.Before(today) })
|
|
userDays := make([]map[string]any, 0, 7)
|
|
for offset := 6; offset >= 0; offset-- {
|
|
start := today.AddDate(0, 0, -offset)
|
|
end := start.AddDate(0, 0, 1)
|
|
daySummary := summarize(filterUsage(records, func(record fakeUsage) bool {
|
|
return !record.RequestedAt.Before(start) && record.RequestedAt.Before(end)
|
|
}))
|
|
userDays = append(userDays, map[string]any{"date": start.Format("2006-01-02"), "total_tokens": daySummary["total_tokens"], "cost_usd": daySummary["cost_usd"]})
|
|
}
|
|
var last any
|
|
if len(records) > 0 {
|
|
last = records[0].RequestedAt
|
|
}
|
|
users = append(users, map[string]any{"key_id": key.ID, "key_alias": key.Name, "today": summarize(current), "days": userDays, "last_used_at": last})
|
|
}
|
|
days := make([]map[string]any, 0, 7)
|
|
for offset := 6; offset >= 0; offset-- {
|
|
start := today.AddDate(0, 0, -offset)
|
|
end := start.AddDate(0, 0, 1)
|
|
records := filterUsage(input.usage, func(record fakeUsage) bool {
|
|
return !record.RequestedAt.Before(start) && record.RequestedAt.Before(end)
|
|
})
|
|
summary := summarize(records)
|
|
days = append(days, map[string]any{"date": start.Format("2006-01-02"), "total_tokens": summary["total_tokens"], "cost_usd": summary["cost_usd"]})
|
|
}
|
|
return map[string]any{"today": summarize(todayRecords), "users": users, "days": days}
|
|
}
|
|
|
|
func startOfDay(value time.Time) time.Time {
|
|
return time.Date(value.Year(), value.Month(), value.Day(), 0, 0, 0, 0, value.Location())
|
|
}
|
|
|
|
func filterUsage(records []fakeUsage, keep func(fakeUsage) bool) []fakeUsage {
|
|
result := make([]fakeUsage, 0, len(records))
|
|
for _, record := range records {
|
|
if keep(record) {
|
|
result = append(result, record)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
func (input *fakeInput) keyStats(id string) map[string]any {
|
|
records := filterUsage(input.usage, func(record fakeUsage) bool { return record.ManagedKeyID == id })
|
|
today := startOfDay(input.now)
|
|
current := filterUsage(records, func(record fakeUsage) bool { return !record.RequestedAt.Before(today) })
|
|
return map[string]any{"stats": map[string]any{"total": summarize(records), "today": summarize(current)}, "recent": records[:min(10, len(records))]}
|
|
}
|
|
|
|
func (input *fakeInput) billingLedger(request *http.Request) map[string]any {
|
|
id := request.URL.Query().Get("id")
|
|
limit := boundedInt(request.URL.Query().Get("limit"), 50, 1, 100)
|
|
entries := make([]fakeLedger, 0, limit)
|
|
for _, entry := range input.ledger {
|
|
if entry.KeyID == id && len(entries) < limit {
|
|
entries = append(entries, entry)
|
|
}
|
|
}
|
|
return map[string]any{"entries": entries}
|
|
}
|
|
|
|
func (input *fakeInput) resetBilling(request *http.Request) (map[string]any, error) {
|
|
var value map[string]any
|
|
if err := decodeBody(request, &value); err != nil {
|
|
return nil, err
|
|
}
|
|
if all, _ := value["all"].(bool); all {
|
|
count := 0
|
|
for index := range input.keys {
|
|
if input.keys[index].Status == "archived" {
|
|
continue
|
|
}
|
|
input.keys[index].Billing["spent_usd"] = "0"
|
|
input.keys[index].Billing["balance_usd"] = fmt.Sprint(input.keys[index].Billing["quota_usd"])
|
|
input.keys[index].Billing["cycle_started_at"] = time.Now()
|
|
count++
|
|
}
|
|
return map[string]any{"reset_count": count}, nil
|
|
}
|
|
index := input.keyIndex(fmt.Sprint(value["id"]))
|
|
if index < 0 {
|
|
return nil, fmt.Errorf("key not found")
|
|
}
|
|
input.keys[index].Billing["spent_usd"] = "0"
|
|
input.keys[index].Billing["balance_usd"] = fmt.Sprint(input.keys[index].Billing["quota_usd"])
|
|
input.keys[index].Billing["cycle_started_at"] = time.Now()
|
|
return input.keys[index].Billing, nil
|
|
}
|
|
|
|
func (input *fakeInput) putPrice(request *http.Request) (map[string]any, error) {
|
|
var value map[string]any
|
|
if err := decodeBody(request, &value); err != nil {
|
|
return nil, err
|
|
}
|
|
model := strings.TrimSpace(fmt.Sprint(value["model"]))
|
|
if model == "" {
|
|
return nil, fmt.Errorf("model is required")
|
|
}
|
|
value["source"] = map[string]any{"kind": "manual"}
|
|
input.storePrice(model, value)
|
|
return value, nil
|
|
}
|
|
|
|
func (input *fakeInput) deletePrice(request *http.Request) (map[string]any, error) {
|
|
var value map[string]any
|
|
if err := decodeBody(request, &value); err != nil {
|
|
return nil, err
|
|
}
|
|
model := fmt.Sprint(value["model"])
|
|
filtered := input.prices[:0]
|
|
for _, price := range input.prices {
|
|
if fmt.Sprint(price["model"]) != model {
|
|
filtered = append(filtered, price)
|
|
}
|
|
}
|
|
input.prices = filtered
|
|
return map[string]any{"deleted": model}, nil
|
|
}
|
|
|
|
func (input *fakeInput) storePrice(model string, value map[string]any) {
|
|
for index := range input.prices {
|
|
if fmt.Sprint(input.prices[index]["model"]) == model {
|
|
input.prices[index] = value
|
|
return
|
|
}
|
|
}
|
|
input.prices = append(input.prices, value)
|
|
sort.Slice(input.prices, func(left, right int) bool {
|
|
return fmt.Sprint(input.prices[left]["model"]) < fmt.Sprint(input.prices[right]["model"])
|
|
})
|
|
}
|
|
|
|
func (input *fakeInput) catalogInfo() map[string]any {
|
|
return map[string]any{"revision": "fake-2026-08-19", "models": len(input.catalog), "fetched_at": input.now}
|
|
}
|
|
|
|
func (input *fakeInput) searchCatalog(query string) map[string]any {
|
|
normalized := strings.ToLower(strings.TrimSpace(query))
|
|
models := make([]map[string]any, 0, len(input.catalog))
|
|
for _, entry := range input.catalog {
|
|
values := []any{entry["id"], entry["provider"], entry["provider_name"], entry["model"], entry["model_name"]}
|
|
for _, value := range values {
|
|
if normalized == "" || strings.Contains(strings.ToLower(fmt.Sprint(value)), normalized) {
|
|
models = append(models, entry)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
return map[string]any{"loaded": true, "catalog": input.catalogInfo(), "models": models}
|
|
}
|
|
|
|
func (input *fakeInput) importPrice(request *http.Request) (map[string]any, error) {
|
|
var value map[string]any
|
|
if err := decodeBody(request, &value); err != nil {
|
|
return nil, err
|
|
}
|
|
model, catalogID := fmt.Sprint(value["model"]), fmt.Sprint(value["catalog_id"])
|
|
for _, entry := range input.catalog {
|
|
if fmt.Sprint(entry["id"]) != catalogID {
|
|
continue
|
|
}
|
|
price := map[string]any{"model": model, "base": entry["base"], "fast_pricing_enabled": false, "fast_multiplier": "2.5", "source": map[string]any{"kind": "models.dev", "provider": entry["provider"], "model": entry["model"], "catalog_id": catalogID, "revision": "fake-2026-08-19"}}
|
|
if longContext, ok := entry["long_context"]; ok {
|
|
price["long_context"] = longContext
|
|
}
|
|
input.storePrice(model, price)
|
|
return price, nil
|
|
}
|
|
return nil, fmt.Errorf("catalog entry not found")
|
|
}
|