feat!: 账号池改渠道网关,对外协议换成 vivid /v1
- 新增 channels/channel_models:上游 URL + AES-GCM 加密 key +
并发/权重/本地统计,模型经渠道 /v1/models 导入绑定
- internal/upstream:vivid 协议客户端(NDJSON 流 + jobId 断流重连 +
uploads 预签名 + models + balance + 错误分类)
- service/v1 重写:权重分组调度、Redis 并发槽 conc:c:、故障转移,
站内/测试/对外统一走渠道执行器
- 对外端点换成 vivid 四件套:POST /v1/generate(NDJSON+jobId 重连)、
POST /v1/uploads(SigV4 预签名 PUT)、GET /v1/models、GET /v1/balance,
错误包 {"error":{"code","message"}}
- API key 新发改 vk_+48hex;删除全部内置 provider 与 token 池
- 管理端:渠道 CRUD/拉模型/导入/余额端点,前端账号页换渠道页,
概览/文档/测试弹窗适配
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
@@ -19,20 +19,20 @@ type AdminReadService struct {
|
||||
models *repo.ModelRepository
|
||||
events *repo.EventRepository
|
||||
settings *repo.SiteSettingRepository
|
||||
tokens *repo.TokenRepository
|
||||
channels *repo.ChannelRepository
|
||||
cdks *repo.CDKRepository
|
||||
store *storage.Client
|
||||
showcase *repo.ShowcaseRepository
|
||||
}
|
||||
|
||||
func NewAdminReadService(cfg *config.Config, users *repo.UserRepository, models *repo.ModelRepository, events *repo.EventRepository, settings *repo.SiteSettingRepository, tokens *repo.TokenRepository, cdks *repo.CDKRepository, store *storage.Client, showcase *repo.ShowcaseRepository) *AdminReadService {
|
||||
func NewAdminReadService(cfg *config.Config, users *repo.UserRepository, models *repo.ModelRepository, events *repo.EventRepository, settings *repo.SiteSettingRepository, channels *repo.ChannelRepository, cdks *repo.CDKRepository, store *storage.Client, showcase *repo.ShowcaseRepository) *AdminReadService {
|
||||
return &AdminReadService{
|
||||
cfg: cfg,
|
||||
users: users,
|
||||
models: models,
|
||||
events: events,
|
||||
settings: settings,
|
||||
tokens: tokens,
|
||||
channels: channels,
|
||||
cdks: cdks,
|
||||
store: store,
|
||||
showcase: showcase,
|
||||
@@ -104,6 +104,10 @@ func (s *AdminReadService) ModelsView(ctx context.Context) ([]map[string]any, er
|
||||
"max_reference_images": item.MaxReferenceImages,
|
||||
"max_reference_videos": item.MaxReferenceVideos,
|
||||
"max_reference_audios": item.MaxReferenceAudios,
|
||||
"duration_min": item.DurationMin,
|
||||
"duration_max": item.DurationMax,
|
||||
"max_ref_video_seconds": item.MaxRefVideoSeconds,
|
||||
"max_ref_audio_seconds": item.MaxRefAudioSeconds,
|
||||
"reference_mode": item.ReferenceMode,
|
||||
"weight": item.Weight,
|
||||
"generation_count": item.GenerationCount,
|
||||
@@ -196,24 +200,24 @@ func (s *AdminReadService) UserNameMap(ctx context.Context) (map[string]string,
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// AccountNameMap builds a token-account id -> display label lookup (account
|
||||
// email, else display name, else id) used to annotate log rows with which
|
||||
// provider account fulfilled each generation (event_logs.account_id).
|
||||
// AccountNameMap builds a channel id -> display name lookup used to annotate
|
||||
// log rows with which channel fulfilled each generation (event_logs.account_id
|
||||
// now stores the channel id; account_email carries a denormalized name stamp).
|
||||
func (s *AdminReadService) AccountNameMap(ctx context.Context) (map[string]string, error) {
|
||||
accounts, err := s.tokens.List(ctx)
|
||||
if s.channels == nil {
|
||||
return map[string]string{}, nil
|
||||
}
|
||||
channels, err := s.channels.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out := make(map[string]string, len(accounts))
|
||||
for _, a := range accounts {
|
||||
label := strings.TrimSpace(a.AccountEmail)
|
||||
out := make(map[string]string, len(channels))
|
||||
for _, ch := range channels {
|
||||
label := strings.TrimSpace(ch.Name)
|
||||
if label == "" {
|
||||
label = strings.TrimSpace(a.AccountDisplayName)
|
||||
label = ch.ID
|
||||
}
|
||||
if label == "" {
|
||||
label = a.ID
|
||||
}
|
||||
out[a.ID] = label
|
||||
out[ch.ID] = label
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
@@ -422,66 +426,30 @@ func (s *AdminReadService) Invites(ctx context.Context) ([]repo.InviteRecord, *r
|
||||
return s.users.AllInvites(ctx, reward)
|
||||
}
|
||||
|
||||
// Providers is now a channel summary (the account pools are gone): one row per
|
||||
// channel with its bound-model count and local usage stats.
|
||||
func (s *AdminReadService) Providers(ctx context.Context) ([]map[string]any, error) {
|
||||
models, err := s.models.List(ctx)
|
||||
if s.channels == nil {
|
||||
return []map[string]any{}, nil
|
||||
}
|
||||
channels, err := s.channels.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
tokens, err := s.tokens.List(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
modelCounts := map[string]int{}
|
||||
for _, item := range models {
|
||||
modelCounts[item.Provider]++
|
||||
}
|
||||
type aggregate struct {
|
||||
active int
|
||||
disabled int
|
||||
quota int
|
||||
}
|
||||
tokenCounts := map[string]*aggregate{}
|
||||
for _, item := range tokens {
|
||||
if _, ok := tokenCounts[item.Pool]; !ok {
|
||||
tokenCounts[item.Pool] = &aggregate{}
|
||||
}
|
||||
switch item.Status {
|
||||
case "active":
|
||||
tokenCounts[item.Pool].active++
|
||||
case "quota":
|
||||
tokenCounts[item.Pool].quota++
|
||||
default:
|
||||
tokenCounts[item.Pool].disabled++
|
||||
}
|
||||
}
|
||||
providers := []struct {
|
||||
Name string
|
||||
Pool string
|
||||
Type string
|
||||
}{
|
||||
{Name: "chatgpt", Pool: "chatgpt", Type: "openai"},
|
||||
{Name: "adobe", Pool: "adobe", Type: "adobe"},
|
||||
{Name: "runway", Pool: "runway", Type: "runway"},
|
||||
{Name: "leonardo", Pool: "leonardo", Type: "leonardo"},
|
||||
{Name: "krea", Pool: "krea", Type: "krea"},
|
||||
{Name: "imagine", Pool: "imagine", Type: "imagine"},
|
||||
{Name: "grok", Pool: "grok", Type: "grok"},
|
||||
}
|
||||
out := make([]map[string]any, 0, len(providers))
|
||||
for _, item := range providers {
|
||||
count := tokenCounts[item.Pool]
|
||||
if count == nil {
|
||||
count = &aggregate{}
|
||||
}
|
||||
out := make([]map[string]any, 0, len(channels))
|
||||
for _, ch := range channels {
|
||||
modelIDs, _ := s.channels.ModelIDsByChannel(ctx, ch.ID)
|
||||
out = append(out, map[string]any{
|
||||
"name": item.Name,
|
||||
"token_pool": item.Pool,
|
||||
"type": item.Type,
|
||||
"model_count": modelCounts[item.Name],
|
||||
"tokens_total": count.active + count.disabled + count.quota,
|
||||
"tokens_active": count.active,
|
||||
"tokens_disabled": count.disabled,
|
||||
"tokens_quota": count.quota,
|
||||
"id": ch.ID,
|
||||
"name": ch.Name,
|
||||
"base_url": ch.BaseURL,
|
||||
"enabled": ch.Enabled,
|
||||
"concurrency": ch.Concurrency,
|
||||
"weight": ch.Weight,
|
||||
"model_count": len(modelIDs),
|
||||
"success_total": ch.SuccessTotal,
|
||||
"used_credits": ch.UsedCredits,
|
||||
"type": "vivid",
|
||||
})
|
||||
}
|
||||
return out, nil
|
||||
|
||||
Reference in New Issue
Block a user