improve(ui):新增模型广场页面
This commit is contained in:
1 parent
cb7854c529
commit
0ec970a182
28 files changed
+3301
-16
No files matched your search
@@ -57,6 +57,8 @@ type ITenantV1 interface {
|
||||
HelpPublicArticleGet(ctx context.Context, req *v1.HelpPublicArticleGetReq) (res *v1.HelpPublicArticleGetRes, err error)
|
||||
HelpPublicSearch(ctx context.Context, req *v1.HelpPublicSearchReq) (res *v1.HelpPublicSearchRes, err error)
|
||||
TenantLevelBenefits(ctx context.Context, req *v1.TenantLevelBenefitsReq) (res *v1.TenantLevelBenefitsRes, err error)
|
||||
MarketplaceList(ctx context.Context, req *v1.MarketplaceListReq) (res *v1.MarketplaceListRes, err error)
|
||||
MarketplaceDetail(ctx context.Context, req *v1.MarketplaceDetailReq) (res *v1.MarketplaceDetailRes, err error)
|
||||
TenantMemberList(ctx context.Context, req *v1.TenantMemberListReq) (res *v1.TenantMemberListRes, err error)
|
||||
TenantMemberInvite(ctx context.Context, req *v1.TenantMemberInviteReq) (res *v1.TenantMemberInviteRes, err error)
|
||||
TenantInvitationList(ctx context.Context, req *v1.TenantInvitationListReq) (res *v1.TenantInvitationListRes, err error)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
type MarketplaceListReq struct {
|
||||
g.Meta `path:"/marketplace/models" method:"get" tags:"公开-模型广场" summary:"获取模型广场列表" group:"public" middleware:"-"`
|
||||
Keyword string `json:"keyword" dc:"搜索关键词"`
|
||||
Category string `json:"category" dc:"模型类别筛选(chat/embedding/image/audio等)"`
|
||||
Page int `json:"page" d:"1" dc:"页码"`
|
||||
PageSize int `json:"page_size" d:"20" v:"between:1,100" dc:"每页数量"`
|
||||
}
|
||||
|
||||
type MarketplaceListRes struct {
|
||||
List []MarketplaceModelItem `json:"list"`
|
||||
Total int `json:"total"`
|
||||
Page int `json:"page"`
|
||||
PageSize int `json:"page_size"`
|
||||
}
|
||||
|
||||
type MarketplaceModelItem struct {
|
||||
ModelId string `json:"model_id"` // 模型标识
|
||||
ModelName string `json:"model_name"` // 模型显示名称
|
||||
Category string `json:"category"` // 分类(chat/embedding/image等)
|
||||
Description string `json:"description"` // 描述
|
||||
MaxContextTokens int `json:"max_context_tokens"` // 最大上下文 tokens
|
||||
MaxOutputTokens int `json:"max_output_tokens"` // 最大输出 tokens
|
||||
InputPrice float64 `json:"input_price"` // 输入价格(每百万 token,USD)
|
||||
OutputPrice float64 `json:"output_price"` // 输出价格(每百万 token,USD)
|
||||
Tags []string `json:"tags"` // 标签
|
||||
Capabilities g.Map `json:"capabilities"` // 能力标签
|
||||
}
|
||||
|
||||
type MarketplaceDetailReq struct {
|
||||
g.Meta `path:"/marketplace/models/{model_id}" method:"get" tags:"公开-模型广场" summary:"获取模型详情" group:"public" middleware:"-"`
|
||||
ModelId string `json:"model_id" in:"path" v:"required" dc:"模型标识"`
|
||||
}
|
||||
|
||||
type MarketplaceDetailRes struct {
|
||||
MarketplaceModelItem
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package tenant
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/service"
|
||||
)
|
||||
|
||||
func (c *ControllerV1) MarketplaceList(ctx context.Context, req *v1.MarketplaceListReq) (res *v1.MarketplaceListRes, err error) {
|
||||
return service.Tenant().GetModelList(ctx, req)
|
||||
}
|
||||
func (c *ControllerV1) MarketplaceDetail(ctx context.Context, req *v1.MarketplaceDetailReq) (res *v1.MarketplaceDetailRes, err error) {
|
||||
return service.Tenant().GetModelDetail(ctx, req)
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/qianfree/team-api/internal/dao/internal"
|
||||
)
|
||||
|
||||
// bilDailyUsageSummaryDao is the data access object for the table bil_daily_usage_summary.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type bilDailyUsageSummaryDao struct {
|
||||
*internal.BilDailyUsageSummaryDao
|
||||
}
|
||||
|
||||
var (
|
||||
// BilDailyUsageSummary is a globally accessible object for table bil_daily_usage_summary operations.
|
||||
BilDailyUsageSummary = bilDailyUsageSummaryDao{internal.NewBilDailyUsageSummaryDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -0,0 +1,22 @@
|
||||
// =================================================================================
|
||||
// This file is auto-generated by the GoFrame CLI tool. You may modify it as needed.
|
||||
// =================================================================================
|
||||
|
||||
package dao
|
||||
|
||||
import (
|
||||
"github.com/qianfree/team-api/internal/dao/internal"
|
||||
)
|
||||
|
||||
// bilMonthlyUsageSummaryDao is the data access object for the table bil_monthly_usage_summary.
|
||||
// You can define custom methods on it to extend its functionality as needed.
|
||||
type bilMonthlyUsageSummaryDao struct {
|
||||
*internal.BilMonthlyUsageSummaryDao
|
||||
}
|
||||
|
||||
var (
|
||||
// BilMonthlyUsageSummary is a globally accessible object for table bil_monthly_usage_summary operations.
|
||||
BilMonthlyUsageSummary = bilMonthlyUsageSummaryDao{internal.NewBilMonthlyUsageSummaryDao()}
|
||||
)
|
||||
|
||||
// Add your custom methods and functionality below.
|
||||
@@ -51,7 +51,6 @@ type AudRequestLogsColumns struct {
|
||||
TaskResult string // 异步任务完成时上游返回的原始响应体
|
||||
TaskUpstreamHeaders string // 异步任务完成时上游返回的响应头(仅审计级别为 full 时记录)
|
||||
TaskCompletedAt string // 异步任务达到终态的时间
|
||||
AttachmentIds string // 提取的媒体附件 fil_files ID 数组(JSONB: [1,2,3])
|
||||
}
|
||||
|
||||
// audRequestLogsColumns holds the columns for the table aud_request_logs.
|
||||
@@ -86,7 +85,6 @@ var audRequestLogsColumns = AudRequestLogsColumns{
|
||||
TaskResult: "task_result",
|
||||
TaskUpstreamHeaders: "task_upstream_headers",
|
||||
TaskCompletedAt: "task_completed_at",
|
||||
AttachmentIds: "attachment_ids",
|
||||
}
|
||||
|
||||
// NewAudRequestLogsDao creates and returns a new DAO object for table data access.
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// BilDailyUsageSummaryDao is the data access object for the table bil_daily_usage_summary.
|
||||
type BilDailyUsageSummaryDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of the current DAO.
|
||||
columns BilDailyUsageSummaryColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// BilDailyUsageSummaryColumns defines and stores column names for the table bil_daily_usage_summary.
|
||||
type BilDailyUsageSummaryColumns struct {
|
||||
Id string //
|
||||
TenantId string //
|
||||
Date string //
|
||||
TotalRequests string //
|
||||
TotalTokens string //
|
||||
TotalCost string //
|
||||
CreatedAt string //
|
||||
UpdatedAt string //
|
||||
}
|
||||
|
||||
// bilDailyUsageSummaryColumns holds the columns for the table bil_daily_usage_summary.
|
||||
var bilDailyUsageSummaryColumns = BilDailyUsageSummaryColumns{
|
||||
Id: "id",
|
||||
TenantId: "tenant_id",
|
||||
Date: "date",
|
||||
TotalRequests: "total_requests",
|
||||
TotalTokens: "total_tokens",
|
||||
TotalCost: "total_cost",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewBilDailyUsageSummaryDao creates and returns a new DAO object for table data access.
|
||||
func NewBilDailyUsageSummaryDao(handlers ...gdb.ModelHandler) *BilDailyUsageSummaryDao {
|
||||
return &BilDailyUsageSummaryDao{
|
||||
group: "default",
|
||||
table: "bil_daily_usage_summary",
|
||||
columns: bilDailyUsageSummaryColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *BilDailyUsageSummaryDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *BilDailyUsageSummaryDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *BilDailyUsageSummaryDao) Columns() BilDailyUsageSummaryColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *BilDailyUsageSummaryDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
|
||||
func (dao *BilDailyUsageSummaryDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
model := dao.DB().Model(dao.table)
|
||||
for _, handler := range dao.handlers {
|
||||
model = handler(model)
|
||||
}
|
||||
return model.Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rolls back the transaction and returns the error if function f returns a non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note: Do not commit or roll back the transaction in function f,
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *BilDailyUsageSummaryDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -0,0 +1,93 @@
|
||||
// ==========================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// ==========================================================================
|
||||
|
||||
package internal
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
)
|
||||
|
||||
// BilMonthlyUsageSummaryDao is the data access object for the table bil_monthly_usage_summary.
|
||||
type BilMonthlyUsageSummaryDao struct {
|
||||
table string // table is the underlying table name of the DAO.
|
||||
group string // group is the database configuration group name of the current DAO.
|
||||
columns BilMonthlyUsageSummaryColumns // columns contains all the column names of Table for convenient usage.
|
||||
handlers []gdb.ModelHandler // handlers for customized model modification.
|
||||
}
|
||||
|
||||
// BilMonthlyUsageSummaryColumns defines and stores column names for the table bil_monthly_usage_summary.
|
||||
type BilMonthlyUsageSummaryColumns struct {
|
||||
Id string //
|
||||
TenantId string //
|
||||
Month string //
|
||||
TotalRequests string //
|
||||
TotalTokens string //
|
||||
TotalCost string //
|
||||
CreatedAt string //
|
||||
UpdatedAt string //
|
||||
}
|
||||
|
||||
// bilMonthlyUsageSummaryColumns holds the columns for the table bil_monthly_usage_summary.
|
||||
var bilMonthlyUsageSummaryColumns = BilMonthlyUsageSummaryColumns{
|
||||
Id: "id",
|
||||
TenantId: "tenant_id",
|
||||
Month: "month",
|
||||
TotalRequests: "total_requests",
|
||||
TotalTokens: "total_tokens",
|
||||
TotalCost: "total_cost",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewBilMonthlyUsageSummaryDao creates and returns a new DAO object for table data access.
|
||||
func NewBilMonthlyUsageSummaryDao(handlers ...gdb.ModelHandler) *BilMonthlyUsageSummaryDao {
|
||||
return &BilMonthlyUsageSummaryDao{
|
||||
group: "default",
|
||||
table: "bil_monthly_usage_summary",
|
||||
columns: bilMonthlyUsageSummaryColumns,
|
||||
handlers: handlers,
|
||||
}
|
||||
}
|
||||
|
||||
// DB retrieves and returns the underlying raw database management object of the current DAO.
|
||||
func (dao *BilMonthlyUsageSummaryDao) DB() gdb.DB {
|
||||
return g.DB(dao.group)
|
||||
}
|
||||
|
||||
// Table returns the table name of the current DAO.
|
||||
func (dao *BilMonthlyUsageSummaryDao) Table() string {
|
||||
return dao.table
|
||||
}
|
||||
|
||||
// Columns returns all column names of the current DAO.
|
||||
func (dao *BilMonthlyUsageSummaryDao) Columns() BilMonthlyUsageSummaryColumns {
|
||||
return dao.columns
|
||||
}
|
||||
|
||||
// Group returns the database configuration group name of the current DAO.
|
||||
func (dao *BilMonthlyUsageSummaryDao) Group() string {
|
||||
return dao.group
|
||||
}
|
||||
|
||||
// Ctx creates and returns a Model for the current DAO. It automatically sets the context for the current operation.
|
||||
func (dao *BilMonthlyUsageSummaryDao) Ctx(ctx context.Context) *gdb.Model {
|
||||
model := dao.DB().Model(dao.table)
|
||||
for _, handler := range dao.handlers {
|
||||
model = handler(model)
|
||||
}
|
||||
return model.Safe().Ctx(ctx)
|
||||
}
|
||||
|
||||
// Transaction wraps the transaction logic using function f.
|
||||
// It rolls back the transaction and returns the error if function f returns a non-nil error.
|
||||
// It commits the transaction and returns nil if function f returns nil.
|
||||
//
|
||||
// Note: Do not commit or roll back the transaction in function f,
|
||||
// as it is automatically handled by this function.
|
||||
func (dao *BilMonthlyUsageSummaryDao) Transaction(ctx context.Context, f func(ctx context.Context, tx gdb.TX) error) (err error) {
|
||||
return dao.Ctx(ctx).Transaction(ctx, f)
|
||||
}
|
||||
@@ -33,10 +33,10 @@ type BilUsageDailyColumns struct {
|
||||
OutputTokens string // 输出Token合计
|
||||
TotalCost string // 客户侧成本合计(USD,源自 bil_usage_logs.total_cost)
|
||||
AccountCost string // 上游账户成本合计(USD,源自 bil_usage_logs.account_cost,用于利润分析)
|
||||
CreatedAt string //
|
||||
UpdatedAt string //
|
||||
SumLatencyMs string // 总延迟合计(ms,源自 bil_usage_logs.latency_ms;视图按 SUM/COUNT 求均值)
|
||||
SumFirstTokenMs string // 首Token延迟合计(ms,源自 bil_usage_logs.first_token_ms;视图按 SUM/COUNT 求均值)
|
||||
CreatedAt string //
|
||||
UpdatedAt string //
|
||||
}
|
||||
|
||||
// bilUsageDailyColumns holds the columns for the table bil_usage_daily.
|
||||
@@ -53,10 +53,10 @@ var bilUsageDailyColumns = BilUsageDailyColumns{
|
||||
OutputTokens: "output_tokens",
|
||||
TotalCost: "total_cost",
|
||||
AccountCost: "account_cost",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
SumLatencyMs: "sum_latency_ms",
|
||||
SumFirstTokenMs: "sum_first_token_ms",
|
||||
CreatedAt: "created_at",
|
||||
UpdatedAt: "updated_at",
|
||||
}
|
||||
|
||||
// NewBilUsageDailyDao creates and returns a new DAO object for table data access.
|
||||
|
||||
@@ -27,7 +27,7 @@ type MdlTenantModelsColumns struct {
|
||||
Enabled string // 是否启用(禁用后该租户无法调用此模型)
|
||||
CustomInputPrice string // 租户自定义输入价格(NULL 表示使用默认定价)
|
||||
CustomOutputPrice string // 租户自定义输出价格(NULL 表示使用默认定价)
|
||||
Multiplier string // 租户价格倍率(VIP 折扣)。作为 tenant_multiplier 参与最终价格计算:最终价格 = 基础价格 × 租户倍率。倍率来源优先级:discount_ratio > multiplier > 租户等级 price_multiplier
|
||||
Multiplier string // 租户价格倍率(VIP 折扣等,最终价格 = 基础价格 × 模型倍率 × 租户倍率)
|
||||
CreatedAt string // 创建时间
|
||||
UpdatedAt string // 更新时间
|
||||
BillingMode string // 覆盖模型计费方式(NULL表示跟随模型默认)
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
package tenant
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/util/gconv"
|
||||
|
||||
v1 "github.com/qianfree/team-api/api/tenant/v1"
|
||||
"github.com/qianfree/team-api/internal/dao"
|
||||
"github.com/qianfree/team-api/internal/logic/billing"
|
||||
"github.com/qianfree/team-api/internal/model/entity"
|
||||
)
|
||||
|
||||
// GetModelList 获取模型广场列表(从默认模型分组加载)
|
||||
func (s *sTenant) GetModelList(ctx context.Context, req *v1.MarketplaceListReq) (*v1.MarketplaceListRes, error) {
|
||||
// 1. 查找默认模型分组
|
||||
var defaultGroup *entity.MdlModelGroups
|
||||
err := dao.MdlModelGroups.Ctx(ctx).
|
||||
Where("is_default", true).
|
||||
Where("status", "active").
|
||||
Scan(&defaultGroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if defaultGroup == nil {
|
||||
// 没有默认分组,返回空列表
|
||||
return &v1.MarketplaceListRes{
|
||||
List: []v1.MarketplaceModelItem{},
|
||||
Total: 0,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 2. 构建查询:通过分组关联查询模型
|
||||
m := dao.MdlModels.Ctx(ctx).
|
||||
LeftJoin("mdl_group_models", "mdl_group_models.model_id = mdl_models.id").
|
||||
Where("mdl_group_models.group_id", defaultGroup.Id).
|
||||
Where("mdl_models.status", "active")
|
||||
|
||||
// 搜索关键词
|
||||
if req.Keyword != "" {
|
||||
keyword := "%" + req.Keyword + "%"
|
||||
m = m.Where("(mdl_models.model_id LIKE ? OR mdl_models.model_name LIKE ? OR mdl_models.description LIKE ?)", keyword, keyword, keyword)
|
||||
}
|
||||
|
||||
// 分类筛选
|
||||
if req.Category != "" {
|
||||
m = m.Where("mdl_models.category", req.Category)
|
||||
}
|
||||
|
||||
// 排序:按类别、模型名称
|
||||
m = m.OrderAsc("mdl_models.category").OrderAsc("mdl_models.model_id")
|
||||
|
||||
// 3. 查询总数
|
||||
total, err := m.Count()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 4. 分页查询模型
|
||||
var models []*entity.MdlModels
|
||||
err = m.Fields("mdl_models.*").
|
||||
Page(req.Page, req.PageSize).
|
||||
Scan(&models)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 5. 批量查询价格(从 mdl_pricing 表)
|
||||
if len(models) == 0 {
|
||||
return &v1.MarketplaceListRes{
|
||||
List: []v1.MarketplaceModelItem{},
|
||||
Total: 0,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
modelIds := make([]int64, len(models))
|
||||
for i, model := range models {
|
||||
modelIds[i] = model.Id
|
||||
}
|
||||
|
||||
var pricings []*entity.MdlPricing
|
||||
err = dao.MdlPricing.Ctx(ctx).
|
||||
WhereIn("model_id", modelIds).
|
||||
Where("billing_mode", "token"). // 只取 token 计费模式的基础价格
|
||||
Scan(&pricings)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 构建价格映射(model_id -> pricing)
|
||||
priceMap := make(map[int64]*entity.MdlPricing)
|
||||
for _, pricing := range pricings {
|
||||
priceMap[pricing.ModelId] = pricing
|
||||
}
|
||||
|
||||
// 6. 转换为响应格式
|
||||
list := make([]v1.MarketplaceModelItem, 0, len(models))
|
||||
for _, model := range models {
|
||||
item := s.convertToMarketplaceItem(ctx, model, priceMap[model.Id])
|
||||
list = append(list, item)
|
||||
}
|
||||
|
||||
return &v1.MarketplaceListRes{
|
||||
List: list,
|
||||
Total: total,
|
||||
Page: req.Page,
|
||||
PageSize: req.PageSize,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// GetModelDetail 获取模型详情
|
||||
func (s *sTenant) GetModelDetail(ctx context.Context, req *v1.MarketplaceDetailReq) (*v1.MarketplaceDetailRes, error) {
|
||||
// 1. 检查模型是否在默认分组中
|
||||
var defaultGroup *entity.MdlModelGroups
|
||||
err := dao.MdlModelGroups.Ctx(ctx).
|
||||
Where("is_default", true).
|
||||
Where("status", "active").
|
||||
Scan(&defaultGroup)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if defaultGroup == nil {
|
||||
return nil, fmt.Errorf("默认模型分组不存在")
|
||||
}
|
||||
|
||||
// 2. 查询模型(需在默认分组中)
|
||||
var model *entity.MdlModels
|
||||
err = dao.MdlModels.Ctx(ctx).
|
||||
LeftJoin("mdl_group_models", "mdl_group_models.model_id = mdl_models.id").
|
||||
Where("mdl_group_models.group_id", defaultGroup.Id).
|
||||
Where("mdl_models.model_id", req.ModelId).
|
||||
Where("mdl_models.status", "active").
|
||||
Fields("mdl_models.*").
|
||||
Scan(&model)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if model == nil {
|
||||
return nil, fmt.Errorf("模型不存在或未公开")
|
||||
}
|
||||
|
||||
// 3. 查询价格
|
||||
var pricing *entity.MdlPricing
|
||||
err = dao.MdlPricing.Ctx(ctx).
|
||||
Where("model_id", model.Id).
|
||||
Where("billing_mode", "token").
|
||||
Scan(&pricing)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 4. 转换为响应格式
|
||||
item := s.convertToMarketplaceItem(ctx, model, pricing)
|
||||
|
||||
return &v1.MarketplaceDetailRes{
|
||||
MarketplaceModelItem: item,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// convertToMarketplaceItem 转换模型实体为响应格式
|
||||
func (s *sTenant) convertToMarketplaceItem(ctx context.Context, model *entity.MdlModels, pricing *entity.MdlPricing) v1.MarketplaceModelItem {
|
||||
item := v1.MarketplaceModelItem{
|
||||
ModelId: model.ModelId,
|
||||
ModelName: model.ModelName,
|
||||
Category: model.Category,
|
||||
Description: model.Description,
|
||||
MaxContextTokens: model.MaxContextTokens,
|
||||
MaxOutputTokens: model.MaxOutputTokens,
|
||||
Tags: []string{},
|
||||
Capabilities: g.Map{},
|
||||
}
|
||||
|
||||
// 标签
|
||||
if model.Tags != nil && len(model.Tags) > 0 {
|
||||
item.Tags = model.Tags
|
||||
}
|
||||
|
||||
// 能力标签(从 capabilities JSONB 解析)
|
||||
if model.Capabilities != "" {
|
||||
var capabilities map[string]interface{}
|
||||
if err := gconv.Struct(model.Capabilities, &capabilities); err == nil {
|
||||
item.Capabilities = capabilities
|
||||
}
|
||||
}
|
||||
|
||||
// 价格(数据库已存储每百万 token 的美元价格,直接返回)
|
||||
if pricing != nil {
|
||||
// input_price/output_price 数据库字段存储的就是每 1M token 价格(USD)
|
||||
// 无需转换,直接使用
|
||||
item.InputPrice = billing.InexactFloat64(pricing.InputPrice)
|
||||
item.OutputPrice = billing.InexactFloat64(pricing.OutputPrice)
|
||||
}
|
||||
|
||||
return item
|
||||
}
|
||||
@@ -42,5 +42,4 @@ type AudRequestLogs struct {
|
||||
TaskResult any // 异步任务完成时上游返回的原始响应体
|
||||
TaskUpstreamHeaders any // 异步任务完成时上游返回的响应头(仅审计级别为 full 时记录)
|
||||
TaskCompletedAt *gtime.Time // 异步任务达到终态的时间
|
||||
AttachmentIds any // 提取的媒体附件 fil_files ID 数组(JSONB: [1,2,3])
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package do
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// BilDailyUsageSummary is the golang structure of table bil_daily_usage_summary for DAO operations like Where/Data.
|
||||
type BilDailyUsageSummary struct {
|
||||
g.Meta `orm:"table:bil_daily_usage_summary, do:true"`
|
||||
Id any //
|
||||
TenantId any //
|
||||
Date *gtime.Time //
|
||||
TotalRequests any //
|
||||
TotalTokens any //
|
||||
TotalCost any //
|
||||
CreatedAt *gtime.Time //
|
||||
UpdatedAt *gtime.Time //
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package do
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/frame/g"
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
)
|
||||
|
||||
// BilMonthlyUsageSummary is the golang structure of table bil_monthly_usage_summary for DAO operations like Where/Data.
|
||||
type BilMonthlyUsageSummary struct {
|
||||
g.Meta `orm:"table:bil_monthly_usage_summary, do:true"`
|
||||
Id any //
|
||||
TenantId any //
|
||||
Month *gtime.Time //
|
||||
TotalRequests any //
|
||||
TotalTokens any //
|
||||
TotalCost any //
|
||||
CreatedAt *gtime.Time //
|
||||
UpdatedAt *gtime.Time //
|
||||
}
|
||||
@@ -24,8 +24,8 @@ type BilUsageDaily struct {
|
||||
OutputTokens any // 输出Token合计
|
||||
TotalCost any // 客户侧成本合计(USD,源自 bil_usage_logs.total_cost)
|
||||
AccountCost any // 上游账户成本合计(USD,源自 bil_usage_logs.account_cost,用于利润分析)
|
||||
CreatedAt *gtime.Time //
|
||||
UpdatedAt *gtime.Time //
|
||||
SumLatencyMs any // 总延迟合计(ms,源自 bil_usage_logs.latency_ms;视图按 SUM/COUNT 求均值)
|
||||
SumFirstTokenMs any // 首Token延迟合计(ms,源自 bil_usage_logs.first_token_ms;视图按 SUM/COUNT 求均值)
|
||||
CreatedAt *gtime.Time //
|
||||
UpdatedAt *gtime.Time //
|
||||
}
|
||||
@@ -19,7 +19,7 @@ type MdlTenantModels struct {
|
||||
Enabled any // 是否启用(禁用后该租户无法调用此模型)
|
||||
CustomInputPrice *decimal.Decimal // 租户自定义输入价格(NULL 表示使用默认定价)
|
||||
CustomOutputPrice *decimal.Decimal // 租户自定义输出价格(NULL 表示使用默认定价)
|
||||
Multiplier any // 租户价格倍率(VIP 折扣)。作为 tenant_multiplier 参与最终价格计算:最终价格 = 基础价格 × 租户倍率。倍率来源优先级:discount_ratio > multiplier > 租户等级 price_multiplier
|
||||
Multiplier any // 租户价格倍率(VIP 折扣等,最终价格 = 基础价格 × 模型倍率 × 租户倍率)
|
||||
CreatedAt *gtime.Time // 创建时间
|
||||
UpdatedAt *gtime.Time // 更新时间
|
||||
BillingMode any // 覆盖模型计费方式(NULL表示跟随模型默认)
|
||||
|
||||
@@ -40,5 +40,4 @@ type AudRequestLogs struct {
|
||||
TaskResult string `json:"task_result" orm:"task_result" description:"异步任务完成时上游返回的原始响应体"` // 异步任务完成时上游返回的原始响应体
|
||||
TaskUpstreamHeaders string `json:"task_upstream_headers" orm:"task_upstream_headers" description:"异步任务完成时上游返回的响应头(仅审计级别为 full 时记录)"` // 异步任务完成时上游返回的响应头(仅审计级别为 full 时记录)
|
||||
TaskCompletedAt *gtime.Time `json:"task_completed_at" orm:"task_completed_at" description:"异步任务达到终态的时间"` // 异步任务达到终态的时间
|
||||
AttachmentIds string `json:"attachment_ids" orm:"attachment_ids" description:"提取的媒体附件 fil_files ID 数组(JSONB: [1,2,3])"` // 提取的媒体附件 fil_files ID 数组(JSONB: [1,2,3])
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// BilDailyUsageSummary is the golang structure for table bil_daily_usage_summary.
|
||||
type BilDailyUsageSummary struct {
|
||||
Id int64 `json:"id" orm:"id" description:""` //
|
||||
TenantId int64 `json:"tenant_id" orm:"tenant_id" description:""` //
|
||||
Date *gtime.Time `json:"date" orm:"date" description:""` //
|
||||
TotalRequests int `json:"total_requests" orm:"total_requests" description:""` //
|
||||
TotalTokens int64 `json:"total_tokens" orm:"total_tokens" description:""` //
|
||||
TotalCost decimal.Decimal `json:"total_cost" orm:"total_cost" description:""` //
|
||||
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:""` //
|
||||
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:""` //
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
// =================================================================================
|
||||
// Code generated and maintained by GoFrame CLI tool. DO NOT EDIT.
|
||||
// =================================================================================
|
||||
|
||||
package entity
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/v2/os/gtime"
|
||||
"github.com/shopspring/decimal"
|
||||
)
|
||||
|
||||
// BilMonthlyUsageSummary is the golang structure for table bil_monthly_usage_summary.
|
||||
type BilMonthlyUsageSummary struct {
|
||||
Id int64 `json:"id" orm:"id" description:""` //
|
||||
TenantId int64 `json:"tenant_id" orm:"tenant_id" description:""` //
|
||||
Month *gtime.Time `json:"month" orm:"month" description:""` //
|
||||
TotalRequests int `json:"total_requests" orm:"total_requests" description:""` //
|
||||
TotalTokens int64 `json:"total_tokens" orm:"total_tokens" description:""` //
|
||||
TotalCost decimal.Decimal `json:"total_cost" orm:"total_cost" description:""` //
|
||||
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:""` //
|
||||
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:""` //
|
||||
}
|
||||
@@ -23,8 +23,8 @@ type BilUsageDaily struct {
|
||||
OutputTokens int64 `json:"output_tokens" orm:"output_tokens" description:"输出Token合计"` // 输出Token合计
|
||||
TotalCost decimal.Decimal `json:"total_cost" orm:"total_cost" description:"客户侧成本合计(USD,源自 bil_usage_logs.total_cost)"` // 客户侧成本合计(USD,源自 bil_usage_logs.total_cost)
|
||||
AccountCost decimal.Decimal `json:"account_cost" orm:"account_cost" description:"上游账户成本合计(USD,源自 bil_usage_logs.account_cost,用于利润分析)"` // 上游账户成本合计(USD,源自 bil_usage_logs.account_cost,用于利润分析)
|
||||
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:""` //
|
||||
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:""` //
|
||||
SumLatencyMs int64 `json:"sum_latency_ms" orm:"sum_latency_ms" description:"总延迟合计(ms,源自 bil_usage_logs.latency_ms;视图按 SUM/COUNT 求均值)"` // 总延迟合计(ms,源自 bil_usage_logs.latency_ms;视图按 SUM/COUNT 求均值)
|
||||
SumFirstTokenMs int64 `json:"sum_first_token_ms" orm:"sum_first_token_ms" description:"首Token延迟合计(ms,源自 bil_usage_logs.first_token_ms;视图按 SUM/COUNT 求均值)"` // 首Token延迟合计(ms,源自 bil_usage_logs.first_token_ms;视图按 SUM/COUNT 求均值)
|
||||
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:""` //
|
||||
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:""` //
|
||||
}
|
||||
@@ -17,7 +17,7 @@ type MdlTenantModels struct {
|
||||
Enabled bool `json:"enabled" orm:"enabled" description:"是否启用(禁用后该租户无法调用此模型)"` // 是否启用(禁用后该租户无法调用此模型)
|
||||
CustomInputPrice *decimal.Decimal `json:"custom_input_price" orm:"custom_input_price" description:"租户自定义输入价格(NULL 表示使用默认定价)"` // 租户自定义输入价格(NULL 表示使用默认定价)
|
||||
CustomOutputPrice *decimal.Decimal `json:"custom_output_price" orm:"custom_output_price" description:"租户自定义输出价格(NULL 表示使用默认定价)"` // 租户自定义输出价格(NULL 表示使用默认定价)
|
||||
Multiplier decimal.Decimal `json:"multiplier" orm:"multiplier" description:"租户价格倍率(VIP 折扣)。作为 tenant_multiplier 参与最终价格计算:最终价格 = 基础价格 × 租户倍率。倍率来源优先级:discount_ratio > multiplier > 租户等级 price_multiplier"` // 租户价格倍率(VIP 折扣)。作为 tenant_multiplier 参与最终价格计算:最终价格 = 基础价格 × 租户倍率。倍率来源优先级:discount_ratio > multiplier > 租户等级 price_multiplier
|
||||
Multiplier decimal.Decimal `json:"multiplier" orm:"multiplier" description:"租户价格倍率(VIP 折扣等,最终价格 = 基础价格 × 模型倍率 × 租户倍率)"` // 租户价格倍率(VIP 折扣等,最终价格 = 基础价格 × 模型倍率 × 租户倍率)
|
||||
CreatedAt *gtime.Time `json:"created_at" orm:"created_at" description:"创建时间"` // 创建时间
|
||||
UpdatedAt *gtime.Time `json:"updated_at" orm:"updated_at" description:"更新时间"` // 更新时间
|
||||
BillingMode string `json:"billing_mode" orm:"billing_mode" description:"覆盖模型计费方式(NULL表示跟随模型默认)"` // 覆盖模型计费方式(NULL表示跟随模型默认)
|
||||
|
||||
@@ -125,6 +125,10 @@ type (
|
||||
RequestClosure(ctx context.Context, req *v1.TenantRequestClosureReq) (*v1.TenantRequestClosureRes, error)
|
||||
// CancelClosure 取消关户(仅 owner)
|
||||
CancelClosure(ctx context.Context, req *v1.TenantCancelClosureReq) (*v1.TenantCancelClosureRes, error)
|
||||
// GetModelList 获取模型广场列表(从默认模型分组加载)
|
||||
GetModelList(ctx context.Context, req *v1.MarketplaceListReq) (*v1.MarketplaceListRes, error)
|
||||
// GetModelDetail 获取模型详情
|
||||
GetModelDetail(ctx context.Context, req *v1.MarketplaceDetailReq) (*v1.MarketplaceDetailRes, error)
|
||||
// ListMembers returns a paginated list of tenant members.
|
||||
ListMembers(ctx context.Context, req *v1.TenantMemberListReq) (*v1.TenantMemberListRes, error)
|
||||
// InviteMember generates an invitation link.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
import request from '@/utils/request'
|
||||
|
||||
export interface MarketplaceModel {
|
||||
model_id: string
|
||||
model_name: string
|
||||
category: string
|
||||
description: string
|
||||
max_context_tokens: number
|
||||
max_output_tokens: number
|
||||
input_price: number
|
||||
output_price: number
|
||||
tags: string[]
|
||||
capabilities: Record<string, any>
|
||||
}
|
||||
|
||||
export interface MarketplaceListParams {
|
||||
keyword?: string
|
||||
category?: string
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
export interface MarketplaceListResponse {
|
||||
list: MarketplaceModel[]
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
}
|
||||
|
||||
export const getMarketplaceModels = (params: MarketplaceListParams) => {
|
||||
return request.get<{ data: MarketplaceListResponse }>('/tenant/marketplace/models', { params }).then(res => res.data.data)
|
||||
}
|
||||
|
||||
export const getMarketplaceModelDetail = (modelId: string) => {
|
||||
return request.get<{ data: MarketplaceModel }>(`/tenant/marketplace/models/${modelId}`).then(res => res.data.data)
|
||||
}
|
||||
@@ -40,6 +40,12 @@ const tenantRoutes: RouteRecordRaw[] = [
|
||||
component: () => import('@/views/auth/JoinPage.vue'),
|
||||
meta: { requiresAuth: false, title: '加入组织' },
|
||||
},
|
||||
{
|
||||
path: '/marketplace',
|
||||
name: 'Marketplace',
|
||||
component: () => import('@/views/marketplace/MarketplacePage.vue'),
|
||||
meta: { requiresAuth: false, title: '模型广场' },
|
||||
},
|
||||
{
|
||||
path: '/tenant',
|
||||
component: () => import('@/views/Layout.vue'),
|
||||
|
||||
@@ -43,13 +43,15 @@ export interface FormatMoneyOptions {
|
||||
* 统一金额格式化。默认 USD、6 位精度(与 NUMERIC(20,10) 对齐)。
|
||||
* currency='CNY' 时用 ¥ 前缀 + 2 位精度(订单/充值层)。
|
||||
* showSign 用 +/− 前缀(交易流水)。
|
||||
* 自动去掉末尾的 0(如 $1.500000 → $1.5)。
|
||||
*/
|
||||
export function formatMoney(value: unknown, opts: FormatMoneyOptions = {}): string {
|
||||
const { currency = 'USD', precision = 6, showSign = false } = opts
|
||||
const num = Number(value ?? 0)
|
||||
const symbol = currency === 'CNY' ? '¥' : '$'
|
||||
const sign = showSign ? (num > 0 ? '+' : num < 0 ? '-' : '') : ''
|
||||
return `${sign}${symbol}${Math.abs(num).toFixed(precision)}`
|
||||
// 先 toFixed 确保精度,再用 Number 去掉末尾的 0
|
||||
return `${sign}${symbol}${Number(Math.abs(num).toFixed(precision))}`
|
||||
}
|
||||
|
||||
/** 统一日期格式化:ISO T 转空格、截断到分钟;空值显示 -- */
|
||||
|
||||
@@ -150,6 +150,8 @@ const PUBLIC_PATHS = [
|
||||
// 找回密码流程无需登录态:发送验证码 / 重置密码
|
||||
'/email/send-code',
|
||||
'/email/reset-password',
|
||||
// 模型广场(公开访问)
|
||||
'/marketplace/',
|
||||
]
|
||||
|
||||
function isPublicPath(url?: string): boolean {
|
||||
@@ -240,6 +242,13 @@ request.interceptors.response.use(
|
||||
async (error) => {
|
||||
const originalRequest = error.config as AxiosRequestConfig & { _retry?: boolean }
|
||||
|
||||
// 公开接口即使暂时返回 401,也不应触发刷新令牌或把访客踢到登录页。
|
||||
// 交给调用页面展示自己的错误状态即可。
|
||||
if (error.response?.status === 401 && isPublicPath(originalRequest?.url)) {
|
||||
showRequestError(error, originalRequest)
|
||||
return Promise.reject(error)
|
||||
}
|
||||
|
||||
if (error.response?.status !== 401 || originalRequest._retry) {
|
||||
// Extract unified error from response body
|
||||
if (error.response?.data && typeof error.response.data === 'object' && 'code' in error.response.data) {
|
||||
|
||||
@@ -18,6 +18,12 @@
|
||||
<span class="text-lg font-bold tracking-tight text-gray-900">{{ siteName }}</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-2">
|
||||
<router-link :to="{ name: 'Marketplace' }" class="btn-nav-link">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M9 3.75H6.912a2.25 2.25 0 00-2.15 1.588L2.35 13.177a2.25 2.25 0 00-.1.661V18a2.25 2.25 0 002.25 2.25h15A2.25 2.25 0 0021.75 18v-4.162c0-.224-.034-.447-.1-.661L19.24 5.338a2.25 2.25 0 00-2.15-1.588H15M2.25 13.5h3.86a2.25 2.25 0 012.012 1.244l.256.512a2.25 2.25 0 002.013 1.244h3.218a2.25 2.25 0 002.013-1.244l.256-.512a2.25 2.25 0 012.013-1.244h3.859M12 3v8.25m0 0l-3-3m3 3l3-3"/>
|
||||
</svg>
|
||||
<span>模型广场</span>
|
||||
</router-link>
|
||||
<button v-if="announcements.length" @click="openAnnouncements" class="btn-nav-announce" title="查看公告">
|
||||
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M14.857 17.082a23.848 23.848 0 005.454-1.31A8.967 8.967 0 0118 9.75V9A6 6 0 006 9v.75a8.967 8.967 0 01-2.312 6.022c1.733.64 3.56 1.085 5.455 1.31m5.714 0a24.255 24.255 0 01-5.714 0m5.714 0a3 3 0 11-5.714 0"/></svg>
|
||||
</button>
|
||||
@@ -532,6 +538,20 @@ const mockChartHeights = [45, 62, 38, 71, 55, 82, 67, 48, 73, 58, 90, 65, 52, 78
|
||||
box-shadow: 0 4px 14px rgba(76,91,142,0.1), inset 0 1px 0 rgba(255,255,255,0.9);
|
||||
}
|
||||
|
||||
/* Nav link — 模型广场链接 */
|
||||
.btn-nav-link {
|
||||
display: flex; align-items: center; gap: 6px;
|
||||
padding: 8px 16px; border-radius: 10px; border: 1px solid rgba(255,255,255,0.72);
|
||||
font-size: 14px; font-weight: 500; cursor: pointer; transition: all 0.2s ease;
|
||||
background: rgba(255,255,255,0.55); color: #64748b;
|
||||
box-shadow: inset 0 1px 0 rgba(255,255,255,0.7);
|
||||
text-decoration: none;
|
||||
}
|
||||
.btn-nav-link:hover {
|
||||
background: rgba(255,255,255,0.9); color: #0d9488;
|
||||
box-shadow: 0 4px 14px rgba(76,91,142,0.1), inset 0 1px 0 rgba(255,255,255,0.9);
|
||||
}
|
||||
|
||||
/* ================================================
|
||||
合并区 — 产品标题 + 场景展示
|
||||
================================================ */
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user