251 lines
12 KiB
Go
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
|
|
}
|