feat(channels): add "Available Channels" aggregate view
Add a read-only aggregate view per channel: its linked groups and a deterministic wildcard-free supported-model list with pricing details. Backend - service.Channel.SupportedModels(): combine ModelMapping keys with same-platform ModelPricing.Models; trailing "*" keys expand via pricing prefix match; platforms without a mapping produce no entries (intentional "no mapping = not shown" rule). - Extract splitWildcardSuffix() shared with toModelEntry. - Build a per-call pricing lookup map (platform+lowerName -> *pricing) to avoid O(N*M) scans in SupportedModels. - ChannelService.ListAvailable() aggregates channels + active groups; filters out group IDs no longer active. - Admin route GET /api/v1/admin/channels/available returns the full DTO (id, status, billing_model_source, restrict_models, groups, supported_models). - User route GET /api/v1/channels/available applies three filters: Status==active, visible-group intersection, and platform filter on supported_models (prevents cross-platform leak when a channel links to both a user-accessible group and an inaccessible one on another platform). Response is a plain array (matches the /groups/available sibling shape). Field whitelist omits billing_model_source, restrict_models, ids, status, sort_order. Frontend - New /admin/available-channels and /available-channels views backed by a shared AvailableChannelsTable component (admin adds status + billing-source columns via slots). - PricingRow extracted to its own SFC; SupportedModelChip references shared billing-mode constants in constants/channel.ts. - Sidebar: new entry above "渠道管理" for admin; matching entry in user nav. - i18n: zh + en coverage for both namespaces. Tests - SupportedModels: wildcard-only pricing skipped, prefix-matches- nothing, cross-platform bleed, case-insensitive dedup, empty platform mapping. - ListAvailable: nil groupRepo, inactive-group-ID dropped, stable case-insensitive name sort. - User handler: 401 on unauthenticated, visible-group intersection, platform filter on supported_models, JSON whitelist. - Admin handler: full DTO including default BillingModelSource fallback. Refs: issue #1729
This commit is contained in:
@@ -344,6 +344,7 @@ export default {
|
||||
users: '用户管理',
|
||||
groups: '分组管理',
|
||||
channels: '渠道管理',
|
||||
availableChannels: '可用渠道',
|
||||
subscriptions: '订阅管理',
|
||||
accounts: '账号管理',
|
||||
proxies: 'IP管理',
|
||||
@@ -933,6 +934,38 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// Available Channels (user-facing)
|
||||
availableChannels: {
|
||||
title: '可用渠道',
|
||||
description: '查看您可访问的渠道与其支持的模型、定价',
|
||||
searchPlaceholder: '搜索渠道或模型...',
|
||||
empty: '暂无可用渠道',
|
||||
noModels: '未配置模型',
|
||||
noPricing: '未配置定价',
|
||||
columns: {
|
||||
name: '渠道名',
|
||||
groups: '我可访问的分组',
|
||||
supportedModels: '支持模型'
|
||||
},
|
||||
pricing: {
|
||||
billingMode: '计费模式',
|
||||
billingModeToken: '按 Token',
|
||||
billingModePerRequest: '按次',
|
||||
billingModeImage: '按图片',
|
||||
inputPrice: '输入',
|
||||
outputPrice: '输出',
|
||||
cacheWritePrice: '缓存写入',
|
||||
cacheReadPrice: '缓存读取',
|
||||
imageOutputPrice: '图片输出',
|
||||
perRequestPrice: '每次请求',
|
||||
intervals: '阶梯定价',
|
||||
tierLabel: '层级',
|
||||
tokenRange: 'Token 区间',
|
||||
unitPerMillion: '/ 1M token',
|
||||
unitPerRequest: '/ 次'
|
||||
}
|
||||
},
|
||||
|
||||
// Redeem
|
||||
redeem: {
|
||||
title: '兑换码',
|
||||
@@ -2059,6 +2092,48 @@ export default {
|
||||
}
|
||||
},
|
||||
|
||||
// Available Channels (aggregated read-only view)
|
||||
availableChannels: {
|
||||
title: '可用渠道',
|
||||
description: '按渠道聚合查看关联分组与支持模型(已展开通配符)',
|
||||
searchPlaceholder: '搜索渠道或模型...',
|
||||
columns: {
|
||||
name: '渠道名',
|
||||
status: '状态',
|
||||
billingSource: '计费模型来源',
|
||||
groups: '关联分组',
|
||||
supportedModels: '支持模型'
|
||||
},
|
||||
empty: '暂无数据',
|
||||
noGroups: '未关联分组',
|
||||
noModels: '未配置模型映射',
|
||||
noPricing: '未配置定价',
|
||||
statusActive: '启用',
|
||||
statusDisabled: '停用',
|
||||
billingSource: {
|
||||
requested: '请求模型',
|
||||
upstream: '上游模型',
|
||||
channel_mapped: '映射后模型'
|
||||
},
|
||||
pricing: {
|
||||
billingMode: '计费模式',
|
||||
billingModeToken: '按 Token',
|
||||
billingModePerRequest: '按次',
|
||||
billingModeImage: '按图片',
|
||||
inputPrice: '输入',
|
||||
outputPrice: '输出',
|
||||
cacheWritePrice: '缓存写入',
|
||||
cacheReadPrice: '缓存读取',
|
||||
imageOutputPrice: '图片输出',
|
||||
perRequestPrice: '每次请求',
|
||||
intervals: '阶梯定价',
|
||||
tierLabel: '层级',
|
||||
tokenRange: 'Token 区间',
|
||||
unitPerMillion: '/ 1M token',
|
||||
unitPerRequest: '/ 次'
|
||||
}
|
||||
},
|
||||
|
||||
// Channel Management
|
||||
channels: {
|
||||
title: '渠道管理',
|
||||
|
||||
Reference in New Issue
Block a user