Files

251 lines
12 KiB
Go

package webdemo
import (
"fmt"
"sync"
"time"
)
type fakeInput struct {
mu sync.Mutex
keys []fakeKey
upstreams []fakeUpstream
models []string
usage []fakeUsage
prices []map[string]any
catalog []map[string]any
ledger []fakeLedger
events []fakeEvent
now time.Time
}
const fakeUsageCount = 50_000
type fakeKey struct {
ID string `json:"id"`
Name string `json:"name"`
Secret string `json:"secret"`
MaskedSecret string `json:"masked_secret"`
Status string `json:"status"`
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"`
}
type fakeUpstream struct {
ID string `json:"id"`
CPAAuthID string `json:"cpa_auth_id"`
Provider string `json:"provider"`
DisplayName string `json:"display_name"`
Priority int `json:"priority"`
Disabled bool `json:"disabled"`
Unavailable bool `json:"unavailable"`
}
type fakeUsage struct {
RequestID string `json:"request_id"`
TraceID string `json:"trace_id"`
RequestedAt time.Time `json:"requested_at"`
ManagedKeyID string `json:"managed_key_id"`
KeyAlias string `json:"key_alias"`
APIKey string `json:"api_key"`
AuthID string `json:"auth_id"`
AuthIndex string `json:"auth_index"`
AuthType string `json:"auth_type"`
Model string `json:"model"`
ReasoningEffort string `json:"reasoning_effort"`
ServiceTier string `json:"service_tier"`
Speed string `json:"speed"`
Failed bool `json:"failed"`
Outcome string `json:"outcome"`
StatusCode int `json:"status_code"`
RequestType string `json:"request_type"`
Endpoint string `json:"endpoint"`
TTFTMilliseconds int64 `json:"ttft_ms"`
SpeedTPS float64 `json:"speed_tps"`
InputTokens int64 `json:"input_tokens"`
OutputTokens int64 `json:"output_tokens"`
ReasoningTokens int64 `json:"reasoning_tokens"`
CacheReadTokens int64 `json:"cache_read_tokens"`
CacheWriteTokens int64 `json:"cache_write_tokens"`
CacheRate float64 `json:"cache_rate"`
TotalTokens int64 `json:"total_tokens"`
CostAvailable bool `json:"cost_available"`
CostUSD float64 `json:"cost_usd"`
ClientIP string `json:"client_ip"`
}
type fakeLedger struct {
ID int64 `json:"id"`
KeyID string `json:"key_id"`
Kind string `json:"kind"`
AmountUSD string `json:"amount_usd"`
BalanceAfterUSD string `json:"balance_after_usd"`
RequestID string `json:"request_id,omitempty"`
Model string `json:"model,omitempty"`
OccurredAt time.Time `json:"occurred_at"`
}
type fakeEvent struct {
ID int64 `json:"id"`
OccurredAt time.Time `json:"occurred_at"`
Event string `json:"event"`
Status string `json:"status"`
}
func NewFakeInput() Input {
now := time.Now()
input := &fakeInput{now: now}
input.upstreams = []fakeUpstream{
{ID: "upstream-primary", CPAAuthID: "codex:deepseek:primary", Provider: "codex", DisplayName: "DeepSeek 主账号", Priority: 10},
{ID: "upstream-backup", CPAAuthID: "codex:deepseek:backup", Provider: "codex", DisplayName: "DeepSeek 备用账号", Priority: 20},
{ID: "upstream-paused", CPAAuthID: "codex:openai:paused", Provider: "codex", DisplayName: "OpenAI 暂停账号", Priority: 30, Disabled: true},
}
input.models = []string{"deepseek-v4-flash", "deepseek-reasoner", "gpt-5.6-sol"}
input.keys = fakeKeys(now)
input.usage = fakeUsageRecords(now, input.keys, input.upstreams, input.models)
populateFakeLifetimeSpend(input.keys, input.usage)
input.prices = fakePrices()
input.catalog = fakeCatalog()
input.ledger = fakeLedgerRecords(now, input.keys, input.models)
input.events = fakeEvents(now)
return input
}
func fakeEvents(now time.Time) []fakeEvent {
values := []struct {
event string
status string
}{
{"管理员修改模型 `gpt-5.6-sol` 的价格", "成功"},
{"管理员手动重置用户 `Alice` 的额度", "成功"},
{"管理员手动重置全部用户额度", "成功,处理 4 个用户"},
{"管理员调整用户 `Bob` 的额度:$8 → $10", "成功"},
{"系统执行用户 `默认用户` 的额度自然重置", "成功"},
{"管理员归档用户 `旧测试账号` 的 Key", "成功"},
{"管理员创建用户 `Carol` 的 Key", "成功"},
{"管理员删除模型 `legacy-model` 的价格", "失败:模型价格不存在"},
{"管理员新增模型 `deepseek-v4-flash` 的价格", "成功"},
}
events := make([]fakeEvent, 0, len(values))
for index, value := range values {
events = append(events, fakeEvent{ID: int64(len(values) - index), OccurredAt: now.Add(-time.Duration(index*37) * time.Minute), Event: value.event, Status: value.status})
}
return events
}
func fakeKeys(now time.Time) []fakeKey {
monthlyReset := time.Date(now.Year(), now.Month()+1, 1, 0, 0, 0, 0, now.Location())
monthlyStart := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location())
return []fakeKey{
{ID: "key_default", Name: "默认用户", Secret: "demo-default-000000", MaskedSecret: "de******0000", Status: "active", RouteMode: "auto", AllModels: true, ShowInStats: true, Billing: fakeBilling("50", "18.427631", "31.572369", "monthly", monthlyReset, 8, 1, monthlyStart)},
{ID: "key_alice", Name: "Alice", Secret: "demo-alice-000000", MaskedSecret: "de******0000", Status: "active", RouteMode: "strict", UpstreamAccountID: "upstream-primary", ShowInStats: true, Models: []string{"deepseek-*", "gpt-5.6-sol"}, Billing: fakeBilling("20", "6.983214", "13.016786", "weekly", now.Add(4*24*time.Hour), 4, 0, now.Add(-3*24*time.Hour))},
{ID: "key_bob", Name: "Bob", Secret: "demo-bob-000000", MaskedSecret: "de******0000", Status: "active", RouteMode: "strict", UpstreamAccountID: "upstream-backup", ShowInStats: true, Models: []string{"deepseek-v4-flash"}, Billing: fakeBilling("10", "9.764502", "0.235498", "none", time.Time{}, 2, 2, now.Add(-18*24*time.Hour))},
{ID: "key_carol", Name: "Carol", Secret: "demo-carol-000000", MaskedSecret: "de******0000", Status: "disabled", RouteMode: "auto", ShowInStats: true, Models: []string{"gpt-5.6-sol"}, Billing: fakeBilling("15", "2.154800", "12.845200", "monthly", monthlyReset, 4, 0, monthlyStart)},
}
}
func fakeBilling(quota, spent, balance, period string, next time.Time, concurrency, active int, started time.Time) map[string]any {
var nextValue any
if !next.IsZero() {
nextValue = next
}
return map[string]any{"quota_usd": quota, "spent_usd": spent, "lifetime_spent_usd": spent, "balance_usd": balance, "reset_period": period, "next_reset_at": nextValue, "max_concurrency": concurrency, "active_requests": active, "cycle_started_at": started}
}
func populateFakeLifetimeSpend(keys []fakeKey, records []fakeUsage) {
totals := make(map[string]float64, len(keys))
for _, record := range records {
if record.CostAvailable {
totals[record.ManagedKeyID] += record.CostUSD
}
}
for index := range keys {
keys[index].Billing["lifetime_spent_usd"] = fmt.Sprintf("%.6f", totals[keys[index].ID])
}
}
func fakeUsageRecords(now time.Time, keys []fakeKey, upstreams []fakeUpstream, models []string) []fakeUsage {
records := make([]fakeUsage, fakeUsageCount)
for index := range records {
key := keys[index%len(keys)]
inputTokens := int64(1200 + (index*977)%48000)
outputTokens := int64(180 + (index*113)%6200)
reasoningTokens := int64(0)
if index%3 == 0 {
reasoningTokens = int64(80 + (index*31)%1600)
}
cacheReadTokens := int64(0)
if index%4 == 0 {
cacheReadTokens = inputTokens * 62 / 100
} else if index%4 == 1 {
cacheReadTokens = inputTokens * 18 / 100
}
outcome, statusCode := "succeeded", httpStatusOK
if index%31 == 0 {
outcome, statusCode = "failed", 502
} else if index%43 == 0 {
outcome, statusCode = "canceled", 499
} else if index%47 == 0 {
outcome, statusCode = "rejected", 429
}
endpoint, requestType := "/v1/responses", "SSE"
if index%17 == 0 {
endpoint, requestType = "/v1/responses/compact", "JSON"
} else if index%11 == 0 {
endpoint = "/v1/chat/completions"
}
clientIP := ""
if index%5 == 0 {
clientIP = fmt.Sprintf("10.0.0.%d", index%20+10)
}
totalTokens := inputTokens + outputTokens
records[index] = fakeUsage{
RequestID: fmt.Sprintf("req_demo_%05d", index+1), TraceID: fmt.Sprintf("trace_demo_%05d", index+1), RequestedAt: now.Add(-time.Duration(index) * 19 * time.Minute), ManagedKeyID: key.ID, KeyAlias: key.Name, APIKey: key.MaskedSecret,
AuthID: upstreams[index%2].CPAAuthID, AuthIndex: fmt.Sprint(index%2 + 1), AuthType: "codex", Model: models[index%len(models)], ReasoningEffort: []string{"high", "medium", "low"}[index%3], ServiceTier: map[bool]string{true: "priority", false: "auto"}[index%8 == 0], Speed: map[bool]string{true: "fast", false: ""}[index%8 == 0],
Failed: outcome == "failed", Outcome: outcome, StatusCode: statusCode, RequestType: requestType, Endpoint: endpoint, TTFTMilliseconds: int64(180 + index%720), SpeedTPS: 24 + float64(index%55)*0.7,
InputTokens: inputTokens, OutputTokens: outputTokens, ReasoningTokens: reasoningTokens, CacheReadTokens: cacheReadTokens, CacheWriteTokens: map[bool]int64{true: 240, false: 0}[index%13 == 0], CacheRate: float64(cacheReadTokens) / float64(inputTokens) * 100, TotalTokens: totalTokens, CostAvailable: true, CostUSD: float64(inputTokens)*0.00000014 + float64(outputTokens)*0.00000028, ClientIP: clientIP,
}
}
return records
}
const httpStatusOK = 200
func fakePrices() []map[string]any {
return []map[string]any{
{"model": "deepseek-v4-flash", "base": map[string]any{"input_per_1m": "0.14", "cache_read_per_1m": "0.014", "cache_write_per_1m": "0.14", "output_per_1m": "0.28"}, "fast_pricing_enabled": false, "fast_multiplier": "2.5", "source": map[string]any{"kind": "models.dev", "provider": "deepseek", "model": "deepseek-v4-flash", "catalog_id": "deepseek/deepseek-v4-flash"}},
{"model": "gpt-5.6-sol", "base": map[string]any{"input_per_1m": "5", "cache_read_per_1m": "0.5", "cache_write_per_1m": "6.25", "output_per_1m": "30"}, "long_context": map[string]any{"threshold_input_tokens": 272000, "comparison": "gt", "input_per_1m": "10", "cache_read_per_1m": "1", "cache_write_per_1m": "12.5", "output_per_1m": "45"}, "fast_pricing_enabled": true, "fast_multiplier": "2.5", "source": map[string]any{"kind": "manual"}},
}
}
func fakeCatalog() []map[string]any {
return []map[string]any{
{"id": "deepseek/deepseek-v4-flash", "provider": "deepseek", "provider_name": "DeepSeek", "model": "deepseek-v4-flash", "model_name": "DeepSeek V4 Flash", "base": map[string]any{"input_per_1m": "0.14", "cache_read_per_1m": "0.014", "cache_write_per_1m": "0.14", "output_per_1m": "0.28"}},
{"id": "openai/gpt-5.6-sol", "provider": "openai", "provider_name": "OpenAI", "model": "gpt-5.6-sol", "model_name": "GPT-5.6 Sol", "base": map[string]any{"input_per_1m": "5", "cache_read_per_1m": "0.5", "cache_write_per_1m": "6.25", "output_per_1m": "30"}, "long_context": map[string]any{"threshold_input_tokens": 272000, "comparison": "gt", "input_per_1m": "10", "cache_read_per_1m": "1", "cache_write_per_1m": "12.5", "output_per_1m": "45"}},
}
}
func fakeLedgerRecords(now time.Time, keys []fakeKey, models []string) []fakeLedger {
entries := make([]fakeLedger, 0, len(keys)*8)
for keyIndex, key := range keys {
for index := 0; index < 8; index++ {
entry := fakeLedger{ID: int64(keyIndex*10 + index + 1), KeyID: key.ID, Kind: "charge", AmountUSD: fmt.Sprintf("-%.6f", 0.08+float64(index)*0.017), BalanceAfterUSD: fmt.Sprintf("%.6f", toFloat(key.Billing["balance_usd"])+float64(index)*0.097), RequestID: fmt.Sprintf("req_demo_%05d", keyIndex+index*len(keys)+1), Model: models[index%len(models)], OccurredAt: now.Add(-time.Duration(index) * 5 * time.Hour)}
if index == 7 {
entry.Kind, entry.AmountUSD, entry.RequestID, entry.Model = "quota_change", fmt.Sprint(key.Billing["quota_usd"]), "", ""
}
entries = append(entries, entry)
}
}
return entries
}
func toFloat(value any) float64 {
var result float64
_, _ = fmt.Sscan(fmt.Sprint(value), &result)
return result
}