1. 22 Apr, 2026 1 commit
    • erio's avatar
      fix(available-channels): description as own column, fixed table layout · 25a50355
      erio authored
      - 描述独立成列:渠道名与描述各占一列,均用 rowspan 纵向合并
      - 渠道名单元格 text-center + align-middle,合并后视觉居中
      - table-fixed:给 name/description/platform 显式宽度,groups 和
        supported_models 在剩余空间均分。支持模型列此前在 table-auto 下
        不会换行导致横向溢出遮挡(反馈截图),加 table-fixed 后天然 flex-wrap
      - i18n 增加 availableChannels.columns.description(zh/en)
      25a50355
  2. 21 Apr, 2026 2 commits
    • erio's avatar
      feat(channels): themed model popover + group-badge with rate, subscription & exclusivity · ff4ef1b5
      erio authored
      Pricing popover:
      - Teleport to body + fixed-position re-measuring on hover/scroll/resize so it
        escapes the card's overflow-hidden clip that was chopping off the lower
        half of the panel.
      - Header + border adopt the platform theme via platformBorderClass /
        platformBadgeLightClass so each model card reads at a glance.
      
      Accessible groups:
      - Backend AvailableGroupRef / user DTO now expose subscription_type,
        rate_multiplier and is_exclusive. User-specific rates continue to come
        from /groups/rates (same pattern as the API keys page).
      - Table renders groups through the shared GroupBadge, which already deepens
        subscription-type colors and shows default vs custom rate as
        <s>default</s> <b>custom</b>.
      - Exclusive groups are labelled with a purple shield row, public groups
        with a grey globe row so admins and users can tell at a glance which
        groups they got via explicit grant vs. public access.
      
      i18n keys for exclusive / public / their tooltips added to zh + en.
      ff4ef1b5
    • erio's avatar
      feat(channels): aggregate by channel with platform sections + rowspan table · 3cdd5754
      erio authored
      Switch the user-facing 'Available Channels' view from "one row per
      platform" to "one channel row-group with N platform sections".
      
      Backend: userAvailableChannel now holds Platforms []section instead
      of being exploded. buildPlatformSections replaces
      explodeChannelByPlatform with the same per-platform grouping logic.
      
      Frontend: drop the DataTable wrapper for this view and write a
      four-column grid table (渠道名 / 平台 / 分组 / 支持模型) where the
      channel name only renders on the first platform row of each channel —
      visual rowspan without hacking DataTable.
      
      - api/channels.ts: UserChannelPlatformSection + platforms[]
      - AvailableChannelsTable: rewritten as native grid (header + per-
        channel section with hover row highlight)
      - AvailableChannelsView: search now filters platforms sub-array;
        channel-name / description hits still keep the whole channel
      - i18n: add availableChannels.columns.platform (zh/en)
      3cdd5754
  3. 20 Apr, 2026 1 commit
    • erio's avatar
      feat(channels): add "Available Channels" aggregate view · 654cfb64
      erio authored
      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
      654cfb64