From d81ea9081e406ea09435d4b09ac1aae740dbb783 Mon Sep 17 00:00:00 2001
From: lanlingxiawu <374397721@qq.com>
Date: Wed, 27 May 2026 11:22:36 +0800
Subject: [PATCH 1/6] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E9=83=A8=E5=88=86?=
=?UTF-8?q?=E6=9C=80=E6=96=B0=E4=BB=A3=E7=A0=81=E5=92=8C=E5=8E=9F=E6=9C=AC?=
=?UTF-8?q?=E4=BF=AE=E6=94=B9=E7=9A=84=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
同步部分最新代码和原本修改的代码
---
backend/internal/domain/constants.go | 1 +
.../internal/handler/admin/group_handler.go | 14 +
backend/internal/handler/gateway_handler.go | 11 +
.../gateway_handler_chat_completions.go | 9 +
.../handler/gateway_handler_responses.go | 9 +
.../handler/openai_gateway_handler.go | 26 ++
.../handler/openai_gateway_handler_test.go | 1 +
backend/internal/handler/openai_images.go | 20 +-
backend/internal/repository/api_key_repo.go | 24 +-
.../api_key_repo_last_used_unit_test.go | 3 +-
backend/internal/repository/group_repo.go | 128 +++++++-
.../internal/repository/usage_billing_repo.go | 2 +-
backend/internal/service/account.go | 30 +-
backend/internal/service/admin_service.go | 16 +
backend/internal/service/domain_constants.go | 1 +
backend/internal/service/group.go | 64 ++++
.../openai_gateway_chat_completions.go | 5 +-
.../openai_gateway_record_usage_test.go | 1 +
.../openai_ws_protocol_forward_test.go | 1 +
.../138_add_group_model_mapping.sql | 3 +
.../components/account/CreateAccountModal.vue | 297 +++++++++++++++++-
.../components/account/EditAccountModal.vue | 185 ++++++++++-
.../admin/account/AccountActionMenu.vue | 2 +-
.../admin/account/AccountTableFilters.vue | 2 +-
.../admin/account/ImportDataModal.vue | 21 +-
.../components/common/PlatformTypeBadge.vue | 3 +
frontend/src/i18n/locales/en.ts | 14 +
frontend/src/i18n/locales/zh.ts | 14 +
frontend/src/types/index.ts | 5 +-
...23\345\214\205\351\203\250\347\275\262.md" | 47 +++
30 files changed, 925 insertions(+), 34 deletions(-)
create mode 100644 backend/migrations/138_add_group_model_mapping.sql
create mode 100644 "\346\211\223\345\214\205\351\203\250\347\275\262.md"
diff --git a/backend/internal/domain/constants.go b/backend/internal/domain/constants.go
index 27c543dd5ec..252f4c466ef 100644
--- a/backend/internal/domain/constants.go
+++ b/backend/internal/domain/constants.go
@@ -31,6 +31,7 @@ const (
AccountTypeAPIKey = "apikey" // API Key类型账号
AccountTypeUpstream = "upstream" // 上游透传类型账号(通过 Base URL + API Key 连接上游)
AccountTypeBedrock = "bedrock" // AWS Bedrock 类型账号(通过 SigV4 签名或 API Key 连接 Bedrock,由 credentials.auth_mode 区分)
+ AccountTypeAnthropicAWS = "anthropic_aws" // Claude Platform on AWS 类型账号(Anthropic 在 AWS 上托管的外部 API,aws-external-anthropic.{region}.api.aws)
AccountTypeServiceAccount = "service_account" // Google Service Account 类型账号(用于 Vertex AI)
)
diff --git a/backend/internal/handler/admin/group_handler.go b/backend/internal/handler/admin/group_handler.go
index 3667bbcd1d7..7c9aa2d3239 100644
--- a/backend/internal/handler/admin/group_handler.go
+++ b/backend/internal/handler/admin/group_handler.go
@@ -115,6 +115,10 @@ type CreateGroupRequest struct {
MessagesDispatchModelConfig service.OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config"`
// 分组 RPM 上限(0 = 不限制)
RPMLimit int `json:"rpm_limit"`
+ // xiugai 修改自动映射功能
+ // 分组级模型映射(支持通配符 * 和正则 ~ 前缀),如 {"gpt-4*": "gpt-4o", "~^claude-.*": "claude-3-5-sonnet-20241022"}
+ ModelMapping map[string]string `json:"model_mapping"`
+ // xiugai end
// 从指定分组复制账号(创建后自动绑定)
CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"`
}
@@ -155,6 +159,10 @@ type UpdateGroupRequest struct {
MessagesDispatchModelConfig *service.OpenAIMessagesDispatchModelConfig `json:"messages_dispatch_model_config"`
// 分组 RPM 上限(0 = 不限制);nil 表示未提供不改动
RPMLimit *int `json:"rpm_limit"`
+ // xiugai 修改自动映射功能
+ // 分组级模型映射(支持通配符 * 和正则 ~ 前缀),nil 表示不改动,空对象 {} 表示清除
+ ModelMapping map[string]string `json:"model_mapping"`
+ // xiugai end
// 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号)
CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"`
}
@@ -276,6 +284,9 @@ func (h *GroupHandler) Create(c *gin.Context) {
DefaultMappedModel: req.DefaultMappedModel,
MessagesDispatchModelConfig: req.MessagesDispatchModelConfig,
RPMLimit: req.RPMLimit,
+ // xiugai 修改自动映射功能
+ ModelMapping: req.ModelMapping,
+ // xiugai end
CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs,
})
if err != nil {
@@ -331,6 +342,9 @@ func (h *GroupHandler) Update(c *gin.Context) {
DefaultMappedModel: req.DefaultMappedModel,
MessagesDispatchModelConfig: req.MessagesDispatchModelConfig,
RPMLimit: req.RPMLimit,
+ // xiugai 修改自动映射功能
+ ModelMapping: req.ModelMapping,
+ // xiugai end
CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs,
})
if err != nil {
diff --git a/backend/internal/handler/gateway_handler.go b/backend/internal/handler/gateway_handler.go
index 4420a87c3bd..570f283cb6a 100644
--- a/backend/internal/handler/gateway_handler.go
+++ b/backend/internal/handler/gateway_handler.go
@@ -160,6 +160,16 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
}
reqModel := parsedReq.Model
reqStream := parsedReq.Stream
+
+ // xiugai 修改自动映射功能
+ // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
+ if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
+ body = service.ReplaceModelInBody(body, mapped)
+ parsedReq.Model = mapped
+ reqModel = mapped
+ }
+ // xiugai end
+
reqLog = reqLog.With(zap.String("model", reqModel), zap.Bool("stream", reqStream))
// 解析渠道级模型映射
@@ -744,6 +754,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
if channelMapping.Mapped {
parsedReq.Model = channelMapping.MappedModel
parsedReq.Body = h.gatewayService.ReplaceModelInBody(parsedReq.Body, channelMapping.MappedModel)
+ body = h.gatewayService.ReplaceModelInBody(body, channelMapping.MappedModel)
}
// Bedrock CC 兼容:渠道模型映射后,清理 Anthropic API 专有字段、注入 Bedrock 必需字段
parsedReq.Body = h.gatewayService.ApplyBedrockCCCompat(c.Request.Context(), parsedReq.Body, parsedReq.Model, account, apiKey.GroupID)
diff --git a/backend/internal/handler/gateway_handler_chat_completions.go b/backend/internal/handler/gateway_handler_chat_completions.go
index 9a091fcd4b2..b6eca3e1d3b 100644
--- a/backend/internal/handler/gateway_handler_chat_completions.go
+++ b/backend/internal/handler/gateway_handler_chat_completions.go
@@ -75,6 +75,15 @@ func (h *GatewayHandler) ChatCompletions(c *gin.Context) {
return
}
reqModel := modelResult.String()
+
+ // xiugai 修改自动映射功能
+ // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
+ if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
+ body = service.ReplaceModelInBody(body, mapped)
+ reqModel = mapped
+ }
+ // xiugai end
+
reqStream := gjson.GetBytes(body, "stream").Bool()
reqLog = reqLog.With(zap.String("model", reqModel), zap.Bool("stream", reqStream))
diff --git a/backend/internal/handler/gateway_handler_responses.go b/backend/internal/handler/gateway_handler_responses.go
index e1a5b723605..bd6a672701b 100644
--- a/backend/internal/handler/gateway_handler_responses.go
+++ b/backend/internal/handler/gateway_handler_responses.go
@@ -75,6 +75,15 @@ func (h *GatewayHandler) Responses(c *gin.Context) {
return
}
reqModel := modelResult.String()
+
+ // xiugai 修改自动映射功能
+ // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
+ if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
+ body = service.ReplaceModelInBody(body, mapped)
+ reqModel = mapped
+ }
+ // xiugai end
+
reqStream := gjson.GetBytes(body, "stream").Bool()
reqLog = reqLog.With(zap.String("model", reqModel), zap.Bool("stream", reqStream))
diff --git a/backend/internal/handler/openai_gateway_handler.go b/backend/internal/handler/openai_gateway_handler.go
index 9c5560f5c00..13818f06922 100644
--- a/backend/internal/handler/openai_gateway_handler.go
+++ b/backend/internal/handler/openai_gateway_handler.go
@@ -189,6 +189,14 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
return
}
+ // xiugai 修改自动映射功能
+ // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
+ if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
+ body = service.ReplaceModelInBody(body, mapped)
+ reqModel = mapped
+ }
+ // xiugai end
+
setOpsRequestContext(c, reqModel, reqStream)
setOpsEndpointContext(c, "", int16(service.RequestTypeFromLegacy(reqStream, false)))
@@ -613,6 +621,15 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
return
}
reqModel := modelResult.String()
+
+ // xiugai 修改自动映射功能
+ // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
+ if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
+ body = service.ReplaceModelInBody(body, mapped)
+ reqModel = mapped
+ }
+ // xiugai end
+
routingModel := service.NormalizeOpenAICompatRequestedModel(reqModel)
preferredMappedModel := resolveOpenAIMessagesDispatchMappedModel(apiKey, reqModel)
reqStream := gjson.GetBytes(body, "stream").Bool()
@@ -1178,6 +1195,15 @@ func (h *OpenAIGatewayHandler) ResponsesWebSocket(c *gin.Context) {
closeOpenAIClientWS(wsConn, coderws.StatusPolicyViolation, "model is required in first response.create payload")
return
}
+
+ // xiugai 修改自动映射功能
+ // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
+ if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
+ firstMessage = service.ReplaceModelInBody(firstMessage, mapped)
+ reqModel = mapped
+ }
+ // xiugai end
+
previousResponseID := strings.TrimSpace(gjson.GetBytes(firstMessage, "previous_response_id").String())
previousResponseIDKind := service.ClassifyOpenAIPreviousResponseIDKind(previousResponseID)
if previousResponseID != "" && previousResponseIDKind == service.OpenAIPreviousResponseIDKindMessageID {
diff --git a/backend/internal/handler/openai_gateway_handler_test.go b/backend/internal/handler/openai_gateway_handler_test.go
index 6bddbce9454..da9548eea4d 100644
--- a/backend/internal/handler/openai_gateway_handler_test.go
+++ b/backend/internal/handler/openai_gateway_handler_test.go
@@ -1190,6 +1190,7 @@ func runOpenAIResponsesWebSocketUsageLogCase(t *testing.T, tc openAIResponsesWSU
channelSvc,
nil,
nil,
+ nil,
)
cache := &concurrencyCacheMock{
diff --git a/backend/internal/handler/openai_images.go b/backend/internal/handler/openai_images.go
index e6c37272882..410436da1cf 100644
--- a/backend/internal/handler/openai_images.go
+++ b/backend/internal/handler/openai_images.go
@@ -62,18 +62,20 @@ func (h *OpenAIGatewayHandler) Images(c *gin.Context) {
return
}
- if isMultipartImagesContentType(c.GetHeader("Content-Type")) {
- setOpsRequestContext(c, "", false)
- } else {
- setOpsRequestContext(c, "", false)
- }
-
+ setOpsRequestContext(c, "", false)
parsed, err := h.gatewayService.ParseOpenAIImagesRequest(c, body)
if err != nil {
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error", err.Error())
return
}
+ // xiugai 修改自动映射功能
+ // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
+ if mapped, ok := apiKey.Group.ResolveGroupMappedModel(parsed.Model); ok {
+ parsed.Model = mapped
+ }
+ // xiugai end
+
reqLog = reqLog.With(
zap.String("model", parsed.Model),
zap.Bool("stream", parsed.Stream),
@@ -97,11 +99,7 @@ func (h *OpenAIGatewayHandler) Images(c *gin.Context) {
defer imageReleaseFunc()
}
- if parsed.Multipart {
- setOpsRequestContext(c, parsed.Model, parsed.Stream)
- } else {
- setOpsRequestContext(c, parsed.Model, parsed.Stream)
- }
+ setOpsRequestContext(c, parsed.Model, parsed.Stream)
setOpsEndpointContext(c, "", int16(service.RequestTypeFromLegacy(parsed.Stream, false)))
channelMapping, _ := h.gatewayService.ResolveChannelMappingAndRestrict(c.Request.Context(), apiKey.GroupID, parsed.Model)
diff --git a/backend/internal/repository/api_key_repo.go b/backend/internal/repository/api_key_repo.go
index 43b13937b17..00e250e2a34 100644
--- a/backend/internal/repository/api_key_repo.go
+++ b/backend/internal/repository/api_key_repo.go
@@ -81,7 +81,13 @@ func (r *apiKeyRepository) GetByID(ctx context.Context, id int64) (*service.APIK
}
return nil, err
}
- return apiKeyEntityToService(m), nil
+ out := apiKeyEntityToService(m)
+ // xiugai 修改自动映射功能
+ if out.Group != nil {
+ out.Group.ModelMapping = loadGroupModelMappingSQL(ctx, r.sql, out.Group.ID)
+ }
+ // xiugai end
+ return out, nil
}
// GetKeyAndOwnerID 根据 API Key ID 获取其 key 与所有者(用户)ID。
@@ -115,7 +121,13 @@ func (r *apiKeyRepository) GetByKey(ctx context.Context, key string) (*service.A
}
return nil, err
}
- return apiKeyEntityToService(m), nil
+ out := apiKeyEntityToService(m)
+ // xiugai 修改自动映射功能
+ if out.Group != nil {
+ out.Group.ModelMapping = loadGroupModelMappingSQL(ctx, r.sql, out.Group.ID)
+ }
+ // xiugai end
+ return out, nil
}
func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*service.APIKey, error) {
@@ -193,7 +205,13 @@ func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*se
}
return nil, err
}
- return apiKeyEntityToService(m), nil
+ out := apiKeyEntityToService(m)
+ // xiugai 修改自动映射功能
+ if out.Group != nil {
+ out.Group.ModelMapping = loadGroupModelMappingSQL(ctx, r.sql, out.Group.ID)
+ }
+ // xiugai end
+ return out, nil
}
func (r *apiKeyRepository) Update(ctx context.Context, key *service.APIKey) error {
diff --git a/backend/internal/repository/api_key_repo_last_used_unit_test.go b/backend/internal/repository/api_key_repo_last_used_unit_test.go
index 7c6e2850e8e..2835c678f0a 100644
--- a/backend/internal/repository/api_key_repo_last_used_unit_test.go
+++ b/backend/internal/repository/api_key_repo_last_used_unit_test.go
@@ -30,7 +30,8 @@ func newAPIKeyRepoSQLite(t *testing.T) (*apiKeyRepository, *dbent.Client) {
client := enttest.NewClient(t, enttest.WithOptions(dbent.Driver(drv)))
t.Cleanup(func() { _ = client.Close() })
- return &apiKeyRepository{client: client}, client
+ // xiugai sql ֶΪ nil loadGroupModelMappingSQL ָ panic
+ return &apiKeyRepository{client: client, sql: db}, client // end
}
func mustCreateAPIKeyRepoUser(t *testing.T, ctx context.Context, client *dbent.Client, email string) *service.User {
diff --git a/backend/internal/repository/group_repo.go b/backend/internal/repository/group_repo.go
index 9c3b201079f..d2cd10609aa 100644
--- a/backend/internal/repository/group_repo.go
+++ b/backend/internal/repository/group_repo.go
@@ -3,6 +3,7 @@ package repository
import (
"context"
"database/sql"
+ "encoding/json"
"errors"
"fmt"
"sort"
@@ -81,6 +82,11 @@ func (r *groupRepository) Create(ctx context.Context, groupIn *service.Group) er
groupIn.ID = created.ID
groupIn.CreatedAt = created.CreatedAt
groupIn.UpdatedAt = created.UpdatedAt
+ // xiugai 修改自动映射功能
+ if setErr := r.setGroupModelMapping(ctx, groupIn.ID, groupIn.ModelMapping); setErr != nil {
+ logger.LegacyPrintf("repository.group", "[ModelMapping] set failed on create: group=%d err=%v", groupIn.ID, setErr)
+ }
+ // xiugai end
if err := enqueueSchedulerOutbox(ctx, r.sql, service.SchedulerOutboxEventGroupChanged, nil, &groupIn.ID, nil); err != nil {
logger.LegacyPrintf("repository.group", "[SchedulerOutbox] enqueue group create failed: group=%d err=%v", groupIn.ID, err)
}
@@ -88,6 +94,95 @@ func (r *groupRepository) Create(ctx context.Context, groupIn *service.Group) er
return translatePersistenceError(err, nil, service.ErrGroupExists)
}
+// setGroupModelMapping 用 raw SQL 写入 model_mapping 列(ENT 不感知此列)。
+func (r *groupRepository) setGroupModelMapping(ctx context.Context, id int64, mapping map[string]string) error {
+ return setGroupModelMappingSQL(ctx, r.sql, id, mapping)
+}
+
+// loadGroupModelMapping 用 raw SQL 读取单个分组的 model_mapping。
+func (r *groupRepository) loadGroupModelMapping(ctx context.Context, id int64) map[string]string {
+ return loadGroupModelMappingSQL(ctx, r.sql, id)
+}
+
+// loadGroupModelMappings 批量读取多个分组的 model_mapping,返回 map[groupID]mapping。
+func (r *groupRepository) loadGroupModelMappings(ctx context.Context, ids []int64) map[int64]map[string]string {
+ return loadGroupModelMappingsSQL(ctx, r.sql, ids)
+}
+
+// xiugai 修改自动映射功能
+// setGroupModelMappingSQL 是包级辅助函数,可被同包其他 repo 调用。
+func setGroupModelMappingSQL(ctx context.Context, db sqlExecutor, id int64, mapping map[string]string) error {
+ data := mapping
+ if data == nil {
+ data = map[string]string{}
+ }
+ b, err := json.Marshal(data)
+ if err != nil {
+ return err
+ }
+ _, err = db.ExecContext(ctx, `UPDATE groups SET model_mapping = $1 WHERE id = $2`, string(b), id)
+ return err
+}
+
+// loadGroupModelMappingSQL 是包级辅助函数,可被同包其他 repo 调用。
+func loadGroupModelMappingSQL(ctx context.Context, db sqlExecutor, id int64) map[string]string {
+ rows, err := db.QueryContext(ctx, `SELECT model_mapping FROM groups WHERE id = $1 AND deleted_at IS NULL`, id)
+ if err != nil {
+ return nil
+ }
+ defer rows.Close()
+ if !rows.Next() {
+ return nil
+ }
+ var raw string
+ if err := rows.Scan(&raw); err != nil {
+ return nil
+ }
+ var m map[string]string
+ if err := json.Unmarshal([]byte(raw), &m); err != nil {
+ return nil
+ }
+ return m
+}
+
+// loadGroupModelMappingsSQL 是包级辅助函数,批量读取多个分组的 model_mapping。
+func loadGroupModelMappingsSQL(ctx context.Context, db sqlExecutor, ids []int64) map[int64]map[string]string {
+ if len(ids) == 0 {
+ return nil
+ }
+ placeholders := make([]string, len(ids))
+ args := make([]any, len(ids))
+ for i, id := range ids {
+ placeholders[i] = fmt.Sprintf("$%d", i+1)
+ args[i] = id
+ }
+ query := fmt.Sprintf(
+ `SELECT id, model_mapping FROM groups WHERE id IN (%s) AND deleted_at IS NULL`,
+ strings.Join(placeholders, ","),
+ )
+ rows, err := db.QueryContext(ctx, query, args...)
+ if err != nil {
+ return nil
+ }
+ defer rows.Close()
+ result := make(map[int64]map[string]string, len(ids))
+ for rows.Next() {
+ var gid int64
+ var raw string
+ if err := rows.Scan(&gid, &raw); err != nil {
+ continue
+ }
+ var m map[string]string
+ if err := json.Unmarshal([]byte(raw), &m); err != nil {
+ continue
+ }
+ result[gid] = m
+ }
+ return result
+}
+
+// xiugai end
+
func (r *groupRepository) GetByID(ctx context.Context, id int64) (*service.Group, error) {
out, err := r.GetByIDLite(ctx, id)
if err != nil {
@@ -111,7 +206,11 @@ func (r *groupRepository) GetByIDLite(ctx context.Context, id int64) (*service.G
if err != nil {
return nil, translatePersistenceError(err, service.ErrGroupNotFound, nil)
}
- return groupEntityToService(m), nil
+ out := groupEntityToService(m)
+ // xiugai 修改自动映射功能
+ out.ModelMapping = r.loadGroupModelMapping(ctx, id)
+ // xiugai end
+ return out, nil
}
func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) error {
@@ -203,6 +302,11 @@ func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) er
return translatePersistenceError(err, service.ErrGroupNotFound, service.ErrGroupExists)
}
groupIn.UpdatedAt = updated.UpdatedAt
+ // xiugai 修改自动映射功能
+ if setErr := r.setGroupModelMapping(ctx, groupIn.ID, groupIn.ModelMapping); setErr != nil {
+ logger.LegacyPrintf("repository.group", "[ModelMapping] set failed on update: group=%d err=%v", groupIn.ID, setErr)
+ }
+ // xiugai end
if err := enqueueSchedulerOutbox(ctx, r.sql, service.SchedulerOutboxEventGroupChanged, nil, &groupIn.ID, nil); err != nil {
logger.LegacyPrintf("repository.group", "[SchedulerOutbox] enqueue group update failed: group=%d err=%v", groupIn.ID, err)
}
@@ -281,6 +385,12 @@ func (r *groupRepository) ListWithFilters(ctx context.Context, params pagination
outGroups[i].RateLimitedAccountCount = c.RateLimited
}
}
+ // xiugai 修改自动映射功能
+ mappings := r.loadGroupModelMappings(ctx, groupIDs)
+ for i := range outGroups {
+ outGroups[i].ModelMapping = mappings[outGroups[i].ID]
+ }
+ // xiugai end
return outGroups, paginationResultFromTotal(int64(total), params), nil
}
@@ -358,16 +468,20 @@ func (r *groupRepository) listWithAccountCountSort(ctx context.Context, q *dbent
}
outGroups := make([]service.Group, len(page))
+ // xiugai 修改自动映射功能
+ mappings := r.loadGroupModelMappings(ctx, pageIDs)
for i := range groups {
g := groupEntityToService(groups[i])
c := counts[g.ID]
g.AccountCount = c.Total
g.ActiveAccountCount = c.Active
g.RateLimitedAccountCount = c.RateLimited
+ g.ModelMapping = mappings[g.ID]
if idx, ok := pageIdx[g.ID]; ok {
outGroups[idx] = *g
}
}
+ // xiugai end
return outGroups, paginationResultFromTotal(int64(total), params), nil
}
@@ -452,6 +566,12 @@ func (r *groupRepository) ListActive(ctx context.Context) ([]service.Group, erro
outGroups[i].RateLimitedAccountCount = c.RateLimited
}
}
+ // xiugai 修改自动映射功能
+ mappings := r.loadGroupModelMappings(ctx, groupIDs)
+ for i := range outGroups {
+ outGroups[i].ModelMapping = mappings[outGroups[i].ID]
+ }
+ // xiugai end
return outGroups, nil
}
@@ -482,6 +602,12 @@ func (r *groupRepository) ListActiveByPlatform(ctx context.Context, platform str
outGroups[i].RateLimitedAccountCount = c.RateLimited
}
}
+ // xiugai 修改自动映射功能
+ mappings := r.loadGroupModelMappings(ctx, groupIDs)
+ for i := range outGroups {
+ outGroups[i].ModelMapping = mappings[outGroups[i].ID]
+ }
+ // xiugai end
return outGroups, nil
}
diff --git a/backend/internal/repository/usage_billing_repo.go b/backend/internal/repository/usage_billing_repo.go
index 62f48b58f4c..0e40affd448 100644
--- a/backend/internal/repository/usage_billing_repo.go
+++ b/backend/internal/repository/usage_billing_repo.go
@@ -134,7 +134,7 @@ func (r *usageBillingRepository) applyUsageBillingEffects(ctx context.Context, t
}
}
- if cmd.AccountQuotaCost > 0 && (strings.EqualFold(cmd.AccountType, service.AccountTypeAPIKey) || strings.EqualFold(cmd.AccountType, service.AccountTypeBedrock)) {
+ if cmd.AccountQuotaCost > 0 && (strings.EqualFold(cmd.AccountType, service.AccountTypeAPIKey) || strings.EqualFold(cmd.AccountType, service.AccountTypeBedrock) || strings.EqualFold(cmd.AccountType, service.AccountTypeAnthropicAWS)) {
quotaState, err := incrementUsageBillingAccountQuota(ctx, tx, cmd.AccountID, cmd.AccountQuotaCost)
if err != nil {
return err
diff --git a/backend/internal/service/account.go b/backend/internal/service/account.go
index cd06ffa3c49..e4f73e7837d 100644
--- a/backend/internal/service/account.go
+++ b/backend/internal/service/account.go
@@ -720,6 +720,9 @@ func (a *Account) ResolveCompactMappedModel(requestedModel string) (mappedModel
}
func (a *Account) GetBaseURL() string {
+ if a.Type == AccountTypeAnthropicAWS {
+ return a.GetAnthropicAWSBaseURL()
+ }
if a.Type != AccountTypeAPIKey {
return ""
}
@@ -956,9 +959,34 @@ func (a *Account) IsBedrockAPIKey() bool {
return a.IsBedrock() && a.GetCredential("auth_mode") == "apikey"
}
+// IsAnthropicAWS 返回账号是否为 Claude Platform on AWS 类型
+func (a *Account) IsAnthropicAWS() bool {
+ return a.Platform == PlatformAnthropic && a.Type == AccountTypeAnthropicAWS
+}
+
+// GetAnthropicAWSRegion 返回账号配置的 AWS region(默认 us-east-1)
+func (a *Account) GetAnthropicAWSRegion() string {
+ region := strings.TrimSpace(a.GetCredential("aws_region"))
+ if region == "" {
+ return "us-east-1"
+ }
+ return region
+}
+
+// GetAnthropicAWSWorkspaceID 返回 Claude Platform on AWS 的 workspace ID
+func (a *Account) GetAnthropicAWSWorkspaceID() string {
+ return strings.TrimSpace(a.GetCredential("anthropic_workspace_id"))
+}
+
+// GetAnthropicAWSBaseURL 根据 region 推导 Claude Platform on AWS 的 endpoint
+func (a *Account) GetAnthropicAWSBaseURL() string {
+ return "https://aws-external-anthropic." + a.GetAnthropicAWSRegion() + ".api.aws"
+}
+
// IsAPIKeyOrBedrock 返回账号类型是否支持配额和池模式等特性
+// 同时覆盖 anthropic_aws(Claude Platform on AWS),其特性等同 API Key 账号
func (a *Account) IsAPIKeyOrBedrock() bool {
- return a.Type == AccountTypeAPIKey || a.Type == AccountTypeBedrock
+ return a.Type == AccountTypeAPIKey || a.Type == AccountTypeBedrock || a.Type == AccountTypeAnthropicAWS
}
func (a *Account) IsOpenAI() bool {
diff --git a/backend/internal/service/admin_service.go b/backend/internal/service/admin_service.go
index 4468420619d..72c95d00adc 100644
--- a/backend/internal/service/admin_service.go
+++ b/backend/internal/service/admin_service.go
@@ -214,6 +214,10 @@ type CreateGroupInput struct {
MessagesDispatchModelConfig OpenAIMessagesDispatchModelConfig
// RPMLimit 分组 RPM 上限(0 = 不限制)
RPMLimit int
+ // xiugai 修改自动映射功能
+ // 分组级模型映射(支持通配符和正则)
+ ModelMapping map[string]string
+ // xiugai end
// 从指定分组复制账号(创建分组后在同一事务内绑定)
CopyAccountsFromGroupIDs []int64
}
@@ -254,6 +258,10 @@ type UpdateGroupInput struct {
MessagesDispatchModelConfig *OpenAIMessagesDispatchModelConfig
// RPMLimit 分组 RPM 上限(0 = 不限制),nil 表示未提供不改动。
RPMLimit *int
+ // xiugai 修改自动映射功能
+ // 分组级模型映射(支持通配符和正则),nil 表示未提供不改动
+ ModelMapping map[string]string
+ // xiugai end
// 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号)
CopyAccountsFromGroupIDs []int64
}
@@ -1695,6 +1703,9 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
DefaultMappedModel: input.DefaultMappedModel,
MessagesDispatchModelConfig: normalizeOpenAIMessagesDispatchModelConfig(input.MessagesDispatchModelConfig),
RPMLimit: input.RPMLimit,
+ // xiugai 修改自动映射功能
+ ModelMapping: input.ModelMapping,
+ // xiugai end
}
sanitizeGroupMessagesDispatchFields(group)
if err := s.groupRepo.Create(ctx, group); err != nil {
@@ -1944,6 +1955,11 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd
if input.RPMLimit != nil {
group.RPMLimit = *input.RPMLimit
}
+ // xiugai 修改自动映射功能
+ if input.ModelMapping != nil {
+ group.ModelMapping = input.ModelMapping
+ }
+ // xiugai end
sanitizeGroupMessagesDispatchFields(group)
if err := s.groupRepo.Update(ctx, group); err != nil {
diff --git a/backend/internal/service/domain_constants.go b/backend/internal/service/domain_constants.go
index c7fa425a2f3..09a07775609 100644
--- a/backend/internal/service/domain_constants.go
+++ b/backend/internal/service/domain_constants.go
@@ -46,6 +46,7 @@ const (
AccountTypeAPIKey = domain.AccountTypeAPIKey // API Key类型账号
AccountTypeUpstream = domain.AccountTypeUpstream // 上游透传类型账号(通过 Base URL + API Key 连接上游)
AccountTypeBedrock = domain.AccountTypeBedrock // AWS Bedrock 类型账号(通过 SigV4 签名或 API Key 连接 Bedrock,由 credentials.auth_mode 区分)
+ AccountTypeAnthropicAWS = domain.AccountTypeAnthropicAWS // Claude Platform on AWS 类型账号(Anthropic 在 AWS 上托管的外部 API)
AccountTypeServiceAccount = domain.AccountTypeServiceAccount // Google Service Account 类型账号(用于 Vertex AI)
)
diff --git a/backend/internal/service/group.go b/backend/internal/service/group.go
index f61553522af..e989d2a23b8 100644
--- a/backend/internal/service/group.go
+++ b/backend/internal/service/group.go
@@ -1,6 +1,8 @@
package service
import (
+ "regexp"
+ "sort"
"strings"
"time"
@@ -66,6 +68,16 @@ type Group struct {
// 一旦设置即接管该分组用户的限流(覆盖用户级 rpm_limit),可被 user-group rpm_override 进一步覆盖。
RPMLimit int
+ // xiugai 修改自动映射功能
+ // 分组级模型映射(分组维度,优先于渠道/账号级映射)。
+ // key: 匹配模式,value: 目标模型名。
+ // 匹配规则(优先级由高到低):
+ // 1. 精确匹配
+ // 2. 正则匹配:pattern 以 "~" 开头,去掉前缀后作为 Go regexp 编译匹配
+ // 3. 通配符匹配:pattern 以 "*" 结尾,匹配对应前缀
+ ModelMapping map[string]string
+ // xiugai end
+
CreatedAt time.Time
UpdatedAt time.Time
@@ -165,3 +177,55 @@ func matchModelPattern(pattern, model string) bool {
return false
}
+
+// xiugai 修改自动映射功能
+// ResolveGroupMappedModel 查找分组级别的模型映射。
+// matched=true 表示命中规则(即使映射结果与原模型名相同)。
+// 匹配优先级:精确 > 正则(~ 前缀)> 通配符(* 后缀),同级按 pattern 长度降序取最长匹配。
+func (g *Group) ResolveGroupMappedModel(requestedModel string) (mappedModel string, matched bool) {
+ if g == nil || len(g.ModelMapping) == 0 || requestedModel == "" {
+ return requestedModel, false
+ }
+ // 1. 精确匹配
+ if target, ok := g.ModelMapping[requestedModel]; ok {
+ return target, true
+ }
+ // 2. 正则匹配(pattern 以 "~" 开头)
+ type candidate struct {
+ pattern string
+ target string
+ }
+ var regexCandidates, wildcardCandidates []candidate
+ for pattern, target := range g.ModelMapping {
+ if strings.HasPrefix(pattern, "~") {
+ regexCandidates = append(regexCandidates, candidate{pattern, target})
+ } else if strings.HasSuffix(pattern, "*") {
+ wildcardCandidates = append(wildcardCandidates, candidate{pattern, target})
+ }
+ }
+ // 正则:按 pattern 长度降序,取第一个匹配
+ sort.Slice(regexCandidates, func(i, j int) bool {
+ return len(regexCandidates[i].pattern) > len(regexCandidates[j].pattern)
+ })
+ for _, c := range regexCandidates {
+ re, err := regexp.Compile(c.pattern[1:]) // 去掉 "~" 前缀
+ if err != nil {
+ continue
+ }
+ if re.MatchString(requestedModel) {
+ return c.target, true
+ }
+ }
+ // 3. 通配符:按 pattern 长度降序(最长前缀优先)
+ sort.Slice(wildcardCandidates, func(i, j int) bool {
+ return len(wildcardCandidates[i].pattern) > len(wildcardCandidates[j].pattern)
+ })
+ for _, c := range wildcardCandidates {
+ prefix := c.pattern[:len(c.pattern)-1]
+ if strings.HasPrefix(requestedModel, prefix) {
+ return c.target, true
+ }
+ }
+ return requestedModel, false
+}
+// xiugai end
diff --git a/backend/internal/service/openai_gateway_chat_completions.go b/backend/internal/service/openai_gateway_chat_completions.go
index 27eb211e2a7..413a739b6a7 100644
--- a/backend/internal/service/openai_gateway_chat_completions.go
+++ b/backend/internal/service/openai_gateway_chat_completions.go
@@ -213,9 +213,12 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
return nil, fmt.Errorf("build upstream request: %w", err)
}
+ // xiugai 修复 session 隔离缺失:用 isolateOpenAISessionID 将 apiKeyID 混入 session_id,
+ // 防止不同 API Key 使用相同 promptCacheKey 时共享上游 session
if promptCacheKey != "" {
- upstreamReq.Header.Set("session_id", generateSessionUUID(promptCacheKey))
+ upstreamReq.Header.Set("session_id", generateSessionUUID(isolateOpenAISessionID(getAPIKeyIDFromContext(c), promptCacheKey)))
}
+ // end
// 7. Send request
proxyURL := ""
diff --git a/backend/internal/service/openai_gateway_record_usage_test.go b/backend/internal/service/openai_gateway_record_usage_test.go
index 096f5b1079b..01ef24701fb 100644
--- a/backend/internal/service/openai_gateway_record_usage_test.go
+++ b/backend/internal/service/openai_gateway_record_usage_test.go
@@ -155,6 +155,7 @@ func newOpenAIRecordUsageServiceForTest(usageRepo UsageLogRepository, userRepo U
nil,
nil,
nil,
+ nil,
)
svc.userGroupRateResolver = newUserGroupRateResolver(
rateRepo,
diff --git a/backend/internal/service/openai_ws_protocol_forward_test.go b/backend/internal/service/openai_ws_protocol_forward_test.go
index f3936de1333..47d6e04557b 100644
--- a/backend/internal/service/openai_ws_protocol_forward_test.go
+++ b/backend/internal/service/openai_ws_protocol_forward_test.go
@@ -619,6 +619,7 @@ func TestNewOpenAIGatewayService_InitializesOpenAIWSResolver(t *testing.T) {
nil,
nil,
nil,
+ nil,
)
decision := svc.getOpenAIWSProtocolResolver().Resolve(nil)
diff --git a/backend/migrations/138_add_group_model_mapping.sql b/backend/migrations/138_add_group_model_mapping.sql
new file mode 100644
index 00000000000..56934704763
--- /dev/null
+++ b/backend/migrations/138_add_group_model_mapping.sql
@@ -0,0 +1,3 @@
+-- 138_add_group_model_mapping.sql
+-- 为分组添加模型映射配置:支持通配符(末尾 *)和正则(~ 前缀)匹配,可在分组级别对请求模型进行透明重写。
+ALTER TABLE groups ADD COLUMN IF NOT EXISTS model_mapping JSONB NOT NULL DEFAULT '{}';
diff --git a/frontend/src/components/account/CreateAccountModal.vue b/frontend/src/components/account/CreateAccountModal.vue
index 90d5e15ad41..080e80d1741 100644
--- a/frontend/src/components/account/CreateAccountModal.vue
+++ b/frontend/src/components/account/CreateAccountModal.vue
@@ -153,7 +153,7 @@
-
+
+
+
-
+
+
+
+
+
+
+
+
+
+
+ len: {{ anthropicAwsApiKeyChecks.length }}
+ tail: …{{ anthropicAwsApiKeyChecks.tail }}
+ ⚠ {{ t('admin.accounts.anthropicAwsApiKeyPrefixWarning') }}
+ ⚠ {{ t('admin.accounts.anthropicAwsApiKeyLengthWarning') }}
+ ✓ ok
+
+
{{ t('admin.accounts.anthropicAwsApiKeyHint') }}
+
+
+
+
+
{{ t('admin.accounts.anthropicAwsWorkspaceIdHint') }}
+
+
+
+
+
{{ t('admin.accounts.anthropicAwsRegionHint') }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
+ {{ t('admin.accounts.supportsAllModels') }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('admin.accounts.poolModeHint') }}
+
+
+
+
+
+
+
+
+ {{
+ t('admin.accounts.poolModeRetryCountHint', {
+ default: DEFAULT_POOL_MODE_RETRY_COUNT,
+ max: MAX_POOL_MODE_RETRY_COUNT
+ })
+ }}
+
+
+
+
+
+
@@ -3278,7 +3499,7 @@ interface TempUnschedRuleForm {
// State
const step = ref(1)
const submitting = ref(false)
-const accountCategory = ref<'oauth-based' | 'apikey' | 'bedrock' | 'service_account'>('oauth-based') // UI selection for account category
+const accountCategory = ref<'oauth-based' | 'apikey' | 'bedrock' | 'anthropic_aws' | 'service_account'>('oauth-based') // UI selection for account category
const addMethod = ref
('oauth') // For oauth-based: 'oauth' or 'setup-token'
const apiKeyBaseUrl = ref('https://api.anthropic.com')
const apiKeyValue = ref('')
@@ -3345,6 +3566,23 @@ const bedrockSessionToken = ref('')
const bedrockRegion = ref('us-east-1')
const bedrockForceGlobal = ref(false)
const bedrockApiKeyValue = ref('')
+
+// Claude Platform on AWS (anthropic_aws) credentials
+const anthropicAwsApiKey = ref('')
+const anthropicAwsWorkspaceId = ref('')
+const anthropicAwsRegion = ref('us-east-1')
+const anthropicAwsApiKeyVisible = ref(false)
+// 粘贴回显:暴露长度 + 末 4 位 + 前缀校验,防止 S/5、O/0 等视觉混淆字符
+const anthropicAwsApiKeyChecks = computed(() => {
+ const v = (anthropicAwsApiKey.value || '').trim()
+ return {
+ length: v.length,
+ tail: v.length >= 4 ? v.slice(-4) : v,
+ prefixValid: v.startsWith('AEAA'),
+ // Claude Platform on AWS 的 key 实测约 130–300 字符;过短一般是 paste 丢字
+ lengthLikely: v.length >= 100
+ }
+})
const vertexServiceAccountFileInput = ref(null)
const vertexServiceAccountJson = ref('')
const vertexProjectId = ref('')
@@ -3614,6 +3852,11 @@ watch(
form.type = 'bedrock' as AccountType
return
}
+ // Claude Platform on AWS 类型
+ if (form.platform === 'anthropic' && category === 'anthropic_aws') {
+ form.type = 'anthropic_aws' as AccountType
+ return
+ }
if ((form.platform === 'gemini' || form.platform === 'anthropic') && category === 'service_account') {
form.type = 'service_account' as AccountType
} else if (category === 'oauth-based') {
@@ -3660,6 +3903,9 @@ watch(
if (newPlatform !== 'anthropic' && accountCategory.value === 'bedrock') {
accountCategory.value = 'oauth-based'
}
+ if (newPlatform !== 'anthropic' && accountCategory.value === 'anthropic_aws') {
+ accountCategory.value = 'oauth-based'
+ }
// Reset Bedrock fields when switching platforms
bedrockAccessKeyId.value = ''
bedrockSecretAccessKey.value = ''
@@ -3668,6 +3914,10 @@ watch(
bedrockForceGlobal.value = false
bedrockAuthMode.value = 'sigv4'
bedrockApiKeyValue.value = ''
+ // Reset Claude Platform on AWS fields
+ anthropicAwsApiKey.value = ''
+ anthropicAwsWorkspaceId.value = ''
+ anthropicAwsRegion.value = 'us-east-1'
vertexServiceAccountJson.value = ''
vertexProjectId.value = ''
vertexClientEmail.value = ''
@@ -4358,6 +4608,45 @@ const handleSubmit = async () => {
return
}
+ // For Claude Platform on AWS type, create directly
+ if (form.platform === 'anthropic' && accountCategory.value === 'anthropic_aws') {
+ if (!form.name.trim()) {
+ appStore.showError(t('admin.accounts.pleaseEnterAccountName'))
+ return
+ }
+ if (!anthropicAwsApiKey.value.trim()) {
+ appStore.showError(t('admin.accounts.anthropicAwsApiKeyRequired'))
+ return
+ }
+ if (!anthropicAwsWorkspaceId.value.trim()) {
+ appStore.showError(t('admin.accounts.anthropicAwsWorkspaceIdRequired'))
+ return
+ }
+
+ const credentials: Record = {
+ api_key: anthropicAwsApiKey.value.trim(),
+ anthropic_workspace_id: anthropicAwsWorkspaceId.value.trim(),
+ aws_region: anthropicAwsRegion.value.trim() || 'us-east-1',
+ }
+
+ const modelMapping = buildModelMappingObject(
+ modelRestrictionMode.value, allowedModels.value, modelMappings.value
+ )
+ if (modelMapping) {
+ credentials.model_mapping = modelMapping
+ }
+
+ if (poolModeEnabled.value) {
+ credentials.pool_mode = true
+ credentials.pool_mode_retry_count = normalizePoolModeRetryCount(poolModeRetryCount.value)
+ }
+
+ applyInterceptWarmup(credentials, interceptWarmupRequests.value, 'create')
+
+ await createAccountAndFinish('anthropic', 'anthropic_aws' as AccountType, credentials)
+ return
+ }
+
// For Antigravity upstream type, create directly
if (form.platform === 'antigravity' && antigravityAccountType.value === 'upstream') {
if (!form.name.trim()) {
diff --git a/frontend/src/components/account/EditAccountModal.vue b/frontend/src/components/account/EditAccountModal.vue
index 070887feb1c..b76ea1113f7 100644
--- a/frontend/src/components/account/EditAccountModal.vue
+++ b/frontend/src/components/account/EditAccountModal.vue
@@ -976,6 +976,165 @@
+
+
+
+
+
+
+
+
+
+ len: {{ editAnthropicAwsApiKeyChecks.length }}
+ tail: …{{ editAnthropicAwsApiKeyChecks.tail }}
+ ⚠ {{ t('admin.accounts.anthropicAwsApiKeyPrefixWarning') }}
+ ⚠ {{ t('admin.accounts.anthropicAwsApiKeyLengthWarning') }}
+ ✓ ok
+
+
{{ t('admin.accounts.bedrockApiKeyLeaveEmpty') }}
+
+
+
+
+
{{ t('admin.accounts.anthropicAwsWorkspaceIdHint') }}
+
+
+
+
+
{{ t('admin.accounts.anthropicAwsRegionHint') }}
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
+ {{ t('admin.accounts.supportsAllModels') }}
+
+
+
+
+
+
+
+
+
+
+
+ {{ t('admin.accounts.poolModeHint') }}
+
+
+
+
+
+
+
+
+ {{
+ t('admin.accounts.poolModeRetryCountHint', {
+ default: DEFAULT_POOL_MODE_RETRY_COUNT,
+ max: MAX_POOL_MODE_RETRY_COUNT
+ })
+ }}
+
+
+
+
+
@@ -1487,9 +1646,9 @@
-
+
@@ -2304,6 +2463,20 @@ const editBedrockSessionToken = ref('')
const editBedrockRegion = ref('')
const editBedrockForceGlobal = ref(false)
const editBedrockApiKeyValue = ref('')
+// Claude Platform on AWS credentials
+const editAnthropicAwsApiKey = ref('')
+const editAnthropicAwsWorkspaceId = ref('')
+const editAnthropicAwsRegion = ref('us-east-1')
+const editAnthropicAwsApiKeyVisible = ref(false)
+const editAnthropicAwsApiKeyChecks = computed(() => {
+ const v = (editAnthropicAwsApiKey.value || '').trim()
+ return {
+ length: v.length,
+ tail: v.length >= 4 ? v.slice(-4) : v,
+ prefixValid: v.startsWith('AEAA'),
+ lengthLikely: v.length >= 100
+ }
+})
const editVertexProjectId = ref('')
const editVertexClientEmail = ref('')
const editVertexLocation = ref('us-central1')
@@ -2721,8 +2894,8 @@ const syncFormFromAccount = (newAccount: Account | null) => {
}
}
- // Load quota limit for apikey/bedrock accounts (bedrock quota is also loaded in its own branch above)
- if (newAccount.type === 'apikey' || newAccount.type === 'bedrock') {
+ // Load quota limit for apikey/bedrock/anthropic_aws accounts (bedrock/anthropic_aws quota is also loaded in their own branches above)
+ if (newAccount.type === 'apikey' || newAccount.type === 'bedrock' || newAccount.type === 'anthropic_aws') {
const quotaVal = extra?.quota_limit as number | undefined
editQuotaLimit.value = (quotaVal && quotaVal > 0) ? quotaVal : null
const dailyVal = extra?.quota_daily_limit as number | undefined
@@ -3807,8 +3980,8 @@ const handleSubmit = async () => {
updatePayload.extra = newExtra
}
- // For apikey/bedrock accounts, handle quota_limit in extra
- if (props.account.type === 'apikey' || props.account.type === 'bedrock') {
+ // For apikey/bedrock/anthropic_aws accounts, handle quota_limit in extra
+ if (props.account.type === 'apikey' || props.account.type === 'bedrock' || props.account.type === 'anthropic_aws') {
const currentExtra = (updatePayload.extra as Record) ||
(props.account.extra as Record) || {}
const newExtra: Record = { ...currentExtra }
diff --git a/frontend/src/components/admin/account/AccountActionMenu.vue b/frontend/src/components/admin/account/AccountActionMenu.vue
index 06bd23abf76..f09601db3c0 100644
--- a/frontend/src/components/admin/account/AccountActionMenu.vue
+++ b/frontend/src/components/admin/account/AccountActionMenu.vue
@@ -83,7 +83,7 @@ const isAntigravityOAuth = computed(() => props.account?.platform === 'antigravi
const isOpenAIOAuth = computed(() => props.account?.platform === 'openai' && props.account?.type === 'oauth')
const supportsPrivacy = computed(() => isAntigravityOAuth.value || isOpenAIOAuth.value)
const hasQuotaLimit = computed(() => {
- return (props.account?.type === 'apikey' || props.account?.type === 'bedrock') && (
+ return (props.account?.type === 'apikey' || props.account?.type === 'bedrock' || props.account?.type === 'anthropic_aws') && (
(props.account?.quota_limit ?? 0) > 0 ||
(props.account?.quota_daily_limit ?? 0) > 0 ||
(props.account?.quota_weekly_limit ?? 0) > 0
diff --git a/frontend/src/components/admin/account/AccountTableFilters.vue b/frontend/src/components/admin/account/AccountTableFilters.vue
index b33dad84e0e..5119f1dee3f 100644
--- a/frontend/src/components/admin/account/AccountTableFilters.vue
+++ b/frontend/src/components/admin/account/AccountTableFilters.vue
@@ -26,7 +26,7 @@ const updateStatus = (value: string | number | boolean | null) => { emit('update
const updatePrivacyMode = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, privacy_mode: value }) }
const updateGroup = (value: string | number | boolean | null) => { emit('update:filters', { ...props.filters, group: value }) }
const pOpts = computed(() => [{ value: '', label: t('admin.accounts.allPlatforms') }, { value: 'anthropic', label: 'Anthropic' }, { value: 'openai', label: 'OpenAI' }, { value: 'gemini', label: 'Gemini' }, { value: 'antigravity', label: 'Antigravity' }])
-const tOpts = computed(() => [{ value: '', label: t('admin.accounts.allTypes') }, { value: 'oauth', label: t('admin.accounts.oauthType') }, { value: 'setup-token', label: t('admin.accounts.setupToken') }, { value: 'apikey', label: t('admin.accounts.apiKey') }, { value: 'bedrock', label: 'AWS Bedrock' }])
+const tOpts = computed(() => [{ value: '', label: t('admin.accounts.allTypes') }, { value: 'oauth', label: t('admin.accounts.oauthType') }, { value: 'setup-token', label: t('admin.accounts.setupToken') }, { value: 'apikey', label: t('admin.accounts.apiKey') }, { value: 'bedrock', label: 'AWS Bedrock' }, { value: 'anthropic_aws', label: 'Claude Platform on AWS' }])
const sOpts = computed(() => [{ value: '', label: t('admin.accounts.allStatus') }, { value: 'active', label: t('admin.accounts.status.active') }, { value: 'inactive', label: t('admin.accounts.status.inactive') }, { value: 'error', label: t('admin.accounts.status.error') }, { value: 'rate_limited', label: t('admin.accounts.status.rateLimited') }, { value: 'temp_unschedulable', label: t('admin.accounts.status.tempUnschedulable') }, { value: 'unschedulable', label: t('admin.accounts.status.unschedulable') }])
const privacyOpts = computed(() => [
{ value: '', label: t('admin.accounts.allPrivacyModes') },
diff --git a/frontend/src/components/admin/account/ImportDataModal.vue b/frontend/src/components/admin/account/ImportDataModal.vue
index 6c120be39a0..02b89632a62 100644
--- a/frontend/src/components/admin/account/ImportDataModal.vue
+++ b/frontend/src/components/admin/account/ImportDataModal.vue
@@ -51,6 +51,19 @@
{{ t('admin.accounts.dataImportResultSummary', result) }}
+
+
+ {{ t('admin.accounts.dataImportSkipped') }}
+
+
+
+ {{ item.name || '-' }} — {{ item.message }}
+
+
+
+
{{ t('admin.accounts.dataImportErrors') }}
@@ -114,7 +127,12 @@ const result = ref
(null)
const fileInput = ref(null)
const fileName = computed(() => file.value?.name || '')
-const errorItems = computed(() => result.value?.errors || [])
+const errorItems = computed(() =>
+ (result.value?.errors || []).filter((item) => item.kind !== 'account_skipped')
+)
+const skippedItems = computed(() =>
+ (result.value?.errors || []).filter((item) => item.kind === 'account_skipped')
+)
watch(
() => props.show,
@@ -181,6 +199,7 @@ const handleImport = async () => {
const msgParams: Record = {
account_created: res.account_created,
+ account_skipped: res.account_skipped,
account_failed: res.account_failed,
proxy_created: res.proxy_created,
proxy_reused: res.proxy_reused,
diff --git a/frontend/src/components/common/PlatformTypeBadge.vue b/frontend/src/components/common/PlatformTypeBadge.vue
index 1c7b08c0f1f..94e22e10c15 100644
--- a/frontend/src/components/common/PlatformTypeBadge.vue
+++ b/frontend/src/components/common/PlatformTypeBadge.vue
@@ -26,6 +26,7 @@
+
{{ typeLabel }}
@@ -88,6 +89,8 @@ const typeLabel = computed(() => {
case 'apikey':
return 'Key'
case 'bedrock':
+ return 'Bedrock'
+ case 'anthropic_aws':
return 'AWS'
case 'service_account':
return 'Vertex'
diff --git a/frontend/src/i18n/locales/en.ts b/frontend/src/i18n/locales/en.ts
index 53176a93ced..e02005ce777 100644
--- a/frontend/src/i18n/locales/en.ts
+++ b/frontend/src/i18n/locales/en.ts
@@ -3210,6 +3210,20 @@ export default {
claudeConsole: 'Claude Console',
bedrockLabel: 'AWS Bedrock',
bedrockDesc: 'SigV4 / API Key',
+ anthropicAwsLabel: 'Claude Platform on AWS',
+ anthropicAwsDesc: 'API Key + Workspace ID',
+ anthropicAwsApiKey: 'API Key',
+ anthropicAwsApiKeyHint: 'Anthropic-issued AWS API key (prefix AEAA), sent via x-api-key header. After paste, verify the last 4 chars below to catch 5/S or 0/O lookalikes.',
+ anthropicAwsApiKeyRequired: 'Please enter the API Key',
+ anthropicAwsApiKeyShow: 'Show',
+ anthropicAwsApiKeyHide: 'Hide',
+ anthropicAwsApiKeyPrefixWarning: 'Prefix should be AEAA',
+ anthropicAwsApiKeyLengthWarning: 'Unusual length (usually ≥100 chars); paste may have lost characters',
+ anthropicAwsWorkspaceId: 'Workspace ID',
+ anthropicAwsWorkspaceIdHint: 'Starts with wrkspc_. Sent via anthropic-workspace-id header (required, otherwise upstream returns 400).',
+ anthropicAwsWorkspaceIdRequired: 'Please enter the Workspace ID',
+ anthropicAwsRegion: 'AWS Region',
+ anthropicAwsRegionHint: 'Base URL is auto-derived as https://aws-external-anthropic.{region}.api.aws',
vertexLabel: 'Vertex',
vertexDesc: 'Service Account',
vertexAnthropicHint: 'Use a Google Cloud Service Account JSON to call Anthropic Claude via Vertex AI. It is recommended to configure model mapping to map client Claude model names to Vertex model IDs.',
diff --git a/frontend/src/i18n/locales/zh.ts b/frontend/src/i18n/locales/zh.ts
index b293ec67c51..429861c53c1 100644
--- a/frontend/src/i18n/locales/zh.ts
+++ b/frontend/src/i18n/locales/zh.ts
@@ -3358,6 +3358,20 @@ export default {
claudeConsole: 'Claude Console',
bedrockLabel: 'AWS Bedrock',
bedrockDesc: 'SigV4 / API Key',
+ anthropicAwsLabel: 'Claude Platform on AWS',
+ anthropicAwsDesc: 'API Key + Workspace ID',
+ anthropicAwsApiKey: 'API Key',
+ anthropicAwsApiKeyHint: 'Anthropic 在 AWS 上颁发的 API Key(以 AEAA 开头),通过 x-api-key 传递。粘贴后请核对下方末 4 位避免 5/S、0/O 视觉混淆',
+ anthropicAwsApiKeyRequired: '请输入 API Key',
+ anthropicAwsApiKeyShow: '显示',
+ anthropicAwsApiKeyHide: '隐藏',
+ anthropicAwsApiKeyPrefixWarning: '前缀应该是 AEAA',
+ anthropicAwsApiKeyLengthWarning: '长度异常(通常 ≥100 字符),可能粘贴丢字',
+ anthropicAwsWorkspaceId: 'Workspace ID',
+ anthropicAwsWorkspaceIdHint: '以 wrkspc_ 开头,由 anthropic-workspace-id 请求头传递(必传,否则上游 400)',
+ anthropicAwsWorkspaceIdRequired: '请输入 Workspace ID',
+ anthropicAwsRegion: 'AWS Region',
+ anthropicAwsRegionHint: 'Base URL 会自动拼接为 https://aws-external-anthropic.{region}.api.aws',
vertexLabel: 'Vertex',
vertexDesc: 'Service Account',
vertexAnthropicHint: '使用 Google Cloud Service Account JSON 通过 Vertex AI 调用 Anthropic Claude。建议配置模型映射,将客户端 Claude 模型名映射到 Vertex 模型 ID。',
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts
index 63b9b14f67b..21391977d9c 100644
--- a/frontend/src/types/index.ts
+++ b/frontend/src/types/index.ts
@@ -668,7 +668,7 @@ export interface UpdateGroupRequest {
// ==================== Account & Proxy Types ====================
export type AccountPlatform = 'anthropic' | 'openai' | 'gemini' | 'antigravity'
-export type AccountType = 'oauth' | 'setup-token' | 'apikey' | 'upstream' | 'bedrock' | 'service_account'
+export type AccountType = 'oauth' | 'setup-token' | 'apikey' | 'upstream' | 'bedrock' | 'anthropic_aws' | 'service_account'
export type OAuthAddMethod = 'oauth' | 'setup-token'
export type ProxyProtocol = 'http' | 'https' | 'socks5' | 'socks5h'
@@ -1102,7 +1102,7 @@ export interface AdminDataAccount {
}
export interface AdminDataImportError {
- kind: 'proxy' | 'account'
+ kind: 'proxy' | 'account' | 'account_skipped'
name?: string
proxy_key?: string
message: string
@@ -1113,6 +1113,7 @@ export interface AdminDataImportResult {
proxy_reused: number
proxy_failed: number
account_created: number
+ account_skipped: number
account_failed: number
errors?: AdminDataImportError[]
}
diff --git "a/\346\211\223\345\214\205\351\203\250\347\275\262.md" "b/\346\211\223\345\214\205\351\203\250\347\275\262.md"
new file mode 100644
index 00000000000..252ee0e6df5
--- /dev/null
+++ "b/\346\211\223\345\214\205\351\203\250\347\275\262.md"
@@ -0,0 +1,47 @@
+
+# 本地运行
+
+```
+sudo -u postgres psql
+
+-- 创建用户
+CREATE USER sub2api WITH PASSWORD '你的密码';
+
+-- 创建数据库,指定所有者
+CREATE DATABASE sub2api OWNER sub2api;
+
+-- 授予所有权限
+GRANT ALL PRIVILEGES ON DATABASE sub2api TO sub2api;
+
+-- 退出
+\q
+```
+
+
+cd backend
+go mod download
+go run ./cmd/server/
+
+cd frontend
+pnpm run dev
+
+
+# 进入前端目录,安装依赖
+cd frontend
+pnpm install
+
+# 构建前端,产物自动输出到 backend/internal/web/dist/
+pnpm run build
+
+cd ..
+
+cd backend
+$env:CGO_ENABLED="0"; $env:GOOS="linux"; $env:GOARCH="amd64"
+go build -tags embed -ldflags="-s -w" -trimpath -o bin/sub2api ./cmd/server
+
+
+
+# 清理环境变量
+Remove-Item Env:GOOS; Remove-Item Env:GOARCH; Remove-Item Env:CGO_ENABLED
+
+
From 3cd6bcca818d4738711befbbdd016b07a18519b7 Mon Sep 17 00:00:00 2001
From: lanlingxiawu <374397721@qq.com>
Date: Wed, 27 May 2026 18:12:14 +0800
Subject: [PATCH 2/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E6=A8=A1=E5=9E=8B?=
=?UTF-8?q?=E6=98=A0=E5=B0=84=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
修改模型映射功能
---
backend/internal/handler/dto/mappers.go | 1 +
backend/internal/handler/dto/types.go | 1 +
backend/internal/handler/gateway_handler.go | 6 +-
.../gateway_handler_chat_completions.go | 3 +-
.../handler/gateway_handler_responses.go | 3 +-
.../handler/openai_chat_completions.go | 9 +-
.../handler/openai_gateway_handler.go | 9 +-
.../handler/openai_gateway_handler_test.go | 1 -
backend/internal/handler/openai_images.go | 3 +-
.../api_key_repo_last_used_unit_test.go | 2 +-
backend/internal/repository/group_repo.go | 2 +
backend/internal/service/account.go | 73 ++++++++---
.../internal/service/account_wildcard_test.go | 30 +++++
backend/internal/service/admin_service.go | 12 +-
.../internal/service/api_key_auth_cache.go | 1 +
.../service/api_key_auth_cache_impl.go | 4 +-
.../service/api_key_service_cache_test.go | 8 ++
backend/internal/service/channel_service.go | 118 ++++++++++++++---
.../internal/service/channel_service_test.go | 35 ++++-
backend/internal/service/group.go | 73 ++++++++++-
backend/internal/service/group_test.go | 122 ++++++++++++++++++
.../openai_gateway_record_usage_test.go | 1 -
.../openai_ws_protocol_forward_test.go | 1 -
frontend/src/types/index.ts | 1 +
24 files changed, 461 insertions(+), 58 deletions(-)
diff --git a/backend/internal/handler/dto/mappers.go b/backend/internal/handler/dto/mappers.go
index 2c71be9d492..19afbfa3d67 100644
--- a/backend/internal/handler/dto/mappers.go
+++ b/backend/internal/handler/dto/mappers.go
@@ -144,6 +144,7 @@ func GroupFromServiceAdmin(g *service.Group) *AdminGroup {
Group: groupFromServiceBase(g),
ModelRouting: g.ModelRouting,
ModelRoutingEnabled: g.ModelRoutingEnabled,
+ ModelMapping: g.ModelMapping,
MCPXMLInject: g.MCPXMLInject,
DefaultMappedModel: g.DefaultMappedModel,
MessagesDispatchModelConfig: g.MessagesDispatchModelConfig,
diff --git a/backend/internal/handler/dto/types.go b/backend/internal/handler/dto/types.go
index 318283758b3..be41a7c04c6 100644
--- a/backend/internal/handler/dto/types.go
+++ b/backend/internal/handler/dto/types.go
@@ -131,6 +131,7 @@ type AdminGroup struct {
// 模型路由配置(仅 anthropic 平台使用)
ModelRouting map[string][]int64 `json:"model_routing"`
ModelRoutingEnabled bool `json:"model_routing_enabled"`
+ ModelMapping map[string]string `json:"model_mapping"`
// MCP XML 协议注入(仅 antigravity 平台使用)
MCPXMLInject bool `json:"mcp_xml_inject"`
diff --git a/backend/internal/handler/gateway_handler.go b/backend/internal/handler/gateway_handler.go
index 570f283cb6a..c4320b033c3 100644
--- a/backend/internal/handler/gateway_handler.go
+++ b/backend/internal/handler/gateway_handler.go
@@ -159,12 +159,14 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
return
}
reqModel := parsedReq.Model
+ clientReqModel := reqModel
reqStream := parsedReq.Stream
// xiugai 修改自动映射功能
// 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
body = service.ReplaceModelInBody(body, mapped)
+ parsedReq.Body = body // 更新解析后的请求体,确保后续处理使用映射后的模型名
parsedReq.Model = mapped
reqModel = mapped
}
@@ -533,7 +535,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
RequestPayloadHash: requestPayloadHash,
ForceCacheBilling: fs.ForceCacheBilling,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFields(reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
}); err != nil {
logger.L().With(
zap.String("component", "handler.gateway.messages"),
@@ -926,7 +928,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
RequestPayloadHash: requestPayloadHash,
ForceCacheBilling: fs.ForceCacheBilling,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFields(reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
}); err != nil {
logger.L().With(
zap.String("component", "handler.gateway.messages"),
diff --git a/backend/internal/handler/gateway_handler_chat_completions.go b/backend/internal/handler/gateway_handler_chat_completions.go
index b6eca3e1d3b..50ed3c8826d 100644
--- a/backend/internal/handler/gateway_handler_chat_completions.go
+++ b/backend/internal/handler/gateway_handler_chat_completions.go
@@ -75,6 +75,7 @@ func (h *GatewayHandler) ChatCompletions(c *gin.Context) {
return
}
reqModel := modelResult.String()
+ clientReqModel := reqModel
// xiugai 修改自动映射功能
// 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
@@ -313,7 +314,7 @@ func (h *GatewayHandler) ChatCompletions(c *gin.Context) {
IPAddress: clientIP,
RequestPayloadHash: requestPayloadHash,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFields(reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
}); err != nil {
reqLog.Error("gateway.cc.record_usage_failed",
zap.Int64("account_id", account.ID),
diff --git a/backend/internal/handler/gateway_handler_responses.go b/backend/internal/handler/gateway_handler_responses.go
index bd6a672701b..d722e905023 100644
--- a/backend/internal/handler/gateway_handler_responses.go
+++ b/backend/internal/handler/gateway_handler_responses.go
@@ -75,6 +75,7 @@ func (h *GatewayHandler) Responses(c *gin.Context) {
return
}
reqModel := modelResult.String()
+ clientReqModel := reqModel
// xiugai 修改自动映射功能
// 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
@@ -288,7 +289,7 @@ func (h *GatewayHandler) Responses(c *gin.Context) {
IPAddress: clientIP,
RequestPayloadHash: requestPayloadHash,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFields(reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
}); err != nil {
reqLog.Error("gateway.responses.record_usage_failed",
zap.Int64("account_id", account.ID),
diff --git a/backend/internal/handler/openai_chat_completions.go b/backend/internal/handler/openai_chat_completions.go
index 4d523dbac60..5f8d095162c 100644
--- a/backend/internal/handler/openai_chat_completions.go
+++ b/backend/internal/handler/openai_chat_completions.go
@@ -74,6 +74,13 @@ func (h *OpenAIGatewayHandler) ChatCompletions(c *gin.Context) {
return
}
reqModel := modelResult.String()
+ clientReqModel := reqModel
+
+ if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
+ body = service.ReplaceModelInBody(body, mapped)
+ reqModel = mapped
+ }
+
reqStream := gjson.GetBytes(body, "stream").Bool()
reqLog = reqLog.With(zap.String("model", reqModel), zap.Bool("stream", reqStream))
@@ -285,7 +292,7 @@ func (h *OpenAIGatewayHandler) ChatCompletions(c *gin.Context) {
UserAgent: userAgent,
IPAddress: clientIP,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFields(reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
}); err != nil {
logger.L().With(
zap.String("component", "handler.openai_gateway.chat_completions"),
diff --git a/backend/internal/handler/openai_gateway_handler.go b/backend/internal/handler/openai_gateway_handler.go
index 13818f06922..21e16c3b895 100644
--- a/backend/internal/handler/openai_gateway_handler.go
+++ b/backend/internal/handler/openai_gateway_handler.go
@@ -159,6 +159,7 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
return
}
reqModel := modelResult.String()
+ clientReqModel := reqModel
streamResult := gjson.GetBytes(body, "stream")
if streamResult.Exists() && streamResult.Type != gjson.True && streamResult.Type != gjson.False {
@@ -458,7 +459,7 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
IPAddress: clientIP,
RequestPayloadHash: requestPayloadHash,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFields(reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
}); err != nil {
logger.L().With(
zap.String("component", "handler.openai_gateway.responses"),
@@ -621,6 +622,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
return
}
reqModel := modelResult.String()
+ clientReqModel := reqModel
// xiugai 修改自动映射功能
// 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
@@ -851,7 +853,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
IPAddress: clientIP,
RequestPayloadHash: requestPayloadHash,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMappingMsg.ToUsageFields(reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMappingMsg.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
}); err != nil {
logger.L().With(
zap.String("component", "handler.openai_gateway.messages"),
@@ -1191,6 +1193,7 @@ func (h *OpenAIGatewayHandler) ResponsesWebSocket(c *gin.Context) {
}
reqModel := strings.TrimSpace(gjson.GetBytes(firstMessage, "model").String())
+ clientReqModel := reqModel
if reqModel == "" {
closeOpenAIClientWS(wsConn, coderws.StatusPolicyViolation, "model is required in first response.create payload")
return
@@ -1425,7 +1428,7 @@ func (h *OpenAIGatewayHandler) ResponsesWebSocket(c *gin.Context) {
IPAddress: clientIP,
RequestPayloadHash: service.HashUsageRequestPayload(firstMessage),
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMappingWS.ToUsageFields(reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMappingWS.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
}); err != nil {
reqLog.Error("openai.websocket_record_usage_failed",
zap.Int64("account_id", account.ID),
diff --git a/backend/internal/handler/openai_gateway_handler_test.go b/backend/internal/handler/openai_gateway_handler_test.go
index da9548eea4d..6bddbce9454 100644
--- a/backend/internal/handler/openai_gateway_handler_test.go
+++ b/backend/internal/handler/openai_gateway_handler_test.go
@@ -1190,7 +1190,6 @@ func runOpenAIResponsesWebSocketUsageLogCase(t *testing.T, tc openAIResponsesWSU
channelSvc,
nil,
nil,
- nil,
)
cache := &concurrencyCacheMock{
diff --git a/backend/internal/handler/openai_images.go b/backend/internal/handler/openai_images.go
index 410436da1cf..14bb65852f2 100644
--- a/backend/internal/handler/openai_images.go
+++ b/backend/internal/handler/openai_images.go
@@ -68,6 +68,7 @@ func (h *OpenAIGatewayHandler) Images(c *gin.Context) {
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error", err.Error())
return
}
+ clientReqModel := parsed.Model
// xiugai 修改自动映射功能
// 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
@@ -322,7 +323,7 @@ func (h *OpenAIGatewayHandler) Images(c *gin.Context) {
IPAddress: clientIP,
RequestPayloadHash: requestPayloadHash,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFields(parsed.Model, upstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, parsed.Model, upstreamModel),
}); err != nil {
logger.L().With(
zap.String("component", "handler.openai_gateway.images"),
diff --git a/backend/internal/repository/api_key_repo_last_used_unit_test.go b/backend/internal/repository/api_key_repo_last_used_unit_test.go
index 2835c678f0a..65fdf73a51f 100644
--- a/backend/internal/repository/api_key_repo_last_used_unit_test.go
+++ b/backend/internal/repository/api_key_repo_last_used_unit_test.go
@@ -30,7 +30,7 @@ func newAPIKeyRepoSQLite(t *testing.T) (*apiKeyRepository, *dbent.Client) {
client := enttest.NewClient(t, enttest.WithOptions(dbent.Driver(drv)))
t.Cleanup(func() { _ = client.Close() })
- // xiugai sql ֶΪ nil loadGroupModelMappingSQL ָ panic
+ // xiugai 修复 sql 字段为 nil 导致 loadGroupModelMappingSQL 空指针 panic
return &apiKeyRepository{client: client, sql: db}, client // end
}
diff --git a/backend/internal/repository/group_repo.go b/backend/internal/repository/group_repo.go
index d2cd10609aa..126b4a18fdf 100644
--- a/backend/internal/repository/group_repo.go
+++ b/backend/internal/repository/group_repo.go
@@ -85,6 +85,7 @@ func (r *groupRepository) Create(ctx context.Context, groupIn *service.Group) er
// xiugai 修改自动映射功能
if setErr := r.setGroupModelMapping(ctx, groupIn.ID, groupIn.ModelMapping); setErr != nil {
logger.LegacyPrintf("repository.group", "[ModelMapping] set failed on create: group=%d err=%v", groupIn.ID, setErr)
+ return fmt.Errorf("set group model mapping: %w", setErr)
}
// xiugai end
if err := enqueueSchedulerOutbox(ctx, r.sql, service.SchedulerOutboxEventGroupChanged, nil, &groupIn.ID, nil); err != nil {
@@ -305,6 +306,7 @@ func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) er
// xiugai 修改自动映射功能
if setErr := r.setGroupModelMapping(ctx, groupIn.ID, groupIn.ModelMapping); setErr != nil {
logger.LegacyPrintf("repository.group", "[ModelMapping] set failed on update: group=%d err=%v", groupIn.ID, setErr)
+ return fmt.Errorf("set group model mapping: %w", setErr)
}
// xiugai end
if err := enqueueSchedulerOutbox(ctx, r.sql, service.SchedulerOutboxEventGroupChanged, nil, &groupIn.ID, nil); err != nil {
diff --git a/backend/internal/service/account.go b/backend/internal/service/account.go
index e4f73e7837d..4ad77a92b02 100644
--- a/backend/internal/service/account.go
+++ b/backend/internal/service/account.go
@@ -444,28 +444,23 @@ func stringMappingFromRaw(raw any) map[string]string {
}
func (a *Account) GetModelMapping() map[string]string {
- credentialsPtr := mapPtr(a.Credentials)
- rawMapping, _ := a.Credentials["model_mapping"].(map[string]any)
- rawPtr := mapPtr(rawMapping)
- rawLen := len(rawMapping)
- rawSig := uint64(0)
- rawSigReady := false
+ credentialsPtr := mapPointer(a.Credentials)
+ var rawMapping any
+ if a.Credentials != nil {
+ rawMapping = a.Credentials["model_mapping"]
+ }
+ rawPtr, rawLen, rawSig := modelMappingRawState(rawMapping)
if a.modelMappingCacheReady &&
a.modelMappingCacheCredentialsPtr == credentialsPtr &&
a.modelMappingCacheRawPtr == rawPtr &&
a.modelMappingCacheRawLen == rawLen {
- rawSig = modelMappingSignature(rawMapping)
- rawSigReady = true
if a.modelMappingCacheRawSig == rawSig {
return a.modelMappingCache
}
}
- mapping := a.resolveModelMapping(rawMapping)
- if !rawSigReady {
- rawSig = modelMappingSignature(rawMapping)
- }
+ mapping := a.resolveModelMapping(stringMappingFromRaw(rawMapping))
a.modelMappingCache = mapping
a.modelMappingCacheReady = true
@@ -476,7 +471,7 @@ func (a *Account) GetModelMapping() map[string]string {
return mapping
}
-func (a *Account) resolveModelMapping(rawMapping map[string]any) map[string]string {
+func (a *Account) resolveModelMapping(rawMapping map[string]string) map[string]string {
if a.Credentials == nil {
// Antigravity 平台使用默认映射
if a.Platform == domain.PlatformAntigravity {
@@ -495,8 +490,8 @@ func (a *Account) resolveModelMapping(rawMapping map[string]any) map[string]stri
result := make(map[string]string)
for k, v := range rawMapping {
- if s, ok := v.(string); ok {
- result[k] = s
+ if v != "" {
+ result[k] = v
}
}
if len(result) > 0 {
@@ -517,14 +512,29 @@ func (a *Account) resolveModelMapping(rawMapping map[string]any) map[string]stri
return nil
}
-func mapPtr(m map[string]any) uintptr {
- if m == nil {
+func mapPointer(raw any) uintptr {
+ if raw == nil {
+ return 0
+ }
+ v := reflect.ValueOf(raw)
+ if v.Kind() != reflect.Map || v.IsNil() {
return 0
}
- return reflect.ValueOf(m).Pointer()
+ return v.Pointer()
+}
+
+func modelMappingRawState(raw any) (uintptr, int, uint64) {
+ switch mapping := raw.(type) {
+ case map[string]any:
+ return mapPointer(mapping), len(mapping), modelMappingAnySignature(mapping)
+ case map[string]string:
+ return mapPointer(mapping), len(mapping), modelMappingStringSignature(mapping)
+ default:
+ return 0, 0, 0
+ }
}
-func modelMappingSignature(rawMapping map[string]any) uint64 {
+func modelMappingAnySignature(rawMapping map[string]any) uint64 {
if len(rawMapping) == 0 {
return 0
}
@@ -548,6 +558,26 @@ func modelMappingSignature(rawMapping map[string]any) uint64 {
return h.Sum64()
}
+func modelMappingStringSignature(rawMapping map[string]string) uint64 {
+ if len(rawMapping) == 0 {
+ return 0
+ }
+ keys := make([]string, 0, len(rawMapping))
+ for k := range rawMapping {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+
+ h := fnv.New64a()
+ for _, k := range keys {
+ _, _ = h.Write([]byte(k))
+ _, _ = h.Write([]byte{0})
+ _, _ = h.Write([]byte(rawMapping[k]))
+ _, _ = h.Write([]byte{0xff})
+ }
+ return h.Sum64()
+}
+
func ensureAntigravityDefaultPassthrough(mapping map[string]string, model string) {
if mapping == nil || model == "" {
return
@@ -602,7 +632,7 @@ func resolveRequestedModelInMapping(mapping map[string]string, requestedModel st
if requestedModel == "" {
return "", false
}
- if mappedModel, exists := mapping[requestedModel]; exists {
+ if mappedModel, exists := mapping[requestedModel]; exists && mappedModel != "" {
return mappedModel, true
}
return matchWildcardMappingResult(mapping, requestedModel)
@@ -802,6 +832,9 @@ func matchWildcardMappingResult(mapping map[string]string, requestedModel string
var matches []patternMatch
for pattern, target := range mapping {
+ if target == "" {
+ continue
+ }
if matchWildcard(pattern, requestedModel) {
matches = append(matches, patternMatch{pattern, target})
}
diff --git a/backend/internal/service/account_wildcard_test.go b/backend/internal/service/account_wildcard_test.go
index d903b940a51..b139780976c 100644
--- a/backend/internal/service/account_wildcard_test.go
+++ b/backend/internal/service/account_wildcard_test.go
@@ -4,6 +4,8 @@ package service
import (
"testing"
+
+ "github.com/stretchr/testify/require"
)
func TestMatchWildcard(t *testing.T) {
@@ -525,3 +527,31 @@ func TestAccountGetModelMapping_CacheInvalidatesOnInPlaceValueChange(t *testing.
t.Fatalf("expected cache invalidated after in-place value change, got: %v", second)
}
}
+
+func TestAccountGetModelMapping_AcceptsStringMap(t *testing.T) {
+ account := &Account{
+ Credentials: map[string]any{
+ "model_mapping": map[string]string{
+ "gpt-5": "gpt-5.4",
+ },
+ },
+ }
+
+ require.Equal(t, "gpt-5.4", account.GetMappedModel("gpt-5"))
+}
+
+func TestAccountGetMappedModel_EmptyTargetSkipped(t *testing.T) {
+ account := &Account{
+ Credentials: map[string]any{
+ "model_mapping": map[string]any{
+ "gpt-5": "",
+ "gpt-*": "gpt-5.4",
+ "bad-*": "",
+ "other": "other-upstream",
+ },
+ },
+ }
+
+ require.Equal(t, "gpt-5.4", account.GetMappedModel("gpt-5"))
+ require.Equal(t, "bad-model", account.GetMappedModel("bad-model"))
+}
diff --git a/backend/internal/service/admin_service.go b/backend/internal/service/admin_service.go
index 72c95d00adc..eff363d6c29 100644
--- a/backend/internal/service/admin_service.go
+++ b/backend/internal/service/admin_service.go
@@ -1641,6 +1641,10 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
if input.MCPXMLInject != nil {
mcpXMLInject = *input.MCPXMLInject
}
+ modelMapping, err := NormalizeGroupModelMapping(input.ModelMapping)
+ if err != nil {
+ return nil, err
+ }
// 如果指定了复制账号的源分组,先获取账号 ID 列表
var accountIDsToCopy []int64
@@ -1704,7 +1708,7 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
MessagesDispatchModelConfig: normalizeOpenAIMessagesDispatchModelConfig(input.MessagesDispatchModelConfig),
RPMLimit: input.RPMLimit,
// xiugai 修改自动映射功能
- ModelMapping: input.ModelMapping,
+ ModelMapping: modelMapping,
// xiugai end
}
sanitizeGroupMessagesDispatchFields(group)
@@ -1957,7 +1961,11 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd
}
// xiugai 修改自动映射功能
if input.ModelMapping != nil {
- group.ModelMapping = input.ModelMapping
+ modelMapping, err := NormalizeGroupModelMapping(input.ModelMapping)
+ if err != nil {
+ return nil, err
+ }
+ group.ModelMapping = modelMapping
}
// xiugai end
sanitizeGroupMessagesDispatchFields(group)
diff --git a/backend/internal/service/api_key_auth_cache.go b/backend/internal/service/api_key_auth_cache.go
index 3553a18abe5..77f8d93f4bd 100644
--- a/backend/internal/service/api_key_auth_cache.go
+++ b/backend/internal/service/api_key_auth_cache.go
@@ -78,6 +78,7 @@ type APIKeyAuthGroupSnapshot struct {
// Only anthropic groups use these fields; others may leave them empty.
ModelRouting map[string][]int64 `json:"model_routing,omitempty"`
ModelRoutingEnabled bool `json:"model_routing_enabled"`
+ ModelMapping map[string]string `json:"model_mapping,omitempty"`
MCPXMLInject bool `json:"mcp_xml_inject"`
// 支持的模型系列(仅 antigravity 平台使用)
diff --git a/backend/internal/service/api_key_auth_cache_impl.go b/backend/internal/service/api_key_auth_cache_impl.go
index c752ce28896..316d8cabfd6 100644
--- a/backend/internal/service/api_key_auth_cache_impl.go
+++ b/backend/internal/service/api_key_auth_cache_impl.go
@@ -14,7 +14,7 @@ import (
"github.com/dgraph-io/ristretto"
)
-const apiKeyAuthSnapshotVersion = 10 // v10: reload snapshots for group availability checks
+const apiKeyAuthSnapshotVersion = 11 // v11: include group model_mapping in auth snapshots
type apiKeyAuthCacheConfig struct {
l1Size int
@@ -267,6 +267,7 @@ func (s *APIKeyService) snapshotFromAPIKey(ctx context.Context, apiKey *APIKey)
FallbackGroupIDOnInvalidRequest: apiKey.Group.FallbackGroupIDOnInvalidRequest,
ModelRouting: apiKey.Group.ModelRouting,
ModelRoutingEnabled: apiKey.Group.ModelRoutingEnabled,
+ ModelMapping: apiKey.Group.ModelMapping,
MCPXMLInject: apiKey.Group.MCPXMLInject,
SupportedModelScopes: apiKey.Group.SupportedModelScopes,
AllowMessagesDispatch: apiKey.Group.AllowMessagesDispatch,
@@ -337,6 +338,7 @@ func (s *APIKeyService) snapshotToAPIKey(key string, snapshot *APIKeyAuthSnapsho
FallbackGroupIDOnInvalidRequest: snapshot.Group.FallbackGroupIDOnInvalidRequest,
ModelRouting: snapshot.Group.ModelRouting,
ModelRoutingEnabled: snapshot.Group.ModelRoutingEnabled,
+ ModelMapping: snapshot.Group.ModelMapping,
MCPXMLInject: snapshot.Group.MCPXMLInject,
SupportedModelScopes: snapshot.Group.SupportedModelScopes,
AllowMessagesDispatch: snapshot.Group.AllowMessagesDispatch,
diff --git a/backend/internal/service/api_key_service_cache_test.go b/backend/internal/service/api_key_service_cache_test.go
index eaac9a1c898..9a04fcfb9b2 100644
--- a/backend/internal/service/api_key_service_cache_test.go
+++ b/backend/internal/service/api_key_service_cache_test.go
@@ -211,6 +211,9 @@ func TestAPIKeyService_GetByKey_UsesL2Cache(t *testing.T) {
ModelRouting: map[string][]int64{
"claude-opus-*": {1, 2},
},
+ ModelMapping: map[string]string{
+ "gpt-5": "gpt-5.4",
+ },
},
},
}
@@ -225,6 +228,7 @@ func TestAPIKeyService_GetByKey_UsesL2Cache(t *testing.T) {
require.Equal(t, groupID, apiKey.Group.ID)
require.True(t, apiKey.Group.ModelRoutingEnabled)
require.Equal(t, map[string][]int64{"claude-opus-*": {1, 2}}, apiKey.Group.ModelRouting)
+ require.Equal(t, map[string]string{"gpt-5": "gpt-5.4"}, apiKey.Group.ModelMapping)
}
func TestAPIKeyService_SnapshotRoundTrip_PreservesMessagesDispatchModelConfig(t *testing.T) {
@@ -253,6 +257,9 @@ func TestAPIKeyService_SnapshotRoundTrip_PreservesMessagesDispatchModelConfig(t
RateMultiplier: 1,
AllowMessagesDispatch: true,
DefaultMappedModel: "gpt-5.4",
+ ModelMapping: map[string]string{
+ "gpt-5": "gpt-5.4",
+ },
MessagesDispatchModelConfig: OpenAIMessagesDispatchModelConfig{
OpusMappedModel: "gpt-5.4-nano",
SonnetMappedModel: "gpt-5.3-codex",
@@ -270,6 +277,7 @@ func TestAPIKeyService_SnapshotRoundTrip_PreservesMessagesDispatchModelConfig(t
require.NotNil(t, roundTrip)
require.Equal(t, apiKey.Name, roundTrip.Name)
require.NotNil(t, roundTrip.Group)
+ require.Equal(t, apiKey.Group.ModelMapping, roundTrip.Group.ModelMapping)
require.Equal(t, apiKey.Group.MessagesDispatchModelConfig, roundTrip.Group.MessagesDispatchModelConfig)
}
diff --git a/backend/internal/service/channel_service.go b/backend/internal/service/channel_service.go
index 4bf0147f38c..31f345f840a 100644
--- a/backend/internal/service/channel_service.go
+++ b/backend/internal/service/channel_service.go
@@ -4,6 +4,7 @@ import (
"context"
"fmt"
"log/slog"
+ "sort"
"strings"
"sync/atomic"
"time"
@@ -105,31 +106,59 @@ type ChannelMappingResult struct {
// upstreamModel: 上游实际使用的模型名(ForwardResult.UpstreamModel)。
// 返回空字符串表示无映射。
func (r ChannelMappingResult) BuildModelMappingChain(reqModel, upstreamModel string) string {
- if !r.Mapped {
- if upstreamModel != "" && upstreamModel != reqModel {
- return reqModel + "→" + upstreamModel
- }
- return ""
- }
- if upstreamModel != "" && upstreamModel != r.MappedModel {
- return reqModel + "→" + r.MappedModel + "→" + upstreamModel
- }
- return reqModel + "→" + r.MappedModel
+ channelReqModel := reqModel
+ return r.buildModelMappingChainFromClient(reqModel, channelReqModel, upstreamModel)
}
// ToUsageFields 将渠道映射结果转为使用记录字段
func (r ChannelMappingResult) ToUsageFields(reqModel, upstreamModel string) ChannelUsageFields {
- channelMappedModel := reqModel
+ return r.ToUsageFieldsFromClient(reqModel, reqModel, upstreamModel)
+}
+
+func (r ChannelMappingResult) ToUsageFieldsFromClient(clientReqModel, channelReqModel, upstreamModel string) ChannelUsageFields {
+ channelMappedModel := channelReqModel
if r.Mapped {
channelMappedModel = r.MappedModel
}
return ChannelUsageFields{
ChannelID: r.ChannelID,
- OriginalModel: reqModel,
+ OriginalModel: clientReqModel,
ChannelMappedModel: channelMappedModel,
BillingModelSource: r.BillingModelSource,
- ModelMappingChain: r.BuildModelMappingChain(reqModel, upstreamModel),
+ ModelMappingChain: r.buildModelMappingChainFromClient(clientReqModel, channelReqModel, upstreamModel),
+ }
+}
+
+func (r ChannelMappingResult) buildModelMappingChainFromClient(clientReqModel, channelReqModel, upstreamModel string) string {
+ models := []string{clientReqModel}
+ if channelReqModel != "" && channelReqModel != clientReqModel {
+ models = append(models, channelReqModel)
+ }
+ if r.Mapped {
+ models = append(models, r.MappedModel)
}
+ if upstreamModel != "" {
+ models = append(models, upstreamModel)
+ }
+ return compactModelMappingChain(models...)
+}
+
+func compactModelMappingChain(models ...string) string {
+ cleaned := make([]string, 0, len(models))
+ for _, model := range models {
+ model = strings.TrimSpace(model)
+ if model == "" {
+ continue
+ }
+ if len(cleaned) > 0 && cleaned[len(cleaned)-1] == model {
+ continue
+ }
+ cleaned = append(cleaned, model)
+ }
+ if len(cleaned) < 2 {
+ return ""
+ }
+ return strings.Join(cleaned, "\u2192")
}
const (
@@ -325,10 +354,23 @@ func populateChannelCache(channels []Channel, groupPlatforms map[int64]string) *
expandMappingToCache(cache, ch, gid, platform)
}
}
+ sortChannelWildcardMappings(cache)
return cache
}
+func sortChannelWildcardMappings(cache *channelCache) {
+ for key := range cache.wildcardMappingByGP {
+ wildcards := cache.wildcardMappingByGP[key]
+ sort.SliceStable(wildcards, func(i, j int) bool {
+ if len(wildcards[i].prefix) != len(wildcards[j].prefix) {
+ return len(wildcards[i].prefix) > len(wildcards[j].prefix)
+ }
+ return wildcards[i].prefix < wildcards[j].prefix
+ })
+ }
+}
+
// invalidateCache 使缓存失效,让下次读取时自然重建
// isPlatformPricingMatch 判断定价条目的平台是否匹配分组平台。
@@ -678,6 +720,10 @@ func (s *ChannelService) Create(ctx context.Context, input *CreateChannelInput)
if err := s.checkGroupConflicts(ctx, 0, input.GroupIDs); err != nil {
return nil, err
}
+ modelMapping, err := normalizeChannelModelMapping(input.ModelMapping)
+ if err != nil {
+ return nil, err
+ }
channel := &Channel{
Name: input.Name,
@@ -687,7 +733,7 @@ func (s *ChannelService) Create(ctx context.Context, input *CreateChannelInput)
RestrictModels: input.RestrictModels,
GroupIDs: input.GroupIDs,
ModelPricing: input.ModelPricing,
- ModelMapping: input.ModelMapping,
+ ModelMapping: modelMapping,
Features: input.Features,
FeaturesConfig: input.FeaturesConfig,
ApplyPricingToAccountStats: input.ApplyPricingToAccountStats,
@@ -799,7 +845,11 @@ func (s *ChannelService) applyUpdateInput(ctx context.Context, channel *Channel,
channel.ModelPricing = *input.ModelPricing
}
if input.ModelMapping != nil {
- channel.ModelMapping = input.ModelMapping
+ modelMapping, err := normalizeChannelModelMapping(input.ModelMapping)
+ if err != nil {
+ return err
+ }
+ channel.ModelMapping = modelMapping
}
if input.BillingModelSource != "" {
channel.BillingModelSource = input.BillingModelSource
@@ -935,6 +985,44 @@ func validateNoConflictingModels(pricingList []ChannelModelPricing) error {
return nil
}
+func normalizeChannelModelMapping(mapping map[string]map[string]string) (map[string]map[string]string, error) {
+ if mapping == nil {
+ return nil, nil
+ }
+ normalized := make(map[string]map[string]string, len(mapping))
+ for platform, platformMapping := range mapping {
+ platform = strings.TrimSpace(platform)
+ if platform == "" {
+ return nil, infraerrors.BadRequest("INVALID_MAPPING", "mapping platform cannot be empty")
+ }
+ dstMapping := make(map[string]string, len(platformMapping))
+ for source, target := range platformMapping {
+ source = strings.TrimSpace(source)
+ target = strings.TrimSpace(target)
+ if source == "" {
+ return nil, infraerrors.BadRequest("INVALID_MAPPING", fmt.Sprintf("mapping source cannot be empty in platform '%s'", platform))
+ }
+ if target == "" {
+ return nil, infraerrors.BadRequest("INVALID_MAPPING", fmt.Sprintf("mapping target for '%s' cannot be empty in platform '%s'", source, platform))
+ }
+ if strings.Contains(target, "*") {
+ return nil, infraerrors.BadRequest("INVALID_MAPPING", fmt.Sprintf("mapping target for '%s' cannot contain wildcard in platform '%s'", source, platform))
+ }
+ if count := strings.Count(source, "*"); count > 0 {
+ if count > 1 || !strings.HasSuffix(source, "*") {
+ return nil, infraerrors.BadRequest("INVALID_MAPPING", fmt.Sprintf("mapping wildcard source '%s' must use a single trailing * in platform '%s'", source, platform))
+ }
+ }
+ if _, exists := dstMapping[source]; exists {
+ return nil, infraerrors.BadRequest("INVALID_MAPPING", fmt.Sprintf("duplicate mapping source '%s' in platform '%s'", source, platform))
+ }
+ dstMapping[source] = target
+ }
+ normalized[platform] = dstMapping
+ }
+ return normalized, nil
+}
+
// validateNoConflictingMappings 检查模型映射中是否有冲突的源模式
func validateNoConflictingMappings(mapping map[string]map[string]string) error {
for platform, platformMapping := range mapping {
diff --git a/backend/internal/service/channel_service_test.go b/backend/internal/service/channel_service_test.go
index e737a21125b..9991af74dc7 100644
--- a/backend/internal/service/channel_service_test.go
+++ b/backend/internal/service/channel_service_test.go
@@ -876,10 +876,7 @@ func TestResolveChannelMapping_WildcardFirstMatch(t *testing.T) {
result := svc.ResolveChannelMapping(context.Background(), 10, "claude-sonnet-4")
require.True(t, result.Mapped)
- // map iteration order is non-deterministic, so the first-match depends on
- // insertion order which Go maps don't guarantee; verify that one of the
- // wildcard targets matched
- require.Contains(t, []string{"target1", "target2"}, result.MappedModel)
+ require.Equal(t, "target1", result.MappedModel)
}
func TestResolveChannelMapping_NoMapping(t *testing.T) {
@@ -2246,6 +2243,20 @@ func TestToUsageFields_WithUpstreamDifference(t *testing.T) {
require.Equal(t, "my-alias→claude-sonnet-4→claude-sonnet-4-20250514", fields.ModelMappingChain)
}
+func TestToUsageFieldsFromClient_IncludesGroupMappingStep(t *testing.T) {
+ r := ChannelMappingResult{
+ MappedModel: "upstream-channel",
+ ChannelID: 4,
+ Mapped: true,
+ BillingModelSource: BillingModelSourceChannelMapped,
+ }
+
+ fields := r.ToUsageFieldsFromClient("client-alias", "group-alias", "account-upstream")
+ require.Equal(t, "client-alias", fields.OriginalModel)
+ require.Equal(t, "upstream-channel", fields.ChannelMappedModel)
+ require.Equal(t, "client-alias\u2192group-alias\u2192upstream-channel\u2192account-upstream", fields.ModelMappingChain)
+}
+
// ---------------------------------------------------------------------------
// 11. validatePricingBillingMode (moved from handler tests)
// ---------------------------------------------------------------------------
@@ -2378,6 +2389,22 @@ func TestCreate_MappingConflict(t *testing.T) {
require.Contains(t, err.Error(), "MAPPING_PATTERN_CONFLICT")
}
+func TestCreate_MappingRejectsInvalidTarget(t *testing.T) {
+ repo := &mockChannelRepository{}
+ svc := newTestChannelService(repo)
+
+ _, err := svc.Create(context.Background(), &CreateChannelInput{
+ Name: "test",
+ ModelMapping: map[string]map[string]string{
+ PlatformAnthropic: {
+ "claude-*": "target-*",
+ },
+ },
+ })
+ require.Error(t, err)
+ require.Contains(t, err.Error(), "INVALID_MAPPING")
+}
+
func TestUpdate_MappingConflict(t *testing.T) {
existingChannel := &Channel{
ID: 1,
diff --git a/backend/internal/service/group.go b/backend/internal/service/group.go
index e989d2a23b8..4464746a8ca 100644
--- a/backend/internal/service/group.go
+++ b/backend/internal/service/group.go
@@ -1,14 +1,74 @@
package service
import (
+ "fmt"
"regexp"
"sort"
"strings"
+ "sync"
"time"
"github.com/Wei-Shaw/sub2api/internal/domain"
)
+// groupMappingRegexCache 缓存分组模型映射中已编译的正则表达式(自动添加全串锚定)。
+// key: 去掉 "~" 前缀后的原始正则字符串,value: 编译后的 *regexp.Regexp。
+var groupMappingRegexCache sync.Map
+
+// getGroupMappingRegex 返回对应 rawPattern 的编译正则,自动添加 ^(?:...)$ 全串锚定。
+// 编译结果在进程生命周期内缓存,避免热路径重复 Compile。
+func getGroupMappingRegex(rawPattern string) (*regexp.Regexp, error) {
+ anchored := `^(?:` + rawPattern + `)$`
+ if v, ok := groupMappingRegexCache.Load(anchored); ok {
+ return v.(*regexp.Regexp), nil
+ }
+ re, err := regexp.Compile(anchored)
+ if err != nil {
+ return nil, err
+ }
+ actual, _ := groupMappingRegexCache.LoadOrStore(anchored, re)
+ return actual.(*regexp.Regexp), nil
+}
+
+func NormalizeGroupModelMapping(mapping map[string]string) (map[string]string, error) {
+ if mapping == nil {
+ return nil, nil
+ }
+ normalized := make(map[string]string, len(mapping))
+ for source, target := range mapping {
+ source = strings.TrimSpace(source)
+ target = strings.TrimSpace(target)
+ if source == "" {
+ return nil, fmt.Errorf("model_mapping source cannot be empty")
+ }
+ if target == "" {
+ return nil, fmt.Errorf("model_mapping target for %q cannot be empty", source)
+ }
+ if strings.Contains(target, "*") {
+ return nil, fmt.Errorf("model_mapping target for %q cannot contain wildcard", source)
+ }
+ if strings.HasPrefix(source, "~") {
+ rawPattern := strings.TrimSpace(strings.TrimPrefix(source, "~"))
+ if rawPattern == "" {
+ return nil, fmt.Errorf("model_mapping regex source cannot be empty")
+ }
+ if _, err := getGroupMappingRegex(rawPattern); err != nil {
+ return nil, fmt.Errorf("invalid model_mapping regex %q: %w", source, err)
+ }
+ source = "~" + rawPattern
+ } else if count := strings.Count(source, "*"); count > 0 {
+ if count > 1 || !strings.HasSuffix(source, "*") {
+ return nil, fmt.Errorf("model_mapping wildcard source %q must use a single trailing *", source)
+ }
+ }
+ if _, exists := normalized[source]; exists {
+ return nil, fmt.Errorf("duplicate model_mapping source %q", source)
+ }
+ normalized[source] = target
+ }
+ return normalized, nil
+}
+
type OpenAIMessagesDispatchModelConfig = domain.OpenAIMessagesDispatchModelConfig
type Group struct {
@@ -182,12 +242,14 @@ func matchModelPattern(pattern, model string) bool {
// ResolveGroupMappedModel 查找分组级别的模型映射。
// matched=true 表示命中规则(即使映射结果与原模型名相同)。
// 匹配优先级:精确 > 正则(~ 前缀)> 通配符(* 后缀),同级按 pattern 长度降序取最长匹配。
+// 目标值为空字符串的规则视为未配置,不会命中(避免产生误导性的 "model is required" 错误)。
+// 正则匹配自动添加全串锚定(^(?:...)$),防止子串误匹配,并在进程级缓存编译结果。
func (g *Group) ResolveGroupMappedModel(requestedModel string) (mappedModel string, matched bool) {
if g == nil || len(g.ModelMapping) == 0 || requestedModel == "" {
return requestedModel, false
}
- // 1. 精确匹配
- if target, ok := g.ModelMapping[requestedModel]; ok {
+ // 1. 精确匹配(Bug4: 跳过空目标)
+ if target, ok := g.ModelMapping[requestedModel]; ok && target != "" {
return target, true
}
// 2. 正则匹配(pattern 以 "~" 开头)
@@ -197,6 +259,9 @@ func (g *Group) ResolveGroupMappedModel(requestedModel string) (mappedModel stri
}
var regexCandidates, wildcardCandidates []candidate
for pattern, target := range g.ModelMapping {
+ if target == "" {
+ continue // Bug4: 跳过空目标,不加入候选
+ }
if strings.HasPrefix(pattern, "~") {
regexCandidates = append(regexCandidates, candidate{pattern, target})
} else if strings.HasSuffix(pattern, "*") {
@@ -208,7 +273,8 @@ func (g *Group) ResolveGroupMappedModel(requestedModel string) (mappedModel stri
return len(regexCandidates[i].pattern) > len(regexCandidates[j].pattern)
})
for _, c := range regexCandidates {
- re, err := regexp.Compile(c.pattern[1:]) // 去掉 "~" 前缀
+ // Bug2+3: 使用进程级缓存的全串锚定正则(^(?:...)$),避免子串误匹配和重复编译
+ re, err := getGroupMappingRegex(c.pattern[1:])
if err != nil {
continue
}
@@ -228,4 +294,5 @@ func (g *Group) ResolveGroupMappedModel(requestedModel string) (mappedModel stri
}
return requestedModel, false
}
+
// xiugai end
diff --git a/backend/internal/service/group_test.go b/backend/internal/service/group_test.go
index a0f9672c755..1a4d5a0da43 100644
--- a/backend/internal/service/group_test.go
+++ b/backend/internal/service/group_test.go
@@ -8,6 +8,128 @@ import (
"github.com/stretchr/testify/require"
)
+// --- ResolveGroupMappedModel 测试 ---
+
+func TestResolveGroupMappedModel_ExactMatch(t *testing.T) {
+ g := &Group{ModelMapping: map[string]string{"gpt-4": "gpt-4o"}}
+ model, ok := g.ResolveGroupMappedModel("gpt-4")
+ require.True(t, ok)
+ require.Equal(t, "gpt-4o", model)
+}
+
+func TestResolveGroupMappedModel_NoMatch(t *testing.T) {
+ g := &Group{ModelMapping: map[string]string{"gpt-4": "gpt-4o"}}
+ model, ok := g.ResolveGroupMappedModel("gpt-5")
+ require.False(t, ok)
+ require.Equal(t, "gpt-5", model)
+}
+
+func TestResolveGroupMappedModel_WildcardMatch(t *testing.T) {
+ g := &Group{ModelMapping: map[string]string{"gpt-4*": "gpt-4o"}}
+ model, ok := g.ResolveGroupMappedModel("gpt-4-turbo")
+ require.True(t, ok)
+ require.Equal(t, "gpt-4o", model)
+}
+
+// Bug2: 正则全串锚定——"~4" 不应命中 "gpt-4o",只应命中 "4"
+func TestResolveGroupMappedModel_Bug2_RegexFullMatch(t *testing.T) {
+ g := &Group{ModelMapping: map[string]string{"~4": "target"}}
+
+ // 字符串 "4" 完整匹配正则 "4"
+ model, ok := g.ResolveGroupMappedModel("4")
+ require.True(t, ok)
+ require.Equal(t, "target", model)
+
+ // "gpt-4o" 不应被子串匹配命中
+ _, ok = g.ResolveGroupMappedModel("gpt-4o")
+ require.False(t, ok, "正则 '~4' 不应子串匹配 'gpt-4o'")
+
+ // "gpt-4" 也不应被命中
+ _, ok = g.ResolveGroupMappedModel("gpt-4")
+ require.False(t, ok, "正则 '~4' 不应子串匹配 'gpt-4'")
+}
+
+// Bug2: 用户自行写全串锚定的正则仍然正确工作
+func TestResolveGroupMappedModel_Bug2_RegexFullPatternWorks(t *testing.T) {
+ g := &Group{ModelMapping: map[string]string{"~^gpt-4.*$": "gpt-4o"}}
+ model, ok := g.ResolveGroupMappedModel("gpt-4-turbo")
+ require.True(t, ok)
+ require.Equal(t, "gpt-4o", model)
+
+ _, ok = g.ResolveGroupMappedModel("openai-gpt-4")
+ require.False(t, ok, "^gpt-4.*$ 不应匹配 'openai-gpt-4'")
+}
+
+// Bug3: 多次调用不因重复编译 panic,且结果一致(缓存正确)
+func TestResolveGroupMappedModel_Bug3_RegexCachedConsistency(t *testing.T) {
+ g := &Group{ModelMapping: map[string]string{"~^claude-.*": "claude-3-5-sonnet-20241022"}}
+ for range 10 {
+ model, ok := g.ResolveGroupMappedModel("claude-opus-4")
+ require.True(t, ok)
+ require.Equal(t, "claude-3-5-sonnet-20241022", model)
+ }
+}
+
+// Bug4: 空目标不命中,不产生 "model is required" 的误导性错误
+func TestResolveGroupMappedModel_Bug4_EmptyTargetSkipped(t *testing.T) {
+ g := &Group{ModelMapping: map[string]string{
+ "gpt-4": "", // 空目标:不应命中
+ "gpt-4*": "", // 空通配符目标:不应命中
+ "~^gpt-5": "", // 空正则目标:不应命中
+ "gpt-3": "gpt-3o", // 正常规则作为对照
+ }}
+
+ // 空目标的精确匹配不应命中
+ _, ok := g.ResolveGroupMappedModel("gpt-4")
+ require.False(t, ok, "空目标的精确规则不应命中")
+
+ // 空目标的通配符不应命中
+ _, ok = g.ResolveGroupMappedModel("gpt-4-turbo")
+ require.False(t, ok, "空目标的通配符规则不应命中")
+
+ // 空目标的正则不应命中
+ _, ok = g.ResolveGroupMappedModel("gpt-5")
+ require.False(t, ok, "空目标的正则规则不应命中")
+
+ // 正常规则正常命中
+ model, ok := g.ResolveGroupMappedModel("gpt-3")
+ require.True(t, ok)
+ require.Equal(t, "gpt-3o", model)
+}
+
+// Bug4: 空目标精确匹配不阻断后续通配符匹配
+func TestResolveGroupMappedModel_Bug4_EmptyExactFallsThrough(t *testing.T) {
+ g := &Group{ModelMapping: map[string]string{
+ "gpt-4": "", // 空精确匹配,应跳过
+ "gpt-4*": "gpt-4o", // 通配符应继续生效
+ }}
+ model, ok := g.ResolveGroupMappedModel("gpt-4")
+ require.True(t, ok, "空精确规则跳过后应命中通配符规则")
+ require.Equal(t, "gpt-4o", model)
+}
+
+func TestNormalizeGroupModelMapping_RejectsInvalidRegex(t *testing.T) {
+ _, err := NormalizeGroupModelMapping(map[string]string{
+ "~[": "target",
+ })
+ require.Error(t, err)
+ require.Contains(t, err.Error(), "invalid model_mapping regex")
+}
+
+func TestNormalizeGroupModelMapping_RejectsEmptyAndWildcardTarget(t *testing.T) {
+ _, err := NormalizeGroupModelMapping(map[string]string{
+ "gpt-5": "",
+ })
+ require.Error(t, err)
+ require.Contains(t, err.Error(), "cannot be empty")
+
+ _, err = NormalizeGroupModelMapping(map[string]string{
+ "gpt-*": "target-*",
+ })
+ require.Error(t, err)
+ require.Contains(t, err.Error(), "cannot contain wildcard")
+}
+
// TestGroup_GetImagePrice_1K 测试 1K 尺寸返回正确价格
func TestGroup_GetImagePrice_1K(t *testing.T) {
price := 0.10
diff --git a/backend/internal/service/openai_gateway_record_usage_test.go b/backend/internal/service/openai_gateway_record_usage_test.go
index 01ef24701fb..096f5b1079b 100644
--- a/backend/internal/service/openai_gateway_record_usage_test.go
+++ b/backend/internal/service/openai_gateway_record_usage_test.go
@@ -155,7 +155,6 @@ func newOpenAIRecordUsageServiceForTest(usageRepo UsageLogRepository, userRepo U
nil,
nil,
nil,
- nil,
)
svc.userGroupRateResolver = newUserGroupRateResolver(
rateRepo,
diff --git a/backend/internal/service/openai_ws_protocol_forward_test.go b/backend/internal/service/openai_ws_protocol_forward_test.go
index 47d6e04557b..f3936de1333 100644
--- a/backend/internal/service/openai_ws_protocol_forward_test.go
+++ b/backend/internal/service/openai_ws_protocol_forward_test.go
@@ -619,7 +619,6 @@ func TestNewOpenAIGatewayService_InitializesOpenAIWSResolver(t *testing.T) {
nil,
nil,
nil,
- nil,
)
decision := svc.getOpenAIWSProtocolResolver().Resolve(nil)
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts
index 21391977d9c..88f3e0e651d 100644
--- a/frontend/src/types/index.ts
+++ b/frontend/src/types/index.ts
@@ -533,6 +533,7 @@ export interface AdminGroup extends Group {
// 模型路由配置(仅管理员可见,内部信息)
model_routing: Record | null
model_routing_enabled: boolean
+ model_mapping?: Record
// MCP XML 协议注入(仅 antigravity 平台使用)
mcp_xml_inject: boolean
From 4607b71090f0180360e8af0c4870653344f228f3 Mon Sep 17 00:00:00 2001
From: bigfish9 <93386822+bigfish9@users.noreply.github.com>
Date: Wed, 27 May 2026 18:37:23 +0800
Subject: [PATCH 3/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0aws=E8=B4=A6=E5=8F=B7?=
=?UTF-8?q?=E7=B1=BB=E5=9E=8B=E3=80=81=E8=B4=A6=E5=8F=B7=E5=AF=BC=E5=85=A5?=
=?UTF-8?q?=E5=8E=BB=E9=87=8D?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../internal/handler/admin/account_data.go | 54 +-
.../internal/handler/admin/account_handler.go | 73 +-
.../handler/admin/account_identity_helpers.go | 304 +
frontend/package-lock.json | 7453 +++++++++++++++++
frontend/package.json | 1 +
frontend/pnpm-lock.yaml | 18 +-
frontend/pnpm-workspace.yaml | 3 +
.../account/AccountCapacityCell.vue | 2 +-
.../components/account/AccountUsageCell.vue | 2 +-
9 files changed, 7901 insertions(+), 9 deletions(-)
create mode 100644 backend/internal/handler/admin/account_identity_helpers.go
create mode 100644 frontend/package-lock.json
create mode 100644 frontend/pnpm-workspace.yaml
diff --git a/backend/internal/handler/admin/account_data.go b/backend/internal/handler/admin/account_data.go
index 50beadf68e6..5336b2ef6f7 100644
--- a/backend/internal/handler/admin/account_data.go
+++ b/backend/internal/handler/admin/account_data.go
@@ -71,6 +71,7 @@ type DataImportResult struct {
ProxyReused int `json:"proxy_reused"`
ProxyFailed int `json:"proxy_failed"`
AccountCreated int `json:"account_created"`
+ AccountSkipped int `json:"account_skipped"`
AccountFailed int `json:"account_failed"`
Errors []DataImportError `json:"errors,omitempty"`
}
@@ -274,6 +275,17 @@ func (h *AccountHandler) importData(ctx context.Context, req DataImportRequest)
// 收集需要异步设置隐私的 Antigravity OAuth 账号
var privacyAccounts []*service.Account
+ // 预加载该批次涉及的所有平台账号,构建身份索引,用于跨已存在账号去重。
+ importPlatforms := make([]string, 0, len(dataPayload.Accounts))
+ for _, item := range dataPayload.Accounts {
+ importPlatforms = append(importPlatforms, item.Platform)
+ }
+ accountIndex, indexErr := h.loadAccountIdentityIndex(ctx, importPlatforms)
+ if indexErr != nil {
+ return result, indexErr
+ }
+ seenAccountIdentity := make(map[string]int, len(dataPayload.Accounts))
+
for i := range dataPayload.Accounts {
item := dataPayload.Accounts[i]
if err := validateDataAccount(item); err != nil {
@@ -304,6 +316,36 @@ func (h *AccountHandler) importData(ctx context.Context, req DataImportRequest)
enrichCredentialsFromIDToken(&item)
+ // 去重:批内已见 → 跳过;DB 已存在 → 跳过。
+ identityKeys := buildAccountIdentityKeys(accountIdentityInput{
+ Platform: item.Platform,
+ Type: item.Type,
+ Credentials: item.Credentials,
+ Extra: item.Extra,
+ })
+ if len(identityKeys) > 0 {
+ if dupIndex, ok := firstSeenIdentity(seenAccountIdentity, identityKeys); ok {
+ result.AccountSkipped++
+ result.Errors = append(result.Errors, DataImportError{
+ Kind: "account_skipped",
+ Name: item.Name,
+ Message: fmt.Sprintf("与本次导入第 %d 条重复,已跳过", dupIndex),
+ })
+ continue
+ }
+ if existing := accountIndex.Find(identityKeys); existing != nil {
+ result.AccountSkipped++
+ result.Errors = append(result.Errors, DataImportError{
+ Kind: "account_skipped",
+ Name: item.Name,
+ Message: fmt.Sprintf("账号已存在(id=%d, name=%s),已跳过", existing.ID, existing.Name),
+ })
+ markIdentitySeen(seenAccountIdentity, identityKeys, i+1)
+ continue
+ }
+ markIdentitySeen(seenAccountIdentity, identityKeys, i+1)
+ }
+
accountInput := &service.CreateAccountInput{
Name: item.Name,
Notes: item.Notes,
@@ -331,6 +373,10 @@ func (h *AccountHandler) importData(ctx context.Context, req DataImportRequest)
})
continue
}
+ // 新建账号加入索引,保证同批次后续项可以发现它。
+ if created != nil {
+ accountIndex.Add(*created)
+ }
// 收集 Antigravity OAuth 账号,稍后异步设置隐私
if created.Platform == service.PlatformAntigravity && created.Type == service.AccountTypeOAuth {
privacyAccounts = append(privacyAccounts, created)
@@ -563,7 +609,13 @@ func validateDataAccount(item DataAccount) error {
return errors.New("account credentials is required")
}
switch item.Type {
- case service.AccountTypeOAuth, service.AccountTypeSetupToken, service.AccountTypeAPIKey, service.AccountTypeUpstream:
+ case service.AccountTypeOAuth,
+ service.AccountTypeSetupToken,
+ service.AccountTypeAPIKey,
+ service.AccountTypeUpstream,
+ service.AccountTypeBedrock,
+ service.AccountTypeAnthropicAWS,
+ service.AccountTypeServiceAccount:
default:
return fmt.Errorf("account type is invalid: %s", item.Type)
}
diff --git a/backend/internal/handler/admin/account_handler.go b/backend/internal/handler/admin/account_handler.go
index 282ceede0a1..bc5142082c1 100644
--- a/backend/internal/handler/admin/account_handler.go
+++ b/backend/internal/handler/admin/account_handler.go
@@ -98,7 +98,7 @@ type CreateAccountRequest struct {
Name string `json:"name" binding:"required"`
Notes *string `json:"notes"`
Platform string `json:"platform" binding:"required"`
- Type string `json:"type" binding:"required,oneof=oauth setup-token apikey upstream bedrock service_account"`
+ Type string `json:"type" binding:"required,oneof=oauth setup-token apikey upstream bedrock anthropic_aws service_account"`
Credentials map[string]any `json:"credentials" binding:"required"`
Extra map[string]any `json:"extra"`
ProxyID *int64 `json:"proxy_id"`
@@ -117,7 +117,7 @@ type CreateAccountRequest struct {
type UpdateAccountRequest struct {
Name string `json:"name"`
Notes *string `json:"notes"`
- Type string `json:"type" binding:"omitempty,oneof=oauth setup-token apikey upstream bedrock service_account"`
+ Type string `json:"type" binding:"omitempty,oneof=oauth setup-token apikey upstream bedrock anthropic_aws service_account"`
Credentials map[string]any `json:"credentials"`
Extra map[string]any `json:"extra"`
ProxyID *int64 `json:"proxy_id"`
@@ -533,6 +533,24 @@ func (h *AccountHandler) Create(c *gin.Context) {
var createdAccount *service.Account
result, err := executeAdminIdempotent(c, "admin.accounts.create", req, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) {
+ // 去重检查:根据平台与凭证生成身份键,命中即返回 409。
+ if existing, dupErr := h.findDuplicateAccount(ctx, accountIdentityInput{
+ Platform: req.Platform,
+ Type: req.Type,
+ Credentials: req.Credentials,
+ Extra: req.Extra,
+ }); dupErr != nil {
+ return nil, dupErr
+ } else if existing != nil {
+ return nil, infraerrors.Conflict(
+ "ACCOUNT_DUPLICATE",
+ fmt.Sprintf("账号已存在(id=%d, name=%s)", existing.ID, existing.Name),
+ ).WithMetadata(map[string]string{
+ "existing_id": strconv.FormatInt(existing.ID, 10),
+ "existing_name": existing.Name,
+ })
+ }
+
account, execErr := h.adminService.CreateAccount(ctx, &service.CreateAccountInput{
Name: req.Name,
Notes: req.Notes,
@@ -1214,12 +1232,24 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) {
executeAdminIdempotentJSON(c, "admin.accounts.batch_create", req, service.DefaultWriteIdempotencyTTL(), func(ctx context.Context) (any, error) {
success := 0
failed := 0
+ skipped := 0
results := make([]gin.H, 0, len(req.Accounts))
// 收集需要异步设置隐私的 OAuth 账号
var antigravityPrivacyAccounts []*service.Account
var openaiPrivacyAccounts []*service.Account
+ // 预加载该批次涉及的所有平台账号,构建身份索引用于跨已存在账号去重。
+ platforms := make([]string, 0, len(req.Accounts))
for _, item := range req.Accounts {
+ platforms = append(platforms, item.Platform)
+ }
+ index, indexErr := h.loadAccountIdentityIndex(ctx, platforms)
+ if indexErr != nil {
+ return nil, indexErr
+ }
+ seenIdentity := make(map[string]int, len(req.Accounts))
+
+ for batchIdx, item := range req.Accounts {
if item.RateMultiplier != nil && *item.RateMultiplier < 0 {
failed++
results = append(results, gin.H{
@@ -1233,6 +1263,40 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) {
// base_rpm 输入校验:负值归零,超过 10000 截断
sanitizeExtraBaseRPM(item.Extra)
+ // 去重:先检查批内已见,再检查数据库已存在账号。
+ identityKeys := buildAccountIdentityKeys(accountIdentityInput{
+ Platform: item.Platform,
+ Type: item.Type,
+ Credentials: item.Credentials,
+ Extra: item.Extra,
+ })
+ if len(identityKeys) > 0 {
+ if dupIndex, ok := firstSeenIdentity(seenIdentity, identityKeys); ok {
+ skipped++
+ results = append(results, gin.H{
+ "name": item.Name,
+ "success": false,
+ "skipped": true,
+ "error": fmt.Sprintf("与第 %d 条重复,已跳过", dupIndex),
+ })
+ continue
+ }
+ if existing := index.Find(identityKeys); existing != nil {
+ skipped++
+ results = append(results, gin.H{
+ "name": item.Name,
+ "success": false,
+ "skipped": true,
+ "existing_id": existing.ID,
+ "existing_name": existing.Name,
+ "error": fmt.Sprintf("账号已存在(id=%d, name=%s),已跳过", existing.ID, existing.Name),
+ })
+ markIdentitySeen(seenIdentity, identityKeys, batchIdx+1)
+ continue
+ }
+ markIdentitySeen(seenIdentity, identityKeys, batchIdx+1)
+ }
+
skipCheck := item.ConfirmMixedChannelRisk != nil && *item.ConfirmMixedChannelRisk
account, err := h.adminService.CreateAccount(ctx, &service.CreateAccountInput{
@@ -1260,6 +1324,10 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) {
})
continue
}
+ // 新建账号加入索引,保证同批次后续项可以发现它。
+ if account != nil {
+ index.Add(*account)
+ }
// 收集需要异步设置隐私的 OAuth 账号
if account.Type == service.AccountTypeOAuth {
switch account.Platform {
@@ -1313,6 +1381,7 @@ func (h *AccountHandler) BatchCreate(c *gin.Context) {
return gin.H{
"success": success,
"failed": failed,
+ "skipped": skipped,
"results": results,
}, nil
})
diff --git a/backend/internal/handler/admin/account_identity_helpers.go b/backend/internal/handler/admin/account_identity_helpers.go
new file mode 100644
index 00000000000..00083b5e09d
--- /dev/null
+++ b/backend/internal/handler/admin/account_identity_helpers.go
@@ -0,0 +1,304 @@
+// Package admin: account dedup identity helpers.
+//
+// 账号去重的统一身份键计算。
+// 不同平台/类型用不同的稳定字段做身份依据:
+// - OpenAI OAuth: chatgpt_account_id / chatgpt_user_id / email
+// - Claude OAuth/SetupToken: account_uuid (来自 extra) / access_token 指纹
+// - Gemini OAuth / Antigravity OAuth: email / access_token 指纹
+// - 任意平台的 api_key / upstream / bedrock(api_key) / anthropic_aws: api_key 指纹 + base_url
+// - Gemini service_account: client_email
+// - Bedrock SigV4: aws_access_key_id + region
+//
+// 所有键都带 "platform:type:" 前缀,避免跨平台/跨类型误判。
+package admin
+
+import (
+ "context"
+ "crypto/sha256"
+ "encoding/hex"
+ "strings"
+
+ "github.com/Wei-Shaw/sub2api/internal/service"
+)
+
+// accountIdentityInput 构建身份键所需的最小输入。
+type accountIdentityInput struct {
+ Platform string
+ Type string
+ Credentials map[string]any
+ Extra map[string]any
+}
+
+// accountIdentityIndex 维护 identityKey → 已存在账号的映射,用于在创建/导入时检测重复。
+type accountIdentityIndex struct {
+ byKey map[string]service.Account
+}
+
+func newAccountIdentityIndex() *accountIdentityIndex {
+ return &accountIdentityIndex{byKey: map[string]service.Account{}}
+}
+
+// Add 将一个账号的所有身份键写入索引;同一账号可能产生多个键。
+func (i *accountIdentityIndex) Add(account service.Account) {
+ if i == nil {
+ return
+ }
+ if i.byKey == nil {
+ i.byKey = map[string]service.Account{}
+ }
+ keys := buildAccountIdentityKeysFromAccount(account)
+ for _, key := range keys {
+ i.byKey[key] = account
+ }
+}
+
+// Find 按身份键集合查找已存在账号;任一键命中即返回。
+func (i *accountIdentityIndex) Find(keys []string) *service.Account {
+ if i == nil {
+ return nil
+ }
+ for _, key := range keys {
+ if account, ok := i.byKey[key]; ok {
+ return &account
+ }
+ }
+ return nil
+}
+
+// buildAccountIdentityKeysFromAccount 从已存在的 service.Account 提取身份键。
+func buildAccountIdentityKeysFromAccount(account service.Account) []string {
+ return buildAccountIdentityKeys(accountIdentityInput{
+ Platform: account.Platform,
+ Type: account.Type,
+ Credentials: account.Credentials,
+ Extra: account.Extra,
+ })
+}
+
+// buildAccountIdentityKeys 根据平台/类型/凭证生成稳定身份键。
+//
+// 同一账号可能产生多个键(如 OpenAI OAuth 同时有 account_id / user_id / email)。
+// 任一键命中即认为重复。返回的键集合可能为空,调用方需自行判定(空集表示无法去重)。
+func buildAccountIdentityKeys(input accountIdentityInput) []string {
+ platform := strings.ToLower(strings.TrimSpace(input.Platform))
+ accType := strings.ToLower(strings.TrimSpace(input.Type))
+ if platform == "" || accType == "" {
+ return nil
+ }
+ prefix := platform + ":" + accType + ":"
+ keys := make([]string, 0, 4)
+
+ switch platform {
+ case service.PlatformOpenAI:
+ if accType == service.AccountTypeOAuth {
+ keys = appendOAuthOpenAIKeys(keys, prefix, input.Credentials)
+ } else {
+ keys = appendAPIKeyKeys(keys, prefix, input.Credentials)
+ }
+ case service.PlatformAnthropic:
+ switch accType {
+ case service.AccountTypeOAuth, service.AccountTypeSetupToken:
+ keys = appendOAuthClaudeKeys(keys, prefix, input.Credentials, input.Extra)
+ case service.AccountTypeBedrock:
+ keys = appendBedrockKeys(keys, prefix, input.Credentials)
+ default:
+ // apikey / upstream / anthropic_aws
+ keys = appendAPIKeyKeys(keys, prefix, input.Credentials)
+ }
+ case service.PlatformGemini:
+ switch accType {
+ case service.AccountTypeOAuth:
+ keys = appendOAuthCommonKeys(keys, prefix, input.Credentials)
+ case service.AccountTypeServiceAccount:
+ keys = appendServiceAccountKeys(keys, prefix, input.Credentials)
+ default:
+ keys = appendAPIKeyKeys(keys, prefix, input.Credentials)
+ }
+ case service.PlatformAntigravity:
+ if accType == service.AccountTypeOAuth {
+ keys = appendOAuthCommonKeys(keys, prefix, input.Credentials)
+ } else {
+ keys = appendAPIKeyKeys(keys, prefix, input.Credentials)
+ }
+ default:
+ // 未知平台兜底:按 api_key 指纹 + base_url 处理。
+ keys = appendAPIKeyKeys(keys, prefix, input.Credentials)
+ }
+ return keys
+}
+
+func appendOAuthOpenAIKeys(keys []string, prefix string, credentials map[string]any) []string {
+ accountID := credentialString(credentials, "chatgpt_account_id")
+ userID := credentialString(credentials, "chatgpt_user_id")
+ email := strings.ToLower(credentialString(credentials, "email"))
+ accessToken := credentialString(credentials, "access_token")
+ if accountID != "" {
+ keys = append(keys, prefix+"account:"+accountID)
+ }
+ if userID != "" {
+ keys = append(keys, prefix+"user:"+userID)
+ }
+ if accountID == "" && userID == "" && email != "" {
+ keys = append(keys, prefix+"email:"+email)
+ }
+ if accessToken != "" {
+ keys = append(keys, prefix+"access:"+tokenFingerprint(accessToken))
+ }
+ return keys
+}
+
+func appendOAuthClaudeKeys(keys []string, prefix string, credentials, extra map[string]any) []string {
+ accountUUID := extraString(extra, "account_uuid")
+ orgUUID := extraString(extra, "org_uuid")
+ accessToken := credentialString(credentials, "access_token")
+ if accountUUID != "" {
+ keys = append(keys, prefix+"account_uuid:"+accountUUID)
+ }
+ if orgUUID != "" && accountUUID == "" {
+ // org_uuid 单独不足以唯一定位账号,仅在没有 account_uuid 时作为兜底。
+ keys = append(keys, prefix+"org_uuid:"+orgUUID)
+ }
+ if accessToken != "" {
+ keys = append(keys, prefix+"access:"+tokenFingerprint(accessToken))
+ }
+ return keys
+}
+
+func appendOAuthCommonKeys(keys []string, prefix string, credentials map[string]any) []string {
+ email := strings.ToLower(credentialString(credentials, "email"))
+ accessToken := credentialString(credentials, "access_token")
+ if email != "" {
+ keys = append(keys, prefix+"email:"+email)
+ }
+ if accessToken != "" {
+ keys = append(keys, prefix+"access:"+tokenFingerprint(accessToken))
+ }
+ return keys
+}
+
+func appendAPIKeyKeys(keys []string, prefix string, credentials map[string]any) []string {
+ apiKey := credentialString(credentials, "api_key")
+ if apiKey == "" {
+ // 部分平台/类型字段名差异
+ apiKey = credentialString(credentials, "key")
+ }
+ baseURL := strings.ToLower(credentialString(credentials, "base_url"))
+ if apiKey != "" {
+ suffix := tokenFingerprint(apiKey)
+ if baseURL != "" {
+ suffix = baseURL + "|" + suffix
+ }
+ keys = append(keys, prefix+"apikey:"+suffix)
+ }
+ return keys
+}
+
+func appendBedrockKeys(keys []string, prefix string, credentials map[string]any) []string {
+ authMode := strings.ToLower(credentialString(credentials, "auth_mode"))
+ if authMode == "sigv4" || authMode == "" {
+ akid := credentialString(credentials, "aws_access_key_id")
+ region := strings.ToLower(credentialString(credentials, "aws_region"))
+ if akid != "" {
+ key := prefix + "aws_akid:" + akid
+ if region != "" {
+ key += "|" + region
+ }
+ keys = append(keys, key)
+ return keys
+ }
+ }
+ // 回退到 api_key 模式
+ return appendAPIKeyKeys(keys, prefix, credentials)
+}
+
+func appendServiceAccountKeys(keys []string, prefix string, credentials map[string]any) []string {
+ clientEmail := strings.ToLower(credentialString(credentials, "client_email"))
+ if clientEmail != "" {
+ keys = append(keys, prefix+"client_email:"+clientEmail)
+ }
+ return keys
+}
+
+// credentialString 兼容 service.Account.GetCredential 的取值规则,但接受裸 map。
+func credentialString(credentials map[string]any, key string) string {
+ if credentials == nil {
+ return ""
+ }
+ return strings.TrimSpace(codexStringValue(credentials[key]))
+}
+
+// extraString 取 extra map 中的字符串字段。
+func extraString(extra map[string]any, key string) string {
+ if extra == nil {
+ return ""
+ }
+ return strings.TrimSpace(codexStringValue(extra[key]))
+}
+
+// tokenFingerprint 计算 token 的 SHA256 指纹,避免直接持有/比较 token 明文。
+func tokenFingerprint(token string) string {
+ sum := sha256.Sum256([]byte(strings.TrimSpace(token)))
+ return hex.EncodeToString(sum[:])
+}
+
+// firstSeenIdentity 检查批内是否已有相同身份键,返回首次出现的索引。
+func firstSeenIdentity(seen map[string]int, keys []string) (int, bool) {
+ for _, key := range keys {
+ if index, ok := seen[key]; ok {
+ return index, true
+ }
+ }
+ return 0, false
+}
+
+// markIdentitySeen 记录一组身份键到批内已见集合。
+func markIdentitySeen(seen map[string]int, keys []string, index int) {
+ for _, key := range keys {
+ seen[key] = index
+ }
+}
+
+// loadAccountIdentityIndex 加载现有账号并按指定平台集构建身份索引。
+// 当 platforms 为空时不加载任何数据;调用方应明确传入待去重的平台集合,
+// 利用 (platform) 索引避免全表扫描。
+func (h *AccountHandler) loadAccountIdentityIndex(ctx context.Context, platforms []string) (*accountIdentityIndex, error) {
+ index := newAccountIdentityIndex()
+ seen := make(map[string]struct{}, len(platforms))
+ for _, platform := range platforms {
+ platform = strings.ToLower(strings.TrimSpace(platform))
+ if platform == "" {
+ continue
+ }
+ if _, ok := seen[platform]; ok {
+ continue
+ }
+ seen[platform] = struct{}{}
+ accounts, err := h.listAccountsFiltered(ctx, platform, "", "", "", 0, "", "created_at", "desc")
+ if err != nil {
+ return nil, err
+ }
+ for _, account := range accounts {
+ index.Add(account)
+ }
+ }
+ return index, nil
+}
+
+// findDuplicateAccount 查询数据库中是否已有相同身份的账号。
+// 当输入身份键为空(即无法基于凭证生成身份),返回 (nil, nil),调用方按非重复处理。
+func (h *AccountHandler) findDuplicateAccount(ctx context.Context, input accountIdentityInput) (*service.Account, error) {
+ keys := buildAccountIdentityKeys(input)
+ if len(keys) == 0 {
+ return nil, nil
+ }
+ platform := strings.ToLower(strings.TrimSpace(input.Platform))
+ accounts, err := h.listAccountsFiltered(ctx, platform, "", "", "", 0, "", "created_at", "desc")
+ if err != nil {
+ return nil, err
+ }
+ index := newAccountIdentityIndex()
+ for _, account := range accounts {
+ index.Add(account)
+ }
+ return index.Find(keys), nil
+}
diff --git a/frontend/package-lock.json b/frontend/package-lock.json
new file mode 100644
index 00000000000..68b8e35629a
--- /dev/null
+++ b/frontend/package-lock.json
@@ -0,0 +1,7453 @@
+{
+ "name": "sub2api-frontend",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "sub2api-frontend",
+ "version": "1.0.0",
+ "dependencies": {
+ "@airwallex/components-sdk": "^1.30.2",
+ "@lobehub/icons": "^4.0.2",
+ "@stripe/stripe-js": "^9.0.1",
+ "@tanstack/vue-virtual": "^3.13.23",
+ "@vueuse/core": "^10.7.0",
+ "axios": "^1.16.0",
+ "chart.js": "^4.4.1",
+ "dompurify": "^3.3.1",
+ "driver.js": "^1.4.0",
+ "file-saver": "^2.0.5",
+ "marked": "^17.0.1",
+ "pinia": "^2.1.7",
+ "pnpm": "^11.3.0",
+ "qrcode": "^1.5.4",
+ "vue": "^3.4.0",
+ "vue-chartjs": "^5.3.0",
+ "vue-draggable-plus": "^0.6.1",
+ "vue-i18n": "^9.14.5",
+ "vue-router": "^4.2.5",
+ "xlsx": "^0.18.5"
+ },
+ "devDependencies": {
+ "@types/dompurify": "^3.0.5",
+ "@types/file-saver": "^2.0.7",
+ "@types/mdx": "^2.0.13",
+ "@types/node": "^20.10.5",
+ "@types/qrcode": "^1.5.6",
+ "@typescript-eslint/eslint-plugin": "^7.18.0",
+ "@typescript-eslint/parser": "^7.18.0",
+ "@vitejs/plugin-vue": "^5.2.3",
+ "@vitest/coverage-v8": "^2.1.9",
+ "@vue/test-utils": "^2.4.6",
+ "autoprefixer": "^10.4.16",
+ "eslint": "^8.57.0",
+ "eslint-plugin-vue": "^9.25.0",
+ "jsdom": "^24.1.3",
+ "postcss": "^8.4.32",
+ "tailwindcss": "^3.4.0",
+ "typescript": "~5.6.0",
+ "vite": "^5.0.10",
+ "vite-plugin-checker": "^0.9.1",
+ "vitest": "^2.1.9",
+ "vue-tsc": "^2.2.0"
+ }
+ },
+ "node_modules/@airwallex/airtracker": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@airwallex/airtracker/-/airtracker-3.2.0.tgz",
+ "integrity": "sha512-PKE5N38ajTVg6ph9JzLpWsICNjqLtf/wWudNVU3UPX9SVy2I5s5ITc281sMSD8+LIE6RJoGjGTO+VYP/io5kig==",
+ "license": "ISC"
+ },
+ "node_modules/@airwallex/components-sdk": {
+ "version": "1.32.0",
+ "resolved": "https://registry.npmjs.org/@airwallex/components-sdk/-/components-sdk-1.32.0.tgz",
+ "integrity": "sha512-IRrYOyCIWw/cQcndm/n11zx8b7hj2wYF7TD2YLmds9lXIQoir214JF99i1QJAyMsRR//nIRkNFTB2Ta2/etYGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@airwallex/airtracker": "^3.0.0"
+ }
+ },
+ "node_modules/@alloc/quick-lru": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz",
+ "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.3.0.tgz",
+ "integrity": "sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@ant-design/cssinjs": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@ant-design/cssinjs/-/cssinjs-2.1.2.tgz",
+ "integrity": "sha512-2Hy8BnCEH31xPeSLbhhB2ctCPXE2ZnASdi+KbSeS79BNbUhL9hAEe20SkUk+BR8aKTmqb6+FKFruk7w8z0VoRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.11.1",
+ "@emotion/hash": "^0.8.0",
+ "@emotion/unitless": "^0.7.5",
+ "@rc-component/util": "^1.4.0",
+ "clsx": "^2.1.1",
+ "csstype": "^3.1.3",
+ "stylis": "^4.3.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.0.0",
+ "react-dom": ">=16.0.0"
+ }
+ },
+ "node_modules/@asamuzakjp/css-color": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-3.2.0.tgz",
+ "integrity": "sha512-K1A6z8tS3XsmCMM86xoWdn7Fkdn9m6RSVtocUrJYIwZnFVkng/PvkEoWtOWmP+Scc6saYWHWZYbndEEXxl24jw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/css-calc": "^2.1.3",
+ "@csstools/css-color-parser": "^3.0.9",
+ "@csstools/css-parser-algorithms": "^3.0.4",
+ "@csstools/css-tokenizer": "^3.0.3",
+ "lru-cache": "^10.4.3"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz",
+ "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.29.7",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz",
+ "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz",
+ "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz",
+ "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz",
+ "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz",
+ "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz",
+ "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.29.7"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz",
+ "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz",
+ "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/types": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz",
+ "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.29.7",
+ "@babel/generator": "^7.29.7",
+ "@babel/helper-globals": "^7.29.7",
+ "@babel/parser": "^7.29.7",
+ "@babel/template": "^7.29.7",
+ "@babel/types": "^7.29.7",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.7",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz",
+ "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.29.7",
+ "@babel/helper-validator-identifier": "^7.29.7"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@bcoe/v8-coverage": {
+ "version": "0.2.3",
+ "resolved": "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz",
+ "integrity": "sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@csstools/color-helpers": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-5.1.0.tgz",
+ "integrity": "sha512-S11EXWJyy0Mz5SYvRmY8nJYTFFd1LCNV+7cXyAgQtOOuzb4EsgfqDufL+9esx72/eLhsRdGZwaldu/h+E4t4BA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT-0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@csstools/css-calc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.1.4.tgz",
+ "integrity": "sha512-3N8oaj+0juUw/1H3YwmDDJXCgTB1gKU6Hc/bB502u9zR0q2vd786XJH9QfrKIEgFlZmhZiq6epXl4rHqhzsIgQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-color-parser": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.1.0.tgz",
+ "integrity": "sha512-nbtKwh3a6xNVIp/VRuXV64yTKnb1IjTAEEh3irzS+HkKjAOYLTGNb9pmVNntZ8iVBHcWDA2Dof0QtPgFI1BaTA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "@csstools/color-helpers": "^5.1.0",
+ "@csstools/css-calc": "^2.1.4"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-parser-algorithms": "^3.0.5",
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-parser-algorithms": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.5.tgz",
+ "integrity": "sha512-DaDeUkXZKjdGhgYaHNJTV9pV7Y9B3b644jCLs9Upc3VeNGg6LWARAT6O+Q+/COo+2gg/bM5rhpMAtf70WqfBdQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "@csstools/css-tokenizer": "^3.0.4"
+ }
+ },
+ "node_modules/@csstools/css-tokenizer": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.4.tgz",
+ "integrity": "sha512-Vd/9EVDiu6PPJt9yAh6roZP6El1xHrdvIVGjyBsHR0RYwNHgL7FJPyIIW4fANJNG6FtyZfvlRPpFI4ZM/lubvw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/csstools"
+ },
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/csstools"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@emotion/babel-plugin": {
+ "version": "11.13.5",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
+ "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/serialize": "^1.3.3",
+ "babel-plugin-macros": "^3.1.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/babel-plugin/node_modules/@emotion/hash": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/babel-plugin/node_modules/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/cache": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
+ "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/sheet": "^1.4.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/cache/node_modules/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/css": {
+ "version": "11.13.5",
+ "resolved": "https://registry.npmjs.org/@emotion/css/-/css-11.13.5.tgz",
+ "integrity": "sha512-wQdD0Xhkn3Qy2VNcIzbLP9MR8TafI0MJb7BEAXKp+w4+XqErksWR4OXomuDzPsN4InLdGhVe6EYcn2ZIUCpB8w==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/cache": "^11.13.5",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/sheet": "^1.4.0",
+ "@emotion/utils": "^1.4.2"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.8.0.tgz",
+ "integrity": "sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/react": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
+ "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/cache": "^11.14.0",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "hoist-non-react-statics": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/serialize": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
+ "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/unitless": "^0.10.0",
+ "@emotion/utils": "^1.4.2",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@emotion/serialize/node_modules/@emotion/hash": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/serialize/node_modules/@emotion/unitless": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
+ "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/sheet": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
+ "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.7.5",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.7.5.tgz",
+ "integrity": "sha512-OWORNpfjMsSSUBVrRBVGECkhWcULOAJz9ZW8uK9qgxD+87M7jHRcvh/A96XXNhXTLmKcoYSQtBEX7lHMO7YRwg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
+ "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@emotion/utils": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
+ "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/weak-memoize": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
+ "license": "MIT"
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.9.1",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz",
+ "integrity": "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.12.2",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.2.tgz",
+ "integrity": "sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/brace-expansion": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz",
+ "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.13.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz",
+ "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==",
+ "deprecated": "Use @eslint/config-array instead",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.3",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/brace-expansion": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz",
+ "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==",
+ "deprecated": "Use @eslint/object-schema instead",
+ "dev": true,
+ "license": "BSD-3-Clause"
+ },
+ "node_modules/@intlify/core-base": {
+ "version": "9.14.5",
+ "resolved": "https://registry.npmjs.org/@intlify/core-base/-/core-base-9.14.5.tgz",
+ "integrity": "sha512-5ah5FqZG4pOoHjkvs8mjtv+gPKYU0zCISaYNjBNNqYiaITxW8ZtVih3GS/oTOqN8d9/mDLyrjD46GBApNxmlsA==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/message-compiler": "9.14.5",
+ "@intlify/shared": "9.14.5"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/message-compiler": {
+ "version": "9.14.5",
+ "resolved": "https://registry.npmjs.org/@intlify/message-compiler/-/message-compiler-9.14.5.tgz",
+ "integrity": "sha512-IHzgEu61/YIpQV5Pc3aRWScDcnFKWvQA9kigcINcCBXN8mbW+vk9SK+lDxA6STzKQsVJxUPg9ACC52pKKo3SVQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/shared": "9.14.5",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@intlify/shared": {
+ "version": "9.14.5",
+ "resolved": "https://registry.npmjs.org/@intlify/shared/-/shared-9.14.5.tgz",
+ "integrity": "sha512-9gB+E53BYuAEMhbCAxVgG38EZrk59sxBtv3jSizNL2hEWlgjBjAw1AwpLHtNaeda12pe6W20OGEa0TwuMSRbyQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ }
+ },
+ "node_modules/@isaacs/cliui": {
+ "version": "8.0.2",
+ "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
+ "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^5.1.2",
+ "string-width-cjs": "npm:string-width@^4.2.0",
+ "strip-ansi": "^7.0.1",
+ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
+ "wrap-ansi": "^8.1.0",
+ "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/@isaacs/cliui/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/@istanbuljs/schema": {
+ "version": "0.1.6",
+ "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.6.tgz",
+ "integrity": "sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "license": "MIT"
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@kurkle/color": {
+ "version": "0.3.4",
+ "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz",
+ "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==",
+ "license": "MIT"
+ },
+ "node_modules/@lobehub/icons": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@lobehub/icons/-/icons-4.12.0.tgz",
+ "integrity": "sha512-DVH7pVzM6wEvua2LXH+Iv10/cLeBbueggMFBHa8IlfQel5u3I6JzuaNXXxj2qJu5QYjUCNL5LTpSWuh4TnuLGw==",
+ "license": "MIT",
+ "workspaces": [
+ "packages/*"
+ ],
+ "dependencies": {
+ "antd-style": "^4.1.0",
+ "lucide-react": "^0.469.0",
+ "polished": "^4.3.1"
+ },
+ "peerDependencies": {
+ "@lobehub/ui": "^4.3.3",
+ "antd": "^6.1.1",
+ "react": "^19.0.0",
+ "react-dom": "^19.0.0"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@one-ini/wasm": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@one-ini/wasm/-/wasm-0.1.1.tgz",
+ "integrity": "sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@pkgjs/parseargs": {
+ "version": "0.11.0",
+ "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
+ "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/@rc-component/util": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/@rc-component/util/-/util-1.11.1.tgz",
+ "integrity": "sha512-awVlI3ub2vqfqkYxOBc/uQ0efm3jw0wcrhtO/YWLyZfxiKXczKwNbVuhlnyxytDt7H9pbbVQiqr+O6MLATtRYg==",
+ "license": "MIT",
+ "dependencies": {
+ "is-mobile": "^5.0.0",
+ "react-is": "^18.2.0"
+ },
+ "peerDependencies": {
+ "react": ">=18.0.0",
+ "react-dom": ">=18.0.0"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.60.4.tgz",
+ "integrity": "sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.60.4.tgz",
+ "integrity": "sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.60.4.tgz",
+ "integrity": "sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.60.4.tgz",
+ "integrity": "sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.60.4.tgz",
+ "integrity": "sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.60.4.tgz",
+ "integrity": "sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.60.4.tgz",
+ "integrity": "sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.60.4.tgz",
+ "integrity": "sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.60.4.tgz",
+ "integrity": "sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.60.4.tgz",
+ "integrity": "sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.60.4.tgz",
+ "integrity": "sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.60.4.tgz",
+ "integrity": "sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.60.4.tgz",
+ "integrity": "sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.60.4.tgz",
+ "integrity": "sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.60.4.tgz",
+ "integrity": "sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.60.4.tgz",
+ "integrity": "sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.60.4.tgz",
+ "integrity": "sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.60.4.tgz",
+ "integrity": "sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.60.4.tgz",
+ "integrity": "sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.60.4.tgz",
+ "integrity": "sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.60.4.tgz",
+ "integrity": "sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.60.4.tgz",
+ "integrity": "sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.60.4.tgz",
+ "integrity": "sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.60.4.tgz",
+ "integrity": "sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.60.4.tgz",
+ "integrity": "sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@stripe/stripe-js": {
+ "version": "9.7.0",
+ "resolved": "https://registry.npmjs.org/@stripe/stripe-js/-/stripe-js-9.7.0.tgz",
+ "integrity": "sha512-r1ElolvWXM4aYnZZVHvKW3EDL8JcwEuIgTuWxlB5lvC+YsvjkQ0gX35x9d8dTDubX395fViLVqkaolVs1PmIQQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.16"
+ }
+ },
+ "node_modules/@tanstack/virtual-core": {
+ "version": "3.16.0",
+ "resolved": "https://registry.npmjs.org/@tanstack/virtual-core/-/virtual-core-3.16.0.tgz",
+ "integrity": "sha512-Er2N7q3WOiH6y2JLxsxNX+u2/sLqSsL0bxFgDjuiPiA7vKhZRm+IzcS17vRee3GNXr64UsesA5CAp9yTiIYw9A==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/vue-virtual": {
+ "version": "3.13.26",
+ "resolved": "https://registry.npmjs.org/@tanstack/vue-virtual/-/vue-virtual-3.13.26.tgz",
+ "integrity": "sha512-4TmREKi8rKiQC8E2XVEMMgzWbrgHNYolkBgYTXVK1kqXmXRGz6xPWgBq20GUYWUDDhit94+g0ricUQKpZhWRmg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/virtual-core": "3.16.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "vue": "^2.7.0 || ^3.0.0"
+ }
+ },
+ "node_modules/@types/dompurify": {
+ "version": "3.0.5",
+ "resolved": "https://registry.npmjs.org/@types/dompurify/-/dompurify-3.0.5.tgz",
+ "integrity": "sha512-1Wg0g3BtQF7sSb27fJQAKck1HECM6zV1EB66j8JH9i3LCjYabJa0FSdiSgsD5K/RbrsR0SiraKacLB+T8ZVYAg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/trusted-types": "*"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/file-saver": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
+ "integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/mdx": {
+ "version": "2.0.13",
+ "resolved": "https://registry.npmjs.org/@types/mdx/-/mdx-2.0.13.tgz",
+ "integrity": "sha512-+OWZQfAYyio6YkJb3HLxDrvnx6SWWDbC0zVPfBRzUk0/nqoDyf6dNxQi3eArPe8rJ473nobTMQ/8Zk+LxJ+Yuw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/node": {
+ "version": "20.19.41",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.41.tgz",
+ "integrity": "sha512-ECymXOukMnOoVkC2bb1Vc/w/836DXncOg5m8Xj1RH7xSHZJWNYY6Zh7EH477vcnD5egKNNfy2RpNOmuChhFPgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "undici-types": "~6.21.0"
+ }
+ },
+ "node_modules/@types/parse-json": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
+ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
+ "license": "MIT"
+ },
+ "node_modules/@types/qrcode": {
+ "version": "1.5.6",
+ "resolved": "https://registry.npmjs.org/@types/qrcode/-/qrcode-1.5.6.tgz",
+ "integrity": "sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/sortablejs": {
+ "version": "1.15.9",
+ "resolved": "https://registry.npmjs.org/@types/sortablejs/-/sortablejs-1.15.9.tgz",
+ "integrity": "sha512-7HP+rZGE2p886PKV9c9OJzLBI6BBJu1O7lJGYnPyG3fS4/duUCcngkNCjsLwIMV+WMqANe3tt4irrXHSIe68OQ==",
+ "license": "MIT"
+ },
+ "node_modules/@types/trusted-types": {
+ "version": "2.0.7",
+ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz",
+ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==",
+ "devOptional": true,
+ "license": "MIT"
+ },
+ "node_modules/@types/web-bluetooth": {
+ "version": "0.0.20",
+ "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz",
+ "integrity": "sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==",
+ "license": "MIT"
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-7.18.0.tgz",
+ "integrity": "sha512-94EQTWZ40mzBc42ATNIBimBEDltSJ9RQHCC8vc/PDbxi4k8dVwUAv4o98dk50M1zB+JGFxp43FP7f8+FP8R6Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.10.0",
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/type-utils": "7.18.0",
+ "@typescript-eslint/utils": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.3.1",
+ "natural-compare": "^1.4.0",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^7.0.0",
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-7.18.0.tgz",
+ "integrity": "sha512-4Z+L8I2OqhZV8qA132M4wNL30ypZGYOQVBfMgxDH/K5UX0PNqTu1c6za9ST5r9+tavvHiTWmBnKzpCJ/GlVFtg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/typescript-estree": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-7.18.0.tgz",
+ "integrity": "sha512-jjhdIE/FPF2B7Z1uzc6i3oWKbGcHb87Qw7AWj6jmEqNOfDFbJWtjt/XfwCpvNkpGWlcJaog5vTR+VV8+w9JflA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-7.18.0.tgz",
+ "integrity": "sha512-XL0FJXuCLaDuX2sYqZUUSOJ2sG5/i1AAze+axqmLnSkNEVMVYLF+cbwlB2w8D1tinFuSikHmFta+P+HOofrLeA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "7.18.0",
+ "@typescript-eslint/utils": "7.18.0",
+ "debug": "^4.3.4",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-7.18.0.tgz",
+ "integrity": "sha512-iZqi+Ds1y4EDYUtlOOC+aUmxnE9xS/yCigkjA7XpTKV6nCBd3Hp/PRGGmdwnfkV2ThMyYldP1wRpm/id99spTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-7.18.0.tgz",
+ "integrity": "sha512-aP1v/BSPnnyhMHts8cf1qQ6Q1IFwwRvAQGRvBFkWlo3/lH29OXA3Pts+c10nxRxIBrDnoMqzhgdwVe5f2D6OzA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/visitor-keys": "7.18.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "minimatch": "^9.0.4",
+ "semver": "^7.6.0",
+ "ts-api-utils": "^1.3.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-7.18.0.tgz",
+ "integrity": "sha512-kK0/rNa2j74XuHVcoCZxdFBMF+aq/vH83CXAOHieC+2Gis4mF8jJXT5eAfyD3K0sAxtPuwxaIOIOvhwzVDt/kw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "@typescript-eslint/scope-manager": "7.18.0",
+ "@typescript-eslint/types": "7.18.0",
+ "@typescript-eslint/typescript-estree": "7.18.0"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^8.56.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "7.18.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-7.18.0.tgz",
+ "integrity": "sha512-cDF0/Gf81QpY3xYyJKDV14Zwdmid5+uuENhjH2EqFaF0ni+yAyq/LzMaIJdhNJXZI7uLzwIlA+V7oWoyn6Curg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@typescript-eslint/types": "7.18.0",
+ "eslint-visitor-keys": "^3.4.3"
+ },
+ "engines": {
+ "node": "^18.18.0 || >=20.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.1.tgz",
+ "integrity": "sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "5.2.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz",
+ "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0 || ^6.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vitest/coverage-v8": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/coverage-v8/-/coverage-v8-2.1.9.tgz",
+ "integrity": "sha512-Z2cOr0ksM00MpEfyVE8KXIYPEcBFxdbLSs56L8PO0QQMxt/6bDj45uQfxoc96v05KW3clk7vvgP0qfDit9DmfQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@ampproject/remapping": "^2.3.0",
+ "@bcoe/v8-coverage": "^0.2.3",
+ "debug": "^4.3.7",
+ "istanbul-lib-coverage": "^3.2.2",
+ "istanbul-lib-report": "^3.0.1",
+ "istanbul-lib-source-maps": "^5.0.6",
+ "istanbul-reports": "^3.1.7",
+ "magic-string": "^0.30.12",
+ "magicast": "^0.3.5",
+ "std-env": "^3.8.0",
+ "test-exclude": "^7.0.1",
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@vitest/browser": "2.1.9",
+ "vitest": "2.1.9"
+ },
+ "peerDependenciesMeta": {
+ "@vitest/browser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/expect": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
+ "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "2.1.9",
+ "@vitest/utils": "2.1.9",
+ "chai": "^5.1.2",
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz",
+ "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "2.1.9",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.12"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz",
+ "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz",
+ "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "2.1.9",
+ "pathe": "^1.1.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz",
+ "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "2.1.9",
+ "magic-string": "^0.30.12",
+ "pathe": "^1.1.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz",
+ "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^3.0.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz",
+ "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "2.1.9",
+ "loupe": "^3.1.2",
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@volar/language-core": {
+ "version": "2.4.15",
+ "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-2.4.15.tgz",
+ "integrity": "sha512-3VHw+QZU0ZG9IuQmzT68IyN4hZNd9GchGPhbD9+pa8CVv7rnoOZwo7T8weIbrRmihqy3ATpdfXFnqRrfPVK6CA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/source-map": "2.4.15"
+ }
+ },
+ "node_modules/@volar/source-map": {
+ "version": "2.4.15",
+ "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-2.4.15.tgz",
+ "integrity": "sha512-CPbMWlUN6hVZJYGcU/GSoHu4EnCHiLaXI9n8c9la6RaI9W5JHX+NqG+GSQcB0JdC2FIBLdZJwGsfKyBB71VlTg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@volar/typescript": {
+ "version": "2.4.15",
+ "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-2.4.15.tgz",
+ "integrity": "sha512-2aZ8i0cqPGjXb4BhkMsPYDkkuc2ZQ6yOpqwAuNwUoncELqoy5fRgOQtLR9gB0g902iS0NAkvpIzs27geVyVdPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/language-core": "2.4.15",
+ "path-browserify": "^1.0.1",
+ "vscode-uri": "^3.0.8"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.34.tgz",
+ "integrity": "sha512-s9cLyK5mLcvZ4Agva5QgRsQyLKvts9WbU9DB6NqiZkkGEdwmcEiylj5Jbwkp680drF/NNCV8OlAJSe+yMLxaJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.3",
+ "@vue/shared": "3.5.34",
+ "entities": "^7.0.1",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-core/node_modules/entities": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz",
+ "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/@vue/compiler-core/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT"
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.34.tgz",
+ "integrity": "sha512-EbF/T++k0e2MMZlJsBhzK8Sgwt0HcIPOhzn1CTB/lv6sQcyk+OWf8YeiLxZp3ro7MbbLcAfAJ6sEvjFWuNgUCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-core": "3.5.34",
+ "@vue/shared": "3.5.34"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.34.tgz",
+ "integrity": "sha512-D/ihr6uZeIt6r+pVZf46RWT1fAsLFMbUP7k8G1VkiiWexriED9GrX3echHd4Abbt17zjlfiFJ8z7a3BxZOPNjg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.29.3",
+ "@vue/compiler-core": "3.5.34",
+ "@vue/compiler-dom": "3.5.34",
+ "@vue/compiler-ssr": "3.5.34",
+ "@vue/shared": "3.5.34",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.21",
+ "postcss": "^8.5.14",
+ "source-map-js": "^1.2.1"
+ }
+ },
+ "node_modules/@vue/compiler-sfc/node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==",
+ "license": "MIT"
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.34.tgz",
+ "integrity": "sha512-cDtTHKibkThKGHH1SP+WdccquNRYQDFH6rRjQCqT9G2ltFAfoR5pUftpab/z+aM5mW9HLLVQW7hfKKQe/1GBeQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.34",
+ "@vue/shared": "3.5.34"
+ }
+ },
+ "node_modules/@vue/compiler-vue2": {
+ "version": "2.7.16",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-vue2/-/compiler-vue2-2.7.16.tgz",
+ "integrity": "sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "de-indent": "^1.0.2",
+ "he": "^1.2.0"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "6.6.4",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.4.tgz",
+ "integrity": "sha512-sGhTPMuXqZ1rVOk32RylztWkfXTRhuS7vgAKv0zjqk8gbsHkJ7xfFf+jbySxt7tWObEJwyKaHMikV/WGDiQm8g==",
+ "license": "MIT"
+ },
+ "node_modules/@vue/language-core": {
+ "version": "2.2.12",
+ "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-2.2.12.tgz",
+ "integrity": "sha512-IsGljWbKGU1MZpBPN+BvPAdr55YPkj2nB/TBNGNC32Vy2qLG25DYu/NBN2vNtZqdRbTRjaoYrahLrToim2NanA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/language-core": "2.4.15",
+ "@vue/compiler-dom": "^3.5.0",
+ "@vue/compiler-vue2": "^2.7.16",
+ "@vue/shared": "^3.5.0",
+ "alien-signals": "^1.0.3",
+ "minimatch": "^9.0.3",
+ "muggle-string": "^0.4.1",
+ "path-browserify": "^1.0.1"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.34.tgz",
+ "integrity": "sha512-y9XDjCEuBp+98k+UL5dbYkh57AHU4o6cxZedOPXw3bmrZZYLQsVHguGurq7hVrPCSrQtrnz1f9dssyFr+dMXfQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/shared": "3.5.34"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.34.tgz",
+ "integrity": "sha512-mKeBYvu8tcMSLhypAHBmriUFfWXKTCF/23Z4jiCoYK3UtWepkliViNLuR90V9XOyD62mUxs9p1jsrpK3CCGIzw==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.34",
+ "@vue/shared": "3.5.34"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.34.tgz",
+ "integrity": "sha512-e8kZzERmCwUnBRVsgSQlAfrfU2rGoy0FFKPBXSlfEjc/O3KfA7QP0t1/2ZylrbchjmIKB4dPTd07A6WPr0eOrg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/reactivity": "3.5.34",
+ "@vue/runtime-core": "3.5.34",
+ "@vue/shared": "3.5.34",
+ "csstype": "^3.2.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.34.tgz",
+ "integrity": "sha512-nHxmJoTrKsmrkbILRhkC9gY1G3moZbJTqCzDd7DOOzG5KH9oeJ0Unqrff5f9v0pW//jES05ZkJcNtfE8JjOIew==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.5.34",
+ "@vue/shared": "3.5.34"
+ },
+ "peerDependencies": {
+ "vue": "3.5.34"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.34.tgz",
+ "integrity": "sha512-24uqU4OIiX29ryC3MeWid/Xf2fa2EFRUVLb77nRhk+UrTVrh/XiGtFAFmJBAtBRbjwNdsPRP+jj/OL27Eg1NDA==",
+ "license": "MIT"
+ },
+ "node_modules/@vue/test-utils": {
+ "version": "2.4.10",
+ "resolved": "https://registry.npmjs.org/@vue/test-utils/-/test-utils-2.4.10.tgz",
+ "integrity": "sha512-SmoZ5EA1kYiAFs9NkYdiFFQF+cSnUwnvlYEbY+DogWQZUiqOm/Y29eSbc5T6yi75SgSF9863SBeXniIEoPajCA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "js-beautify": "^1.14.9",
+ "vue-component-type-helpers": "^3.0.0"
+ },
+ "peerDependencies": {
+ "@vue/compiler-dom": "3.x",
+ "@vue/server-renderer": "3.x",
+ "vue": "3.x"
+ },
+ "peerDependenciesMeta": {
+ "@vue/server-renderer": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vueuse/core": {
+ "version": "10.11.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.11.1.tgz",
+ "integrity": "sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/web-bluetooth": "^0.0.20",
+ "@vueuse/metadata": "10.11.1",
+ "@vueuse/shared": "10.11.1",
+ "vue-demi": ">=0.14.8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/metadata": {
+ "version": "10.11.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.11.1.tgz",
+ "integrity": "sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/@vueuse/shared": {
+ "version": "10.11.1",
+ "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.11.1.tgz",
+ "integrity": "sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==",
+ "license": "MIT",
+ "dependencies": {
+ "vue-demi": ">=0.14.8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ }
+ },
+ "node_modules/abbrev": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-2.0.0.tgz",
+ "integrity": "sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.16.0",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz",
+ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "license": "MIT",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/adler-32": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/adler-32/-/adler-32-1.3.1.tgz",
+ "integrity": "sha512-ynZ4w/nUUv5rrsR8UUGoe1VC9hZj6V5hU9Qw1HlMDJGEJw5S7TfTErWTjMys6M7vr0YWcPqs3qAr4ss0nDfP+A==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "license": "MIT",
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.15.0",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.15.0.tgz",
+ "integrity": "sha512-fgFx7Hfoq60ytK2c7DhnF8jIvzYgOMxfugjLOSMHjLIPgenqa7S7oaagATUq99mV6IYvN2tRmC0wnTYX6iPbMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/alien-signals": {
+ "version": "1.0.13",
+ "resolved": "https://registry.npmjs.org/alien-signals/-/alien-signals-1.0.13.tgz",
+ "integrity": "sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "license": "MIT",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/antd-style": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/antd-style/-/antd-style-4.1.0.tgz",
+ "integrity": "sha512-vnPBGg0OVlSz90KRYZhxd89aZiOImTiesF+9MQqN8jsLGZUQTjbP04X9jTdEfsztKUuMbBWg/RmB/wHTakbtMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@ant-design/cssinjs": "^2.0.0",
+ "@babel/runtime": "^7.24.1",
+ "@emotion/cache": "^11.11.0",
+ "@emotion/css": "^11.11.2",
+ "@emotion/react": "^11.11.4",
+ "@emotion/serialize": "^1.1.3",
+ "@emotion/utils": "^1.2.1",
+ "use-merge-value": "^1.2.0"
+ },
+ "peerDependencies": {
+ "antd": ">=6.0.0",
+ "react": ">=18"
+ }
+ },
+ "node_modules/any-promise": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
+ "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/arg": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz",
+ "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
+ "dev": true,
+ "license": "Python-2.0"
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/autoprefixer": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.5.0.tgz",
+ "integrity": "sha512-FMhOoZV4+qR6aTUALKX2rEqGG+oyATvwBt9IIzVR5rMa2HRWPkxf+P+PAJLD1I/H5/II+HuZcBJYEFBpq39ong==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/autoprefixer"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "browserslist": "^4.28.2",
+ "caniuse-lite": "^1.0.30001787",
+ "fraction.js": "^5.3.4",
+ "picocolors": "^1.1.1",
+ "postcss-value-parser": "^4.2.0"
+ },
+ "bin": {
+ "autoprefixer": "bin/autoprefixer"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ },
+ "peerDependencies": {
+ "postcss": "^8.1.0"
+ }
+ },
+ "node_modules/axios": {
+ "version": "1.16.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.1.tgz",
+ "integrity": "sha512-caYkukvroVPO8KrzuJEb50Hm07KwfBZPEC3VeFHTsqWHvKTsy54hjJz9BS/cdaypROE2rH6xvm9mHX4fgWkr3A==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.16.0",
+ "form-data": "^4.0.5",
+ "https-proxy-agent": "^5.0.1",
+ "proxy-from-env": "^2.1.0"
+ }
+ },
+ "node_modules/babel-plugin-macros": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10",
+ "npm": ">=6"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.32",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.32.tgz",
+ "integrity": "sha512-wbPvpyjJPC0zdfdKXxqEL3Ea+bOMD/87X4lftiJkkaBiuG6ALQy1SLmEd7BSmVCuwCQsBrCamgBoLyfFDD1EPg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
+ "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/brace-expansion": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.1.tgz",
+ "integrity": "sha512-WR1cURNjuvBLMZBMbqM0UoE+WAfdUcEV1ccD8PVBVOI+Z3ND4+SZbN8RsfT2bMuG1qwz5RFvPukSZm5fF2D5eA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
+ "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fill-range": "^7.1.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.2",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz",
+ "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "baseline-browser-mapping": "^2.10.12",
+ "caniuse-lite": "^1.0.30001782",
+ "electron-to-chromium": "^1.5.328",
+ "node-releases": "^2.0.36",
+ "update-browserslist-db": "^1.2.3"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/call-bind-apply-helpers": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
+ "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-css": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz",
+ "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001793",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001793.tgz",
+ "integrity": "sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "CC-BY-4.0"
+ },
+ "node_modules/cfb": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/cfb/-/cfb-1.2.2.tgz",
+ "integrity": "sha512-KfdUZsSOw19/ObEWasvBP/Ac4reZvAGauZhs6S/gqNhXhI7cKwvlH7ulj+dOEYnca4bm4SGo8C1bTAQvnTjgQA==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "adler-32": "~1.3.0",
+ "crc-32": "~1.2.0"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/chai": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
+ "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chart.js": {
+ "version": "4.5.1",
+ "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz",
+ "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==",
+ "license": "MIT",
+ "dependencies": {
+ "@kurkle/color": "^0.3.0"
+ },
+ "engines": {
+ "pnpm": ">=8"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
+ "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
+ "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
+ "license": "ISC",
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^6.2.0"
+ }
+ },
+ "node_modules/cliui/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/cliui/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/cliui/node_modules/wrap-ansi": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
+ "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/codepage": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/codepage/-/codepage-1.15.0.tgz",
+ "integrity": "sha512-3g6NUTPd/YtuuGrhMnOMRjFc+LJw/bnMp3+0r/Wcz3IXUuCosKRJvMphm5+Q+bvTVGcJJuRvVLuYba+WojaFaA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "license": "MIT"
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/commander": {
+ "version": "10.0.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz",
+ "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/config-chain": {
+ "version": "1.1.13",
+ "resolved": "https://registry.npmjs.org/config-chain/-/config-chain-1.1.13.tgz",
+ "integrity": "sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ini": "^1.3.4",
+ "proto-list": "~1.2.1"
+ }
+ },
+ "node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "license": "MIT"
+ },
+ "node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/crc-32": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz",
+ "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==",
+ "license": "Apache-2.0",
+ "bin": {
+ "crc32": "bin/crc32.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
+ "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/cssstyle": {
+ "version": "4.6.0",
+ "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-4.6.0.tgz",
+ "integrity": "sha512-2z+rWdzbbSZv6/rhtvzvqeZQHrBaqgogqt85sqFNbabZOuFbCVFb8kPeEtZjiKkbrm395irpNKiYeFeLiQnFPg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@asamuzakjp/css-color": "^3.2.0",
+ "rrweb-cssom": "^0.8.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/cssstyle/node_modules/rrweb-cssom": {
+ "version": "0.8.0",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz",
+ "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/csstype": {
+ "version": "3.2.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
+ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
+ "license": "MIT"
+ },
+ "node_modules/data-urls": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-5.0.0.tgz",
+ "integrity": "sha512-ZYP5VBHshaDAiVZxjbRVcFJpc+4xGgT0bK3vzy1HLN8jTO975HEbuYzZJcHoQEY5K1a0z8YayJkyVETa08eNTg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/de-indent": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz",
+ "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/decimal.js": {
+ "version": "10.6.0",
+ "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz",
+ "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/didyoumean": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz",
+ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/dijkstrajs": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz",
+ "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==",
+ "license": "MIT"
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/dlv": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz",
+ "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dompurify": {
+ "version": "3.4.6",
+ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.4.6.tgz",
+ "integrity": "sha512-+7gzEI8trIIQkVCvQ3ucGtNfH3nOmDgVTzc62rAAOlMxLth78pwpPoZCPc7CyRzAQF89MqcfPdEWkDwnjgqktg==",
+ "license": "(MPL-2.0 OR Apache-2.0)",
+ "optionalDependencies": {
+ "@types/trusted-types": "^2.0.7"
+ }
+ },
+ "node_modules/driver.js": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/driver.js/-/driver.js-1.4.0.tgz",
+ "integrity": "sha512-Gm64jm6PmcU+si21sQhBrTAM1JvUrR0QhNmjkprNLxohOBzul9+pNHXgQaT9lW84gwg9GMLB3NZGuGolsz5uew==",
+ "license": "MIT"
+ },
+ "node_modules/dunder-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
+ "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "gopd": "^1.2.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/eastasianwidth": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
+ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/editorconfig": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/editorconfig/-/editorconfig-1.0.7.tgz",
+ "integrity": "sha512-e0GOtq/aTQhVdNyDU9e02+wz9oDDM+SIOQxWME2QRjzRX5yyLAuHDE+0aE8vHb9XRC8XD37eO2u57+F09JqFhw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@one-ini/wasm": "0.1.1",
+ "commander": "^10.0.0",
+ "minimatch": "^9.0.1",
+ "semver": "^7.5.3"
+ },
+ "bin": {
+ "editorconfig": "bin/editorconfig"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.362",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.362.tgz",
+ "integrity": "sha512-PUY2DrLvkjkUuWqq+KPL2iWshrJsZOcIojzRQ7eXFacc9dWga7MGMJAa15VbiejSZB1PAXaRLAiKgruHP8LB1w==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/emoji-regex": {
+ "version": "9.2.2",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
+ "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/entities": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-6.0.1.tgz",
+ "integrity": "sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/es-define-property": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
+ "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-errors": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
+ "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/es-object-atoms": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz",
+ "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/es-set-tostringtag": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
+ "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "get-intrinsic": "^1.2.6",
+ "has-tostringtag": "^1.0.2",
+ "hasown": "^2.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.1",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz",
+ "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==",
+ "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.1",
+ "@humanwhocodes/config-array": "^0.13.0",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-plugin-vue": {
+ "version": "9.33.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.33.0.tgz",
+ "integrity": "sha512-174lJKuNsuDIlLpjeXc5E2Tss8P44uIimAfGD0b90k0NoirJqpG7stLuU9Vp/9ioTOrQdWVREc4mRd1BD+CvGw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "globals": "^13.24.0",
+ "natural-compare": "^1.4.0",
+ "nth-check": "^2.1.1",
+ "postcss-selector-parser": "^6.0.15",
+ "semver": "^7.6.3",
+ "vue-eslint-parser": "^9.4.3",
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/brace-expansion": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/eslint/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.7.0.tgz",
+ "integrity": "sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.3.0.tgz",
+ "integrity": "sha512-knvyeauYhqjOYvQ66MznSMs83wmHrCycNEN6Ao+2AeYEfxUIkuiVxdEa1qlGEPK+We3n0THiDciYSsCcgW/DoA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz",
+ "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.8"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/fastq": {
+ "version": "1.20.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz",
+ "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/file-saver": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
+ "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==",
+ "license": "MIT"
+ },
+ "node_modules/fill-range": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
+ "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
+ "license": "MIT"
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.4.2",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz",
+ "integrity": "sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.16.0",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz",
+ "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/foreground-child": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
+ "integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "cross-spawn": "^7.0.6",
+ "signal-exit": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/frac": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/frac/-/frac-1.1.2.tgz",
+ "integrity": "sha512-w/XBfkibaTl3YDqASwfDUqkna4Z2p9cFSr1aHDt0WoMTECnRfBOv2WArlZILlqgWlmdIlALXGpM2AOhEk5W3IA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/fraction.js": {
+ "version": "5.3.4",
+ "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-5.3.4.tgz",
+ "integrity": "sha512-1X1NTtiJphryn/uLQz3whtY6jK3fTqoE3ohKs0tT+Ujr1W59oopxmoEh7Lu5p6vBaPbgoM0bzveAW4Qi5RyWDQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/rawify"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "license": "ISC",
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
+ "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "call-bind-apply-helpers": "^1.0.2",
+ "es-define-property": "^1.0.1",
+ "es-errors": "^1.3.0",
+ "es-object-atoms": "^1.1.1",
+ "function-bind": "^1.1.2",
+ "get-proto": "^1.0.1",
+ "gopd": "^1.2.0",
+ "has-symbols": "^1.1.0",
+ "hasown": "^2.0.2",
+ "math-intrinsics": "^1.1.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-proto": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
+ "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
+ "license": "MIT",
+ "dependencies": {
+ "dunder-proto": "^1.0.1",
+ "es-object-atoms": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/glob": {
+ "version": "10.5.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
+ "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "foreground-child": "^3.1.0",
+ "jackspeak": "^3.1.2",
+ "minimatch": "^9.0.4",
+ "minipass": "^7.1.2",
+ "package-json-from-dist": "^1.0.0",
+ "path-scurry": "^1.11.1"
+ },
+ "bin": {
+ "glob": "dist/esm/bin.mjs"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/gopd": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
+ "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
+ "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
+ "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
+ "license": "MIT",
+ "dependencies": {
+ "has-symbols": "^1.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/hasown": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz",
+ "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==",
+ "license": "MIT",
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
+ "node_modules/hoist-non-react-statics/node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "license": "MIT"
+ },
+ "node_modules/html-encoding-sniffer": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz",
+ "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "whatwg-encoding": "^3.1.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/html-escaper": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/html-escaper/-/html-escaper-2.0.2.tgz",
+ "integrity": "sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz",
+ "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/http-proxy-agent/node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
+ "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
+ "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
+ "license": "MIT",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/ini": {
+ "version": "1.3.8",
+ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz",
+ "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "license": "MIT"
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.16.2",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.2.tgz",
+ "integrity": "sha512-evOr8xfXKxE6qSR0hSXL2r3sd7ALj8+7jQEUvPYcm5sgZFdJ+AYzT6yNmJenvIYQBgIGwfwz08sL8zoL7yq2BA==",
+ "license": "MIT",
+ "dependencies": {
+ "hasown": "^2.0.3"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-mobile": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/is-mobile/-/is-mobile-5.0.0.tgz",
+ "integrity": "sha512-Tz/yndySvLAEXh+Uk8liFCxOwVH6YutuR74utvOcu7I9Di+DwM0mtdPVZNaVvvBUM2OXxne/NhOs1zAO7riusQ==",
+ "license": "MIT"
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-potential-custom-element-name": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz",
+ "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/istanbul-lib-coverage": {
+ "version": "3.2.2",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.2.tgz",
+ "integrity": "sha512-O8dpsF+r0WV/8MNRKfnmrtCWhuKjxrq2w+jpzBL5UZKTi2LeVWnWOmWRxFlesJONmc+wLAGvKQZEOanko0LFTg==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/istanbul-lib-report": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.1.tgz",
+ "integrity": "sha512-GCfE1mtsHGOELCU8e/Z7YWzpmybrx/+dSTfLrvY8qRmaY6zXTKWn6WQIjaAFw069icm6GVMNkgu0NzI4iPZUNw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "istanbul-lib-coverage": "^3.0.0",
+ "make-dir": "^4.0.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-lib-source-maps": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/istanbul-lib-source-maps/-/istanbul-lib-source-maps-5.0.6.tgz",
+ "integrity": "sha512-yg2d+Em4KizZC5niWhQaIomgf5WlL4vOOjZ5xGCmF8SnPE/mDWWXgvRExdcpCgh9lLRRa1/fSYp2ymmbJ1pI+A==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "@jridgewell/trace-mapping": "^0.3.23",
+ "debug": "^4.1.1",
+ "istanbul-lib-coverage": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/istanbul-reports": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/istanbul-reports/-/istanbul-reports-3.2.0.tgz",
+ "integrity": "sha512-HGYWWS/ehqTV3xN10i23tkPkpH46MLCIMFNCaaKNavAXTF1RkqxawEPtnjnGZ6XKSInBKkiOA5BKS+aZiY3AvA==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "html-escaper": "^2.0.0",
+ "istanbul-lib-report": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/jackspeak": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
+ "integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "@isaacs/cliui": "^8.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ },
+ "optionalDependencies": {
+ "@pkgjs/parseargs": "^0.11.0"
+ }
+ },
+ "node_modules/jiti": {
+ "version": "1.21.7",
+ "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz",
+ "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "jiti": "bin/jiti.js"
+ }
+ },
+ "node_modules/js-beautify": {
+ "version": "1.15.4",
+ "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.15.4.tgz",
+ "integrity": "sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "config-chain": "^1.1.13",
+ "editorconfig": "^1.0.4",
+ "glob": "^10.4.2",
+ "js-cookie": "^3.0.5",
+ "nopt": "^7.2.1"
+ },
+ "bin": {
+ "css-beautify": "js/bin/css-beautify.js",
+ "html-beautify": "js/bin/html-beautify.js",
+ "js-beautify": "js/bin/js-beautify.js"
+ },
+ "engines": {
+ "node": ">=14"
+ }
+ },
+ "node_modules/js-cookie": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.7.tgz",
+ "integrity": "sha512-z/wZZgDrkNV1eA0ULjM/F9/50Ya8fbzgKneSpoPsXSGd0KnpdtHfOZWK+GcwLk+EZbS4F9RBhU+K2RgzuDaItw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "license": "MIT"
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
+ "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsdom": {
+ "version": "24.1.3",
+ "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-24.1.3.tgz",
+ "integrity": "sha512-MyL55p3Ut3cXbeBEG7Hcv0mVM8pp8PBNWxRqchZnSfAiES1v1mRnMeFfaHWIPULpwsYfvO+ZmMZz5tGCnjzDUQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssstyle": "^4.0.1",
+ "data-urls": "^5.0.0",
+ "decimal.js": "^10.4.3",
+ "form-data": "^4.0.0",
+ "html-encoding-sniffer": "^4.0.0",
+ "http-proxy-agent": "^7.0.2",
+ "https-proxy-agent": "^7.0.5",
+ "is-potential-custom-element-name": "^1.0.1",
+ "nwsapi": "^2.2.12",
+ "parse5": "^7.1.2",
+ "rrweb-cssom": "^0.7.1",
+ "saxes": "^6.0.0",
+ "symbol-tree": "^3.2.4",
+ "tough-cookie": "^4.1.4",
+ "w3c-xmlserializer": "^5.0.0",
+ "webidl-conversions": "^7.0.0",
+ "whatwg-encoding": "^3.1.1",
+ "whatwg-mimetype": "^4.0.0",
+ "whatwg-url": "^14.0.0",
+ "ws": "^8.18.0",
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "peerDependencies": {
+ "canvas": "^2.11.2"
+ },
+ "peerDependenciesMeta": {
+ "canvas": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/jsdom/node_modules/agent-base": {
+ "version": "7.1.4",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
+ "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/jsdom/node_modules/https-proxy-agent": {
+ "version": "7.0.6",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
+ "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "agent-base": "^7.1.2",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 14"
+ }
+ },
+ "node_modules/jsdom/node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT"
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lilconfig": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
+ "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antonk52"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.18.1",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
+ "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/loupe": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
+ "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/lru-cache": {
+ "version": "10.4.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
+ "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/lucide-react": {
+ "version": "0.469.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.469.0.tgz",
+ "integrity": "sha512-28vvUnnKQ/dBwiCQtwJw7QauYnE7yd2Cyp4tTTJpvglX4EMpbflcdBgrgToX2j71B3YvugK/NH3BGUk+E/p/Fw==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/magicast": {
+ "version": "0.3.5",
+ "resolved": "https://registry.npmjs.org/magicast/-/magicast-0.3.5.tgz",
+ "integrity": "sha512-L0WhttDl+2BOsybvEOLK7fW3UA0OQ0IQ2d6Zl2x/a6vVRs3bAY0ECOSHHeL5jD+SbOpOCUEi0y1DgHEn9Qn1AQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.25.4",
+ "@babel/types": "^7.25.4",
+ "source-map-js": "^1.2.0"
+ }
+ },
+ "node_modules/make-dir": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-4.0.0.tgz",
+ "integrity": "sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "semver": "^7.5.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/marked": {
+ "version": "17.0.6",
+ "resolved": "https://registry.npmjs.org/marked/-/marked-17.0.6.tgz",
+ "integrity": "sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==",
+ "license": "MIT",
+ "bin": {
+ "marked": "bin/marked.js"
+ },
+ "engines": {
+ "node": ">= 20"
+ }
+ },
+ "node_modules/math-intrinsics": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
+ "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
+ "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "braces": "^3.0.3",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "9.0.9",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
+ "integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "7.1.3",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
+ "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "license": "MIT"
+ },
+ "node_modules/muggle-string": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.4.1.tgz",
+ "integrity": "sha512-VNTrAak/KhO2i8dqqnqnAHOa3cYBwXEZe9h+D5h/1ZqFSTEFHdM65lR7RoIqq3tBBYavsOXV84NoHXZ0AkPyqQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/mz": {
+ "version": "2.7.0",
+ "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz",
+ "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0",
+ "object-assign": "^4.0.1",
+ "thenify-all": "^1.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.12",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.12.tgz",
+ "integrity": "sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.46",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.46.tgz",
+ "integrity": "sha512-GYVXHE2KnrzAfsAjl4uP++evGFCrAU1jta4ubEjIG7YWt/64Gqv66a30yKwWczVjA6j3bM4nBwH7Pk1JmDHaxQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/nopt": {
+ "version": "7.2.1",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-7.2.1.tgz",
+ "integrity": "sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "abbrev": "^2.0.0"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": "^14.17.0 || ^16.13.0 || >=18.0.0"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npm-run-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-6.0.0.tgz",
+ "integrity": "sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "path-key": "^4.0.0",
+ "unicorn-magic": "^0.3.0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/npm-run-path/node_modules/path-key": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
+ "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
+ "node_modules/nwsapi": {
+ "version": "2.2.23",
+ "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.23.tgz",
+ "integrity": "sha512-7wfH4sLbt4M0gCDzGE6vzQBo0bfTKjU7Sfpqy/7gs1qBfYz2vEJH6vXcBKpO3+6Yu1telwd0t9HpyOoLEQQbIQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-hash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz",
+ "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.4",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz",
+ "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.5"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/package-json-from-dist": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
+ "integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
+ "dev": true,
+ "license": "BlueOak-1.0.0"
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "license": "MIT",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parse5": {
+ "version": "7.3.0",
+ "resolved": "https://registry.npmjs.org/parse5/-/parse5-7.3.0.tgz",
+ "integrity": "sha512-IInvU7fabl34qmi9gY8XOVxhYyMyuH2xUNpb2q8/Y+7552KlejkRvqvD19nMoUW/uQGGbqNpA6Tufu5FL5BZgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "entities": "^6.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/inikulin/parse5?sponsor=1"
+ }
+ },
+ "node_modules/path-browserify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz",
+ "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "license": "MIT"
+ },
+ "node_modules/path-scurry": {
+ "version": "1.11.1",
+ "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
+ "integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "lru-cache": "^10.2.0",
+ "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "license": "ISC"
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
+ "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pify": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz",
+ "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/pinia": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.3.1.tgz",
+ "integrity": "sha512-khUlZSwt9xXCaTbbxFYBKDc/bWAGWJjOgvxETwkTN7KRm66EeT1ZdZj6i2ceh9sP2Pzqsbc704r2yngBrxBVug==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.3",
+ "vue-demi": "^0.14.10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.4.4",
+ "vue": "^2.7.0 || ^3.5.11"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pirates": {
+ "version": "4.0.7",
+ "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.7.tgz",
+ "integrity": "sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/pngjs": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz",
+ "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/pnpm": {
+ "version": "11.3.0",
+ "resolved": "https://registry.npmjs.org/pnpm/-/pnpm-11.3.0.tgz",
+ "integrity": "sha512-LEA9ZZRScodnKx9wVjQ6H3w2NANqZ/+r/MKz11ldhDdo+HhxSNG1fPeVbJBga70ZKFfDY68Z6W0tDsnsV0HSFQ==",
+ "license": "MIT",
+ "bin": {
+ "pn": "bin/pnpm.mjs",
+ "pnpm": "bin/pnpm.mjs",
+ "pnpx": "bin/pnpx.mjs",
+ "pnx": "bin/pnpx.mjs"
+ },
+ "engines": {
+ "node": ">=22.13"
+ },
+ "funding": {
+ "url": "https://opencollective.com/pnpm"
+ }
+ },
+ "node_modules/polished": {
+ "version": "4.3.1",
+ "resolved": "https://registry.npmjs.org/polished/-/polished-4.3.1.tgz",
+ "integrity": "sha512-OBatVyC/N7SCW/FaDHrSd+vn0o5cS855TOmYi4OkdWUMSJCET/xip//ch8xGUvtr3i44X9LVyWwQlRMTN3pwSA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.17.8"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.15",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.15.tgz",
+ "integrity": "sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.12",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-import": {
+ "version": "15.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz",
+ "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "postcss-value-parser": "^4.0.0",
+ "read-cache": "^1.0.0",
+ "resolve": "^1.1.7"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.0.0"
+ }
+ },
+ "node_modules/postcss-js": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.1.0.tgz",
+ "integrity": "sha512-oIAOTqgIo7q2EOwbhb8UalYePMvYoIeRY2YKntdpFQXNosSu3vLrniGgmH9OKs/qAkfoj5oB3le/7mINW1LCfw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "camelcase-css": "^2.0.1"
+ },
+ "engines": {
+ "node": "^12 || ^14 || >= 16"
+ },
+ "peerDependencies": {
+ "postcss": "^8.4.21"
+ }
+ },
+ "node_modules/postcss-load-config": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-6.0.1.tgz",
+ "integrity": "sha512-oPtTM4oerL+UXmx+93ytZVN82RrlY/wPUV8IeDxFrzIjXOLF1pN+EmKPLbubvKHT2HC20xXsCAH2Z+CKV6Oz/g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "lilconfig": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 18"
+ },
+ "peerDependencies": {
+ "jiti": ">=1.21.0",
+ "postcss": ">=8.0.9",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "jiti": {
+ "optional": true
+ },
+ "postcss": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss-nested": {
+ "version": "6.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.2.0.tgz",
+ "integrity": "sha512-HQbt28KulC5AJzG+cZtj9kvKB93CFCdLvog1WFLf1D+xmMvPGlBstkpTEZfK5+AN9hfJocyBFCNiqyS48bpgzQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "postcss-selector-parser": "^6.1.1"
+ },
+ "engines": {
+ "node": ">=12.0"
+ },
+ "peerDependencies": {
+ "postcss": "^8.2.14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.1.2",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.1.2.tgz",
+ "integrity": "sha512-Q8qQfPiZ+THO/3ZrOrO0cJJKfpYCagtMUkXbnEfmgUjwXg6z/WBeOyS9APBBPCTSiDV+s4SwQGu8yFsiMRIudg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postcss-value-parser": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz",
+ "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/proto-list": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/proto-list/-/proto-list-1.2.4.tgz",
+ "integrity": "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/proxy-from-env": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz",
+ "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/psl": {
+ "version": "1.15.0",
+ "resolved": "https://registry.npmjs.org/psl/-/psl-1.15.0.tgz",
+ "integrity": "sha512-JZd3gMVBAVQkSs6HdNZo9Sdo0LNcQeMNP3CozBJb3JYC/QUYZTnKxP+f8oWRX4rHP5EurWxqAHTSwUCjlNKa1w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/lupomontero"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/qrcode": {
+ "version": "1.5.4",
+ "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.4.tgz",
+ "integrity": "sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==",
+ "license": "MIT",
+ "dependencies": {
+ "dijkstrajs": "^1.0.1",
+ "pngjs": "^5.0.0",
+ "yargs": "^15.3.1"
+ },
+ "bin": {
+ "qrcode": "bin/qrcode"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/querystringify": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz",
+ "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
+ "node_modules/react-is": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz",
+ "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==",
+ "license": "MIT"
+ },
+ "node_modules/read-cache": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
+ "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "pify": "^2.3.0"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-main-filename": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
+ "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
+ "license": "ISC"
+ },
+ "node_modules/requires-port": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
+ "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/resolve": {
+ "version": "1.22.12",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.12.tgz",
+ "integrity": "sha512-TyeJ1zif53BPfHootBGwPRYT1RUt6oGWsaQr8UyZW/eAm9bKoijtvruSDEmZHm92CwS9nj7/fWttqPCgzep8CA==",
+ "license": "MIT",
+ "dependencies": {
+ "es-errors": "^1.3.0",
+ "is-core-module": "^2.16.1",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz",
+ "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "deprecated": "Rimraf versions prior to v4 are no longer supported",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rimraf/node_modules/brace-expansion": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz",
+ "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/rimraf/node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rimraf/node_modules/minimatch": {
+ "version": "3.1.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz",
+ "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.60.4",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.60.4.tgz",
+ "integrity": "sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.60.4",
+ "@rollup/rollup-android-arm64": "4.60.4",
+ "@rollup/rollup-darwin-arm64": "4.60.4",
+ "@rollup/rollup-darwin-x64": "4.60.4",
+ "@rollup/rollup-freebsd-arm64": "4.60.4",
+ "@rollup/rollup-freebsd-x64": "4.60.4",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.60.4",
+ "@rollup/rollup-linux-arm-musleabihf": "4.60.4",
+ "@rollup/rollup-linux-arm64-gnu": "4.60.4",
+ "@rollup/rollup-linux-arm64-musl": "4.60.4",
+ "@rollup/rollup-linux-loong64-gnu": "4.60.4",
+ "@rollup/rollup-linux-loong64-musl": "4.60.4",
+ "@rollup/rollup-linux-ppc64-gnu": "4.60.4",
+ "@rollup/rollup-linux-ppc64-musl": "4.60.4",
+ "@rollup/rollup-linux-riscv64-gnu": "4.60.4",
+ "@rollup/rollup-linux-riscv64-musl": "4.60.4",
+ "@rollup/rollup-linux-s390x-gnu": "4.60.4",
+ "@rollup/rollup-linux-x64-gnu": "4.60.4",
+ "@rollup/rollup-linux-x64-musl": "4.60.4",
+ "@rollup/rollup-openbsd-x64": "4.60.4",
+ "@rollup/rollup-openharmony-arm64": "4.60.4",
+ "@rollup/rollup-win32-arm64-msvc": "4.60.4",
+ "@rollup/rollup-win32-ia32-msvc": "4.60.4",
+ "@rollup/rollup-win32-x64-gnu": "4.60.4",
+ "@rollup/rollup-win32-x64-msvc": "4.60.4",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/rrweb-cssom": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.7.1.tgz",
+ "integrity": "sha512-TrEMa7JGdVm0UThDJSx7ddw5nVm3UJS9o9CCIZ72B1vSyEZoziDqBYP3XIoi/12lKrJR8rE3jeFHMok2F/Mnsg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/saxes": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz",
+ "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "xmlchars": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=v12.22.7"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.8.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz",
+ "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==",
+ "dev": true,
+ "license": "ISC",
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "license": "ISC"
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/signal-exit": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
+ "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
+ "dev": true,
+ "license": "ISC",
+ "engines": {
+ "node": ">=14"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ssf": {
+ "version": "0.11.2",
+ "resolved": "https://registry.npmjs.org/ssf/-/ssf-0.11.2.tgz",
+ "integrity": "sha512-+idbmIXoYET47hH+d7dfm2epdOMUDjqcB4648sTZ+t2JwoyBFL/insLfB/racrDmsKB3diwsDA696pZMieAC5g==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "frac": "~1.1.2"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
+ "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "eastasianwidth": "^0.2.0",
+ "emoji-regex": "^9.2.2",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/string-width-cjs": {
+ "name": "string-width",
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string-width-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/string-width/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/string-width/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi-cjs": {
+ "name": "strip-ansi",
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/stylis": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.4.0.tgz",
+ "integrity": "sha512-5Z9ZpRzfuH6l/UAvCPAPUo3665Nk2wLaZU3x+TLHKVzIz33+sbJqbtrYoC3KD4/uVOr2Zp+L0LySezP9OHV9yA==",
+ "license": "MIT"
+ },
+ "node_modules/sucrase": {
+ "version": "3.35.1",
+ "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.1.tgz",
+ "integrity": "sha512-DhuTmvZWux4H1UOnWMB3sk0sbaCVOoQZjv8u1rDoTV0HTdGem9hkAZtl4JZy8P2z4Bg0nT+YMeOFyVr4zcG5Tw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "commander": "^4.0.0",
+ "lines-and-columns": "^1.1.6",
+ "mz": "^2.7.0",
+ "pirates": "^4.0.1",
+ "tinyglobby": "^0.2.11",
+ "ts-interface-checker": "^0.1.9"
+ },
+ "bin": {
+ "sucrase": "bin/sucrase",
+ "sucrase-node": "bin/sucrase-node"
+ },
+ "engines": {
+ "node": ">=16 || 14 >=14.17"
+ }
+ },
+ "node_modules/sucrase/node_modules/commander": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz",
+ "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/symbol-tree": {
+ "version": "3.2.4",
+ "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz",
+ "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tailwindcss": {
+ "version": "3.4.19",
+ "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.19.tgz",
+ "integrity": "sha512-3ofp+LL8E+pK/JuPLPggVAIaEuhvIz4qNcf3nA1Xn2o/7fb7s/TYpHhwGDv1ZU3PkBluUVaF8PyCHcm48cKLWQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@alloc/quick-lru": "^5.2.0",
+ "arg": "^5.0.2",
+ "chokidar": "^3.6.0",
+ "didyoumean": "^1.2.2",
+ "dlv": "^1.1.3",
+ "fast-glob": "^3.3.2",
+ "glob-parent": "^6.0.2",
+ "is-glob": "^4.0.3",
+ "jiti": "^1.21.7",
+ "lilconfig": "^3.1.3",
+ "micromatch": "^4.0.8",
+ "normalize-path": "^3.0.0",
+ "object-hash": "^3.0.0",
+ "picocolors": "^1.1.1",
+ "postcss": "^8.4.47",
+ "postcss-import": "^15.1.0",
+ "postcss-js": "^4.0.1",
+ "postcss-load-config": "^4.0.2 || ^5.0 || ^6.0",
+ "postcss-nested": "^6.2.0",
+ "postcss-selector-parser": "^6.1.2",
+ "resolve": "^1.22.8",
+ "sucrase": "^3.35.0"
+ },
+ "bin": {
+ "tailwind": "lib/cli.js",
+ "tailwindcss": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/test-exclude": {
+ "version": "7.0.2",
+ "resolved": "https://registry.npmjs.org/test-exclude/-/test-exclude-7.0.2.tgz",
+ "integrity": "sha512-u9E6A+ZDYdp7a4WnarkXPZOx8Ilz46+kby6p1yZ8zsGTz9gYa6FIS7lj2oezzNKmtdyyJNNmmXDppga5GB7kSw==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "@istanbuljs/schema": "^0.1.2",
+ "glob": "^10.4.1",
+ "minimatch": "^10.2.2"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/test-exclude/node_modules/balanced-match": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz",
+ "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/test-exclude/node_modules/brace-expansion": {
+ "version": "5.0.6",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz",
+ "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "balanced-match": "^4.0.2"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ }
+ },
+ "node_modules/test-exclude/node_modules/minimatch": {
+ "version": "10.2.5",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz",
+ "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==",
+ "dev": true,
+ "license": "BlueOak-1.0.0",
+ "dependencies": {
+ "brace-expansion": "^5.0.5"
+ },
+ "engines": {
+ "node": "18 || 20 || >=22"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/thenify": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz",
+ "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "any-promise": "^1.0.0"
+ }
+ },
+ "node_modules/thenify-all": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz",
+ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "thenify": ">= 3.1.0 < 4"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/tiny-invariant": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz",
+ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.16",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz",
+ "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/tinyglobby/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
+ "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
+ "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tough-cookie": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-4.1.4.tgz",
+ "integrity": "sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "psl": "^1.1.33",
+ "punycode": "^2.1.1",
+ "universalify": "^0.2.0",
+ "url-parse": "^1.5.3"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/tr46": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/tr46/-/tr46-5.1.1.tgz",
+ "integrity": "sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "punycode": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/ts-api-utils": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.4.3.tgz",
+ "integrity": "sha512-i3eMG77UTMD0hZhgRS562pv83RC6ukSAC2GMNWc+9dieh/+jDM5u5YG+NHX6VNDRHQcHwmsTHctP9LhbC3WxVw==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=16"
+ },
+ "peerDependencies": {
+ "typescript": ">=4.2.0"
+ }
+ },
+ "node_modules/ts-interface-checker": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz",
+ "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==",
+ "dev": true,
+ "license": "Apache-2.0"
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.6.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz",
+ "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "6.21.0",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz",
+ "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/unicorn-magic": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/unicorn-magic/-/unicorn-magic-0.3.0.tgz",
+ "integrity": "sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/universalify": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.2.0.tgz",
+ "integrity": "sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/url-parse": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz",
+ "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "querystringify": "^2.1.1",
+ "requires-port": "^1.0.0"
+ }
+ },
+ "node_modules/use-merge-value": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/use-merge-value/-/use-merge-value-1.2.0.tgz",
+ "integrity": "sha512-DXgG0kkgJN45TcyoXL49vJnn55LehnrmoHc7MbKi+QDBvr8dsesqws8UlyIWGHMR+JXgxc1nvY+jDGMlycsUcw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">= 16.x"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz",
+ "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.3.7",
+ "es-module-lexer": "^1.5.4",
+ "pathe": "^1.1.2",
+ "vite": "^5.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vite-plugin-checker": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/vite-plugin-checker/-/vite-plugin-checker-0.9.3.tgz",
+ "integrity": "sha512-Tf7QBjeBtG7q11zG0lvoF38/2AVUzzhMNu+Wk+mcsJ00Rk/FpJ4rmUviVJpzWkagbU13cGXvKpt7CMiqtxVTbQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "chokidar": "^4.0.3",
+ "npm-run-path": "^6.0.0",
+ "picocolors": "^1.1.1",
+ "picomatch": "^4.0.2",
+ "strip-ansi": "^7.1.0",
+ "tiny-invariant": "^1.3.3",
+ "tinyglobby": "^0.2.13",
+ "vscode-uri": "^3.1.0"
+ },
+ "engines": {
+ "node": ">=14.16"
+ },
+ "peerDependencies": {
+ "@biomejs/biome": ">=1.7",
+ "eslint": ">=7",
+ "meow": "^13.2.0",
+ "optionator": "^0.9.4",
+ "stylelint": ">=16",
+ "typescript": "*",
+ "vite": ">=2.0.0",
+ "vls": "*",
+ "vti": "*",
+ "vue-tsc": "~2.2.10"
+ },
+ "peerDependenciesMeta": {
+ "@biomejs/biome": {
+ "optional": true
+ },
+ "eslint": {
+ "optional": true
+ },
+ "meow": {
+ "optional": true
+ },
+ "optionator": {
+ "optional": true
+ },
+ "stylelint": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ },
+ "vls": {
+ "optional": true
+ },
+ "vti": {
+ "optional": true
+ },
+ "vue-tsc": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-plugin-checker/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/vite-plugin-checker/node_modules/chokidar": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
+ "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "readdirp": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 14.16.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/vite-plugin-checker/node_modules/picomatch": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz",
+ "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/vite-plugin-checker/node_modules/readdirp": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
+ "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.18.0"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ },
+ "node_modules/vite-plugin-checker/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz",
+ "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "2.1.9",
+ "@vitest/mocker": "2.1.9",
+ "@vitest/pretty-format": "^2.1.9",
+ "@vitest/runner": "2.1.9",
+ "@vitest/snapshot": "2.1.9",
+ "@vitest/spy": "2.1.9",
+ "@vitest/utils": "2.1.9",
+ "chai": "^5.1.2",
+ "debug": "^4.3.7",
+ "expect-type": "^1.1.0",
+ "magic-string": "^0.30.12",
+ "pathe": "^1.1.2",
+ "std-env": "^3.8.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.1",
+ "tinypool": "^1.0.1",
+ "tinyrainbow": "^1.2.0",
+ "vite": "^5.0.0",
+ "vite-node": "2.1.9",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "@vitest/browser": "2.1.9",
+ "@vitest/ui": "2.1.9",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vscode-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.1.0.tgz",
+ "integrity": "sha512-/BpdSx+yCQGnCvecbyXdxHDkuk55/G3xwnC0GqY4gmQ3j+A+g8kzzgB4Nk/SINjqn6+waqw3EgbVF2QKExkRxQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vue": {
+ "version": "3.5.34",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.34.tgz",
+ "integrity": "sha512-WdLBG9gm02OgJIG9axd5Hpx0TFLdzVgfG2evFFu8Rur5O/IoGc5cMjnjh3tPL6GnRGsYvUhBSKVPYVcxRKpMCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/compiler-dom": "3.5.34",
+ "@vue/compiler-sfc": "3.5.34",
+ "@vue/runtime-dom": "3.5.34",
+ "@vue/server-renderer": "3.5.34",
+ "@vue/shared": "3.5.34"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-chartjs": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/vue-chartjs/-/vue-chartjs-5.3.3.tgz",
+ "integrity": "sha512-jqxtL8KZ6YJ5NTv6XzrzLS7osyegOi28UGNZW0h9OkDL7Sh1396ht4Dorh04aKrl2LiSalQ84WtqiG0RIJb0tA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "chart.js": "^4.1.1",
+ "vue": "^3.0.0-0 || ^2.7.0"
+ }
+ },
+ "node_modules/vue-component-type-helpers": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/vue-component-type-helpers/-/vue-component-type-helpers-3.3.2.tgz",
+ "integrity": "sha512-l4Z2Y34m7nFMlx8vrslJaVtXxUpzgDMSESC7TakG/c5kwjYT/do+E0NcT2/vWDzaoIhsShg/2OKwX7Q4nbzC0g==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/vue-demi": {
+ "version": "0.14.10",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.10.tgz",
+ "integrity": "sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==",
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-draggable-plus": {
+ "version": "0.6.1",
+ "resolved": "https://registry.npmjs.org/vue-draggable-plus/-/vue-draggable-plus-0.6.1.tgz",
+ "integrity": "sha512-FbtQ/fuoixiOfTZzG3yoPl4JAo9HJXRHmBQZFB9x2NYCh6pq0TomHf7g5MUmpaDYv+LU2n6BPq2YN9sBO+FbIg==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/sortablejs": "^1.15.8"
+ },
+ "peerDependencies": {
+ "@types/sortablejs": "^1.15.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-eslint-parser": {
+ "version": "9.4.3",
+ "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.3.tgz",
+ "integrity": "sha512-2rYRLWlIpaiN8xbPiDyXZXRgLGOtWxERV7ND5fFAv5qo1D2N9Fu9MNajBNc6o13lZ+24DAWCkQCvj4klgmcITg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "debug": "^4.3.4",
+ "eslint-scope": "^7.1.1",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.3.1",
+ "esquery": "^1.4.0",
+ "lodash": "^4.17.21",
+ "semver": "^7.3.6"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=6.0.0"
+ }
+ },
+ "node_modules/vue-i18n": {
+ "version": "9.14.5",
+ "resolved": "https://registry.npmjs.org/vue-i18n/-/vue-i18n-9.14.5.tgz",
+ "integrity": "sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==",
+ "deprecated": "v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html",
+ "license": "MIT",
+ "dependencies": {
+ "@intlify/core-base": "9.14.5",
+ "@intlify/shared": "9.14.5",
+ "@vue/devtools-api": "^6.5.0"
+ },
+ "engines": {
+ "node": ">= 16"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/kazupon"
+ },
+ "peerDependencies": {
+ "vue": "^3.0.0"
+ }
+ },
+ "node_modules/vue-router": {
+ "version": "4.6.4",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.6.4.tgz",
+ "integrity": "sha512-Hz9q5sa33Yhduglwz6g9skT8OBPii+4bFn88w6J+J4MfEo4KRRpmiNG/hHHkdbRFlLBOqxN8y8gf2Fb0MTUgVg==",
+ "license": "MIT",
+ "dependencies": {
+ "@vue/devtools-api": "^6.6.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "vue": "^3.5.0"
+ }
+ },
+ "node_modules/vue-tsc": {
+ "version": "2.2.12",
+ "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-2.2.12.tgz",
+ "integrity": "sha512-P7OP77b2h/Pmk+lZdJ0YWs+5tJ6J2+uOQPo7tlBnY44QqQSPYvS0qVT4wqDJgwrZaLe47etJLLQRFia71GYITw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@volar/typescript": "2.4.15",
+ "@vue/language-core": "2.2.12"
+ },
+ "bin": {
+ "vue-tsc": "bin/vue-tsc.js"
+ },
+ "peerDependencies": {
+ "typescript": ">=5.0.0"
+ }
+ },
+ "node_modules/w3c-xmlserializer": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz",
+ "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "xml-name-validator": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/w3c-xmlserializer/node_modules/xml-name-validator": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
+ "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/webidl-conversions": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz",
+ "integrity": "sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==",
+ "dev": true,
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/whatwg-encoding": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
+ "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==",
+ "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "iconv-lite": "0.6.3"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-mimetype": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz",
+ "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/whatwg-url": {
+ "version": "14.2.0",
+ "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-14.2.0.tgz",
+ "integrity": "sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tr46": "^5.1.0",
+ "webidl-conversions": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "license": "ISC",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-module": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz",
+ "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==",
+ "license": "ISC"
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wmf": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wmf/-/wmf-1.0.2.tgz",
+ "integrity": "sha512-/p9K7bEh0Dj6WbXg4JG0xvLQmIadrner1bi45VMJTfnbVHsc7yIajZyoSoK60/dtVBs12Fm6WkUI5/3WAVsNMw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/word": {
+ "version": "0.3.0",
+ "resolved": "https://registry.npmjs.org/word/-/word-0.3.0.tgz",
+ "integrity": "sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz",
+ "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
+ "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^6.1.0",
+ "string-width": "^5.0.1",
+ "strip-ansi": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs": {
+ "name": "wrap-ansi",
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/wrap-ansi-cjs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-regex": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
+ "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-regex?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "6.2.3",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
+ "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/strip-ansi": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
+ "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^6.2.2"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/strip-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/ws": {
+ "version": "8.21.0",
+ "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz",
+ "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependencies": {
+ "bufferutil": "^4.0.1",
+ "utf-8-validate": ">=5.0.2"
+ },
+ "peerDependenciesMeta": {
+ "bufferutil": {
+ "optional": true
+ },
+ "utf-8-validate": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/xlsx": {
+ "version": "0.18.5",
+ "resolved": "https://registry.npmjs.org/xlsx/-/xlsx-0.18.5.tgz",
+ "integrity": "sha512-dmg3LCjBPHZnQp5/F/+nnTa+miPJxUXB6vtk42YjBBKayDNagxGEeIdWApkYPOf3Z3pm3k62Knjzp7lMeTEtFQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "adler-32": "~1.3.0",
+ "cfb": "~1.2.1",
+ "codepage": "~1.15.0",
+ "crc-32": "~1.2.1",
+ "ssf": "~0.11.2",
+ "wmf": "~1.0.1",
+ "word": "~0.3.0"
+ },
+ "bin": {
+ "xlsx": "bin/xlsx.njs"
+ },
+ "engines": {
+ "node": ">=0.8"
+ }
+ },
+ "node_modules/xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/xmlchars": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz",
+ "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/y18n": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz",
+ "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==",
+ "license": "ISC"
+ },
+ "node_modules/yaml": {
+ "version": "1.10.3",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.3.tgz",
+ "integrity": "sha512-vIYeF1u3CjlhAFekPPAk2h/Kv4T3mAkMox5OymRiJQB0spDP10LHvt+K7G9Ny6NuuMAb25/6n1qyUjAcGNf/AA==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/yargs": {
+ "version": "15.4.1",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
+ "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
+ "license": "MIT",
+ "dependencies": {
+ "cliui": "^6.0.0",
+ "decamelize": "^1.2.0",
+ "find-up": "^4.1.0",
+ "get-caller-file": "^2.0.1",
+ "require-directory": "^2.1.1",
+ "require-main-filename": "^2.0.0",
+ "set-blocking": "^2.0.0",
+ "string-width": "^4.2.0",
+ "which-module": "^2.0.0",
+ "y18n": "^4.0.0",
+ "yargs-parser": "^18.1.2"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "18.1.3",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
+ "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
+ "license": "ISC",
+ "dependencies": {
+ "camelcase": "^5.0.0",
+ "decamelize": "^1.2.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/yargs/node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "license": "MIT"
+ },
+ "node_modules/yargs/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "license": "MIT",
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "license": "MIT",
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "license": "MIT",
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/yargs/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "license": "MIT",
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yargs/node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "license": "MIT",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/frontend/package.json b/frontend/package.json
index fd39ba340d0..546440d819a 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -27,6 +27,7 @@
"file-saver": "^2.0.5",
"marked": "^17.0.1",
"pinia": "^2.1.7",
+ "pnpm": "^11.3.0",
"qrcode": "^1.5.4",
"vue": "^3.4.0",
"vue-chartjs": "^5.3.0",
diff --git a/frontend/pnpm-lock.yaml b/frontend/pnpm-lock.yaml
index e23fd3f63b3..fdbd46bc6ee 100644
--- a/frontend/pnpm-lock.yaml
+++ b/frontend/pnpm-lock.yaml
@@ -4,9 +4,6 @@ settings:
autoInstallPeers: true
excludeLinksFromLockfile: false
-overrides:
- js-cookie: 3.0.7
-
importers:
.:
@@ -47,6 +44,9 @@ importers:
pinia:
specifier: ^2.1.7
version: 2.3.1(typescript@5.6.3)(vue@3.5.26(typescript@5.6.3))
+ pnpm:
+ specifier: ^11.3.0
+ version: 11.3.0
qrcode:
specifier: ^1.5.4
version: 1.5.4
@@ -1639,6 +1639,7 @@ packages:
'@ungap/structured-clone@1.3.0':
resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==}
+ deprecated: Potential CWE-502 - Update to 1.3.1 or higher
'@upsetjs/venn.js@2.0.0':
resolution: {integrity: sha512-WbBhLrooyePuQ1VZxrJjtLvTc4NVfpOyKx0sKqioq9bX1C1m7Jgykkn8gLrtwumBioXIqam8DLxp88Adbue6Hw==}
@@ -2632,11 +2633,12 @@ packages:
glob@10.5.0:
resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
hasBin: true
glob@7.2.3:
resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==}
- deprecated: Glob versions prior to v9 are no longer supported
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
globals@13.24.0:
resolution: {integrity: sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==}
@@ -3481,6 +3483,11 @@ packages:
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
engines: {node: '>=10.13.0'}
+ pnpm@11.3.0:
+ resolution: {integrity: sha512-LEA9ZZRScodnKx9wVjQ6H3w2NANqZ/+r/MKz11ldhDdo+HhxSNG1fPeVbJBga70ZKFfDY68Z6W0tDsnsV0HSFQ==}
+ engines: {node: '>=22.13'}
+ hasBin: true
+
points-on-curve@0.2.0:
resolution: {integrity: sha512-0mYKnYYe9ZcqMCWhUjItv/oHjvgEsfKvnUTg8sAtnHr3GVy7rGkXCb6d5cSyqrWqL4k81b9CPg3urd+T7aop3A==}
@@ -4381,6 +4388,7 @@ packages:
vue-i18n@9.14.5:
resolution: {integrity: sha512-0jQ9Em3ymWngyiIkj0+c/k7WgaPO+TNzjKSNq9BvBQaKJECqn9cd9fL4tkDhB5G1QBskGl9YxxbDAhgbFtpe2g==}
engines: {node: '>= 16'}
+ deprecated: v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html
peerDependencies:
vue: ^3.0.0
@@ -8561,6 +8569,8 @@ snapshots:
pngjs@5.0.0: {}
+ pnpm@11.3.0: {}
+
points-on-curve@0.2.0: {}
points-on-path@0.2.1:
diff --git a/frontend/pnpm-workspace.yaml b/frontend/pnpm-workspace.yaml
new file mode 100644
index 00000000000..835b1e6378f
--- /dev/null
+++ b/frontend/pnpm-workspace.yaml
@@ -0,0 +1,3 @@
+allowBuilds:
+ esbuild: true
+ vue-demi: false
diff --git a/frontend/src/components/account/AccountCapacityCell.vue b/frontend/src/components/account/AccountCapacityCell.vue
index 3df5b24b4ff..f736795c223 100644
--- a/frontend/src/components/account/AccountCapacityCell.vue
+++ b/frontend/src/components/account/AccountCapacityCell.vue
@@ -176,7 +176,7 @@ const formatCost = (value: number | null | undefined) => {
}
// ====== 配额 ======
-const isQuotaEligible = computed(() => props.account.type === 'apikey' || props.account.type === 'bedrock')
+const isQuotaEligible = computed(() => props.account.type === 'apikey' || props.account.type === 'bedrock' || props.account.type === 'anthropic_aws')
const showDailyQuota = computed(() =>
isQuotaEligible.value && props.account.quota_daily_limit != null && props.account.quota_daily_limit > 0
diff --git a/frontend/src/components/account/AccountUsageCell.vue b/frontend/src/components/account/AccountUsageCell.vue
index 64f1366b3c0..d7f17970563 100644
--- a/frontend/src/components/account/AccountUsageCell.vue
+++ b/frontend/src/components/account/AccountUsageCell.vue
@@ -1141,7 +1141,7 @@ const makeQuotaBar = (
}
const hasApiKeyQuota = computed(() => {
- if (props.account.type !== 'apikey' && props.account.type !== 'bedrock') return false
+ if (props.account.type !== 'apikey' && props.account.type !== 'bedrock' && props.account.type !== 'anthropic_aws') return false
return (
(props.account.quota_daily_limit ?? 0) > 0 ||
(props.account.quota_weekly_limit ?? 0) > 0 ||
From 88e0612b8269960a467db33e91ab7afe52011334 Mon Sep 17 00:00:00 2001
From: lanlingxiawu <374397721@qq.com>
Date: Thu, 28 May 2026 18:00:34 +0800
Subject: [PATCH 4/6] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E5=90=91=E8=8A=82?=
=?UTF-8?q?=E7=82=B9=E4=B8=8A=E6=8A=A5=E7=8A=B6=E6=80=81=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
添加向节点上报状态功能
---
.env.example | 6 +
backend/cmd/server/main.go | 44 ++
backend/cmd/server/wire.go | 8 +-
backend/cmd/server/wire_gen.go | 10 +-
backend/internal/node/adapter.go | 73 +++
backend/internal/node/client.go | 106 ++++
backend/internal/node/config.go | 104 ++++
backend/internal/node/metrics.go | 196 +++++++
backend/internal/node/reporter.go | 274 ++++++++++
...45\345\217\243\346\226\207\346\241\243.md" | 515 ++++++++++++++++++
10 files changed, 1330 insertions(+), 6 deletions(-)
create mode 100644 .env.example
create mode 100644 backend/internal/node/adapter.go
create mode 100644 backend/internal/node/client.go
create mode 100644 backend/internal/node/config.go
create mode 100644 backend/internal/node/metrics.go
create mode 100644 backend/internal/node/reporter.go
create mode 100644 "\350\212\202\347\202\271\346\216\245\345\217\243\346\226\207\346\241\243.md"
diff --git a/.env.example b/.env.example
new file mode 100644
index 00000000000..6fdeff11271
--- /dev/null
+++ b/.env.example
@@ -0,0 +1,6 @@
+NODE_NAME=node-001
+CONTROL_ADDR=https://127.0.0.1:8888
+NODE_PORT=8080
+TLS_CERT=certs/client.crt
+TLS_KEY=certs/client.key
+TLS_CA=certs/ca.crt
\ No newline at end of file
diff --git a/backend/cmd/server/main.go b/backend/cmd/server/main.go
index 784f309fce8..d85c480784c 100644
--- a/backend/cmd/server/main.go
+++ b/backend/cmd/server/main.go
@@ -18,8 +18,14 @@ import (
_ "github.com/Wei-Shaw/sub2api/ent/runtime"
"github.com/Wei-Shaw/sub2api/internal/config"
"github.com/Wei-Shaw/sub2api/internal/handler"
+ // xiugai 添加节点功能
+ "github.com/Wei-Shaw/sub2api/internal/node"
+ // end
"github.com/Wei-Shaw/sub2api/internal/pkg/logger"
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
+ // xiugai 添加节点功能
+ "github.com/Wei-Shaw/sub2api/internal/service"
+ // end
"github.com/Wei-Shaw/sub2api/internal/setup"
"github.com/Wei-Shaw/sub2api/internal/web"
@@ -154,6 +160,13 @@ func runMainServer() {
}
defer app.Cleanup()
+ // xiugai 添加节点功能
+ // 启动节点上报服务(可选,仅当当前目录存在 .env 时生效)
+ reporterCtx, reporterCancel := context.WithCancel(context.Background())
+ defer reporterCancel()
+ startNodeReporter(reporterCtx, app.AccountRepo)
+ // end
+
// 启动服务器
go func() {
if err := app.Server.ListenAndServe(); err != nil && !errors.Is(err, http.ErrServerClosed) {
@@ -179,3 +192,34 @@ func runMainServer() {
log.Println("Server exited")
}
+
+// xiugai 添加节点功能
+
+// startNodeReporter 尝试从当前工作目录加载 .env 配置并以后台 goroutine 方式
+// 启动节点上报服务。若 .env 不存在或配置有误,仅打印日志后静默返回,不影响主服务启动。
+//
+// 参数:
+// - ctx:上下文,取消时节点上报服务随之停止。
+// - repo:账号数据库访问层,用于读取本地全量账号数据并上报给控制服务器。
+func startNodeReporter(ctx context.Context, repo service.AccountRepository) {
+ cfg, err := node.LoadConfig(".env")
+ if err != nil {
+ // .env 不存在属于正常情况(未启用节点上报功能),其他错误才打印警告。
+ if !os.IsNotExist(err) {
+ log.Printf("[Node] Config error, reporter disabled: %v", err)
+ }
+ return
+ }
+
+ lister := node.NewRepoAccountLister(repo)
+ reporter, err := node.NewReporter(cfg, lister)
+ if err != nil {
+ log.Printf("[Node] Init error, reporter disabled: %v", err)
+ return
+ }
+
+ go reporter.Start(ctx)
+ log.Printf("[Node] Reporter started (node=%s, control=%s)", cfg.NodeName, cfg.ControlAddr)
+}
+
+// end
diff --git a/backend/cmd/server/wire.go b/backend/cmd/server/wire.go
index 9bfa27174db..6ba5d43058a 100644
--- a/backend/cmd/server/wire.go
+++ b/backend/cmd/server/wire.go
@@ -24,7 +24,11 @@ import (
)
type Application struct {
- Server *http.Server
+ Server *http.Server
+ // xiugai 添加节点功能
+ // AccountRepo 账号数据库访问层,供节点上报服务读取本地全量账号数据。
+ AccountRepo service.AccountRepository
+ // end
Cleanup func()
}
@@ -53,7 +57,7 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
provideCleanup,
// Application struct
- wire.Struct(new(Application), "Server", "Cleanup"),
+ wire.Struct(new(Application), "Server", "AccountRepo", "Cleanup"),
)
return nil, nil
}
diff --git a/backend/cmd/server/wire_gen.go b/backend/cmd/server/wire_gen.go
index e39a645ac6b..68f03a1c8be 100644
--- a/backend/cmd/server/wire_gen.go
+++ b/backend/cmd/server/wire_gen.go
@@ -269,8 +269,9 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
channelMonitorRunner := service.ProvideChannelMonitorRunner(channelMonitorService, settingService)
v := provideCleanup(client, redisClient, opsMetricsCollector, opsAggregationService, opsAlertEvaluatorService, opsCleanupService, opsScheduledReportService, opsSystemLogSink, schedulerSnapshotService, tokenRefreshService, accountExpiryService, subscriptionExpiryService, usageCleanupService, idempotencyCleanupService, pricingService, emailQueueService, billingCacheService, usageRecordWorkerPool, subscriptionService, oAuthService, openAIOAuthService, geminiOAuthService, antigravityOAuthService, openAIGatewayService, scheduledTestRunnerService, backupService, paymentOrderExpiryService, channelMonitorRunner)
application := &Application{
- Server: httpServer,
- Cleanup: v,
+ Server: httpServer,
+ AccountRepo: accountRepository,
+ Cleanup: v,
}
return application, nil
}
@@ -278,8 +279,9 @@ func initializeApplication(buildInfo handler.BuildInfo) (*Application, error) {
// wire.go:
type Application struct {
- Server *http.Server
- Cleanup func()
+ Server *http.Server
+ AccountRepo service.AccountRepository
+ Cleanup func()
}
func providePrivacyClientFactory() service.PrivacyClientFactory {
diff --git a/backend/internal/node/adapter.go b/backend/internal/node/adapter.go
new file mode 100644
index 00000000000..b7471999864
--- /dev/null
+++ b/backend/internal/node/adapter.go
@@ -0,0 +1,73 @@
+package node
+
+// xiugai 添加节点功能
+
+import (
+ "context"
+
+ "github.com/Wei-Shaw/sub2api/internal/pkg/pagination"
+ "github.com/Wei-Shaw/sub2api/internal/service"
+)
+
+// accountRepoLister 将 service.AccountRepository 适配为 AccountLister 接口,
+// 通过分页查询将全量账号数据逐批加载后返回。
+type accountRepoLister struct {
+ // repo 底层账号数据库访问层接口。
+ repo service.AccountRepository
+}
+
+// NewRepoAccountLister 将 service.AccountRepository 包装为 AccountLister,
+// 供 Reporter 调用以获取本地全量账号数据。
+//
+// 参数:
+// - repo:账号数据库访问层接口,由 Wire 注入。
+//
+// 返回值:
+// - AccountLister:可被 Reporter 使用的账号列表接口实现。
+func NewRepoAccountLister(repo service.AccountRepository) AccountLister {
+ return &accountRepoLister{repo: repo}
+}
+
+// ListAll 返回数据库中所有账号(不限状态),采用分页查询避免一次性加载过多数据。
+// 每批最多查询 1000 条,直到取完所有记录为止。
+//
+// 参数:
+// - ctx:查询上下文,用于超时和取消控制。
+//
+// 返回值:
+// - []NodeAccount:所有账号的 ID、名称、状态切片。
+// - error:数据库查询失败时返回错误。
+func (l *accountRepoLister) ListAll(ctx context.Context) ([]NodeAccount, error) {
+ const pageSize = 1000
+
+ var all []NodeAccount
+ page := 1
+ for {
+ rows, result, err := l.repo.List(ctx, pagination.PaginationParams{
+ Page: page,
+ PageSize: pageSize,
+ SortOrder: pagination.SortOrderAsc,
+ })
+ if err != nil {
+ return nil, err
+ }
+
+ for _, a := range rows {
+ all = append(all, NodeAccount{
+ ID: a.ID,
+ Name: a.Name,
+ Status: a.Status,
+ })
+ }
+
+ // 已取完所有记录或当前批次不足一页时退出循环。
+ if int64(len(all)) >= result.Total || len(rows) < pageSize {
+ break
+ }
+ page++
+ }
+
+ return all, nil
+}
+
+// end
diff --git a/backend/internal/node/client.go b/backend/internal/node/client.go
new file mode 100644
index 00000000000..f76f8203c9f
--- /dev/null
+++ b/backend/internal/node/client.go
@@ -0,0 +1,106 @@
+package node
+
+// xiugai 添加节点功能
+
+import (
+ "bytes"
+ "context"
+ "crypto/tls"
+ "crypto/x509"
+ "encoding/json"
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "time"
+)
+
+// controlClient 封装对控制服务器的 HTTP 请求,支持 mTLS 双向证书认证。
+type controlClient struct {
+ // http 底层 HTTP 客户端,已配置 TLS 和超时。
+ http *http.Client
+ // baseURL 控制服务器 API 前缀,例如 https://127.0.0.1:8443/api/v1。
+ baseURL string
+}
+
+// newControlClient 根据节点配置构建 controlClient。
+// 当 Config.TLSCert/TLSKey 不为空时加载客户端证书;当 Config.TLSCA 不为空时
+// 使用指定 CA 校验服务端证书,否则使用系统根证书池。
+//
+// 参数:
+// - cfg:节点配置,包含控制服务器地址和 TLS 证书路径。
+//
+// 返回值:
+// - *controlClient:构建成功的客户端。
+// - error:证书加载或解析失败时返回错误。
+func newControlClient(cfg *Config) (*controlClient, error) {
+ tlsCfg := &tls.Config{}
+
+ // 如果同时配置了客户端证书和私钥,则加载以支持 mTLS。
+ if cfg.TLSCert != "" && cfg.TLSKey != "" {
+ cert, err := tls.LoadX509KeyPair(cfg.TLSCert, cfg.TLSKey)
+ if err != nil {
+ return nil, fmt.Errorf("load client cert/key: %w", err)
+ }
+ tlsCfg.Certificates = []tls.Certificate{cert}
+ }
+
+ // 如果指定了 CA 证书,则构建自定义根证书池以校验服务端。
+ if cfg.TLSCA != "" {
+ pem, err := os.ReadFile(cfg.TLSCA)
+ if err != nil {
+ return nil, fmt.Errorf("read CA cert: %w", err)
+ }
+ pool := x509.NewCertPool()
+ if !pool.AppendCertsFromPEM(pem) {
+ return nil, fmt.Errorf("parse CA cert: no valid PEM block found in %s", cfg.TLSCA)
+ }
+ tlsCfg.RootCAs = pool
+ }
+
+ return &controlClient{
+ http: &http.Client{
+ Transport: &http.Transport{TLSClientConfig: tlsCfg},
+ Timeout: 10 * time.Second,
+ },
+ baseURL: cfg.ControlAddr + "/api/v1",
+ }, nil
+}
+
+// post 向控制服务器发送 POST 请求,请求体为 body 的 JSON 序列化结果。
+// 仅接受 HTTP 200 作为成功响应,其他状态码均返回错误。
+//
+// 参数:
+// - ctx:请求上下文,用于超时和取消控制。
+// - path:相对于 baseURL 的接口路径,例如 "/heartbeat"。
+// - body:将被序列化为 JSON 的请求体对象。
+//
+// 返回值:
+// - error:网络错误、JSON 序列化失败或非 200 响应时返回错误。
+func (c *controlClient) post(ctx context.Context, path string, body any) error {
+ data, err := json.Marshal(body)
+ if err != nil {
+ return fmt.Errorf("marshal body: %w", err)
+ }
+
+ req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.baseURL+path, bytes.NewReader(data))
+ if err != nil {
+ return fmt.Errorf("build request: %w", err)
+ }
+ req.Header.Set("Content-Type", "application/json")
+
+ resp, err := c.http.Do(req)
+ if err != nil {
+ return fmt.Errorf("http: %w", err)
+ }
+ defer resp.Body.Close()
+ // 丢弃响应体以复用底层 TCP 连接。
+ _, _ = io.Copy(io.Discard, resp.Body)
+
+ if resp.StatusCode != http.StatusOK {
+ return fmt.Errorf("unexpected status %d", resp.StatusCode)
+ }
+ return nil
+}
+
+// end
diff --git a/backend/internal/node/config.go b/backend/internal/node/config.go
new file mode 100644
index 00000000000..3740fc6ed9e
--- /dev/null
+++ b/backend/internal/node/config.go
@@ -0,0 +1,104 @@
+// Package node 实现节点上报服务,定期向远程控制服务器发送心跳并同步账号状态快照。
+package node
+
+// xiugai 添加节点功能
+
+import (
+ "bufio"
+ "fmt"
+ "os"
+ "strconv"
+ "strings"
+)
+
+// Config 保存节点上报服务的配置信息,从当前目录的 .env 文件中加载。
+// 节点内网 IP 不在此处配置,由上报服务启动时自动从系统网络接口检测。
+type Config struct {
+ // NodeName 节点唯一名称,用于在控制服务器上标识本节点。
+ NodeName string
+ // ControlAddr 控制服务器的基础 URL,例如 https://127.0.0.1:8443。
+ ControlAddr string
+ // NodePort 本节点的监听端口,在心跳请求中上报给控制服务器。
+ NodePort int
+ // TLSCert 客户端证书文件路径,启用 mTLS 双向认证时使用。
+ TLSCert string
+ // TLSKey 客户端私钥文件路径,与 TLSCert 配套使用。
+ TLSKey string
+ // TLSCA CA 证书文件路径,用于校验控制服务器的 TLS 证书。
+ TLSCA string
+}
+
+// LoadConfig 从指定路径的 .env 文件读取并解析节点配置。
+//
+// 必填项:NODE_NAME(节点名称)、CONTROL_ADDR(控制服务器地址)。
+// 可选项:NODE_PORT(本节点监听端口)、TLS_CERT(客户端证书)、
+//
+// TLS_KEY(客户端私钥)、TLS_CA(CA 证书)。
+//
+// 节点内网 IP 不在此处读取,由上报服务启动时自动从系统网络接口检测。
+//
+// 参数:
+// - path:.env 文件路径,通常为当前工作目录下的 ".env"。
+//
+// 返回值:
+// - *Config:解析成功后的配置对象。
+// - error:文件不存在、读取失败或必填项缺失时返回对应错误。
+func LoadConfig(path string) (*Config, error) {
+ f, err := os.Open(path)
+ if err != nil {
+ return nil, fmt.Errorf("open %s: %w", path, err)
+ }
+ defer f.Close()
+
+ // 逐行解析 KEY=VALUE 格式,忽略空行和 # 开头的注释行。
+ env := make(map[string]string)
+ scanner := bufio.NewScanner(f)
+ for scanner.Scan() {
+ line := strings.TrimSpace(scanner.Text())
+ if line == "" || strings.HasPrefix(line, "#") {
+ continue
+ }
+ key, val, ok := strings.Cut(line, "=")
+ if !ok {
+ continue
+ }
+ key = strings.TrimSpace(key)
+ val = strings.TrimSpace(val)
+ // 去除可选的单引号或双引号包裹。
+ if len(val) >= 2 && ((val[0] == '"' && val[len(val)-1] == '"') || (val[0] == '\'' && val[len(val)-1] == '\'')) {
+ val = val[1 : len(val)-1]
+ }
+ env[key] = val
+ }
+ if err := scanner.Err(); err != nil {
+ return nil, fmt.Errorf("read %s: %w", path, err)
+ }
+
+ cfg := &Config{
+ NodeName: env["NODE_NAME"],
+ ControlAddr: strings.TrimRight(env["CONTROL_ADDR"], "/"),
+ TLSCert: env["TLS_CERT"],
+ TLSKey: env["TLS_KEY"],
+ TLSCA: env["TLS_CA"],
+ }
+
+ if cfg.NodeName == "" {
+ return nil, fmt.Errorf("NODE_NAME is required in %s", path)
+ }
+ if cfg.ControlAddr == "" {
+ return nil, fmt.Errorf("CONTROL_ADDR is required in %s", path)
+ }
+
+ // NODE_PORT 为可选项,若填写则校验合法范围。
+ if portStr := env["NODE_PORT"]; portStr != "" {
+ port, convErr := strconv.Atoi(portStr)
+ if convErr != nil || port < 1 || port > 65535 {
+ return nil, fmt.Errorf("NODE_PORT must be a valid port number (1-65535)")
+ }
+ cfg.NodePort = port
+ }
+
+ return cfg, nil
+}
+
+// end
diff --git a/backend/internal/node/metrics.go b/backend/internal/node/metrics.go
new file mode 100644
index 00000000000..7ce34034e5e
--- /dev/null
+++ b/backend/internal/node/metrics.go
@@ -0,0 +1,196 @@
+package node
+
+// xiugai 添加节点功能
+
+import (
+ "context"
+ "net"
+ "sync"
+ "time"
+
+ "github.com/shirou/gopsutil/v4/cpu"
+ "github.com/shirou/gopsutil/v4/mem"
+ gopsnet "github.com/shirou/gopsutil/v4/net"
+)
+
+// systemMetrics 保存某一时刻的主机资源使用率快照。
+type systemMetrics struct {
+ // CPUUsage CPU 总使用率百分比(0~100)。
+ CPUUsage float64
+ // MemUsage 虚拟内存使用率百分比(0~100)。
+ MemUsage float64
+}
+
+// collectSystemMetrics 采集当前主机的 CPU 和内存使用率。
+// CPU 采样会阻塞约 1 秒以计算区间平均值;采集失败时对应字段保持为 0。
+//
+// 参数:
+// - ctx:上下文,用于提前取消采集(如心跳超时时)。
+//
+// 返回值:
+// - systemMetrics:包含 CPU 和内存使用率的快照结构体。
+func collectSystemMetrics(ctx context.Context) systemMetrics {
+ var m systemMetrics
+
+ // cpu.PercentWithContext 会阻塞 1 s 以测量区间 CPU 使用率,false 表示返回全核平均值。
+ if percents, err := cpu.PercentWithContext(ctx, time.Second, false); err == nil && len(percents) > 0 {
+ m.CPUUsage = percents[0]
+ }
+
+ if vm, err := mem.VirtualMemoryWithContext(ctx); err == nil {
+ m.MemUsage = vm.UsedPercent
+ }
+
+ return m
+}
+
+// detectInternalIP 遍历本机所有已启动的非回环网络接口,返回第一个找到的
+// 非回环单播 IPv4 地址字符串。若未找到任何合适地址,返回空字符串。
+//
+// 返回值:
+// - string:内网 IPv4 地址字符串,例如 "10.0.0.12";未检测到时为空字符串。
+func detectInternalIP() string {
+ ifaces, err := net.Interfaces()
+ if err != nil {
+ return ""
+ }
+ for _, iface := range ifaces {
+ // 跳过已关闭或回环接口。
+ if iface.Flags&net.FlagUp == 0 || iface.Flags&net.FlagLoopback != 0 {
+ continue
+ }
+ addrs, err := iface.Addrs()
+ if err != nil {
+ continue
+ }
+ for _, addr := range addrs {
+ var ip net.IP
+ switch v := addr.(type) {
+ case *net.IPNet:
+ ip = v.IP
+ case *net.IPAddr:
+ ip = v.IP
+ }
+ if ip == nil || ip.IsLoopback() {
+ continue
+ }
+ // 仅返回 IPv4 地址。
+ if v4 := ip.To4(); v4 != nil {
+ return v4.String()
+ }
+ }
+ }
+ return ""
+}
+
+// xiugai 添加节点功能 - 带宽采样器
+
+// bandwidthReading 保存一次带宽采样的结果,单位 MB/s。
+type bandwidthReading struct {
+ // UploadMBps 上行带宽,单位 MB/s。
+ UploadMBps float64
+ // DownloadMBps 下行带宽,单位 MB/s。
+ DownloadMBps float64
+}
+
+// bandwidthSampler 在独立 goroutine 中每 5 秒测量一次当前 1 秒内的
+// 网络上行/下行带宽,并将结果缓存供心跳上报读取。
+type bandwidthSampler struct {
+ mu sync.RWMutex
+ latest bandwidthReading
+}
+
+// newBandwidthSampler 创建带宽采样器实例。
+// 需调用 Start(ctx) 启动后台采样 goroutine。
+//
+// 返回值:
+// - *bandwidthSampler:新建的采样器,初始带宽读数均为 0。
+func newBandwidthSampler() *bandwidthSampler {
+ return &bandwidthSampler{}
+}
+
+// Start 启动带宽采样后台 goroutine:立即执行一次采样,之后每 5 秒执行一次。
+// 与心跳上报的间隔对齐,保证心跳发出前带宽读数已更新。
+// ctx 取消时 goroutine 退出。
+//
+// 参数:
+// - ctx:上下文,取消后采样 goroutine 停止。
+func (s *bandwidthSampler) Start(ctx context.Context) {
+ go s.run(ctx)
+}
+
+// run 是 bandwidthSampler 的后台循环,由 Start 启动。
+//
+// 参数:
+// - ctx:上下文,取消时退出循环。
+func (s *bandwidthSampler) run(ctx context.Context) {
+ ticker := time.NewTicker(heartbeatInterval)
+ defer ticker.Stop()
+
+ // 立即执行一次,使首次心跳携带带宽数据。
+ s.sample(ctx)
+
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case <-ticker.C:
+ s.sample(ctx)
+ }
+ }
+}
+
+// sample 在 1 秒窗口内采集两次网络 I/O 计数器,计算期间的收发速率(MB/s)
+// 并更新缓存。采集失败时保持上一次的读数不变。
+//
+// 参数:
+// - ctx:上下文;若在等待 1 秒期间 ctx 被取消则立即返回。
+func (s *bandwidthSampler) sample(ctx context.Context) {
+ // 第一次读取网络 I/O 计数器(pernic=false 表示返回所有接口的汇总值)。
+ counters1, err := gopsnet.IOCountersWithContext(ctx, false)
+ if err != nil || len(counters1) == 0 {
+ return
+ }
+
+ // 等待 1 秒作为采样窗口。
+ select {
+ case <-ctx.Done():
+ return
+ case <-time.After(time.Second):
+ }
+
+ // 第二次读取,与第一次作差得到 1 秒内的字节增量。
+ counters2, err := gopsnet.IOCountersWithContext(ctx, false)
+ if err != nil || len(counters2) == 0 {
+ return
+ }
+
+ // 字节差值除以 1048576 转换为 MB/s。
+ upload := float64(counters2[0].BytesSent-counters1[0].BytesSent) / 1048576.0
+ download := float64(counters2[0].BytesRecv-counters1[0].BytesRecv) / 1048576.0
+
+ // 防止计数器回绕导致负值(系统重启或溢出时)。
+ if upload < 0 {
+ upload = 0
+ }
+ if download < 0 {
+ download = 0
+ }
+
+ s.mu.Lock()
+ s.latest = bandwidthReading{UploadMBps: upload, DownloadMBps: download}
+ s.mu.Unlock()
+}
+
+// Latest 返回最近一次采样得到的带宽读数(单位 MB/s)。
+// 若采样器尚未完成首次采样,返回零值。
+//
+// 返回值:
+// - bandwidthReading:包含上行和下行带宽(MB/s)的结构体。
+func (s *bandwidthSampler) Latest() bandwidthReading {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+ return s.latest
+}
+
+// end
diff --git a/backend/internal/node/reporter.go b/backend/internal/node/reporter.go
new file mode 100644
index 00000000000..180d6964945
--- /dev/null
+++ b/backend/internal/node/reporter.go
@@ -0,0 +1,274 @@
+package node
+
+// xiugai 添加节点功能
+
+import (
+ "context"
+ "fmt"
+ "log/slog"
+ "sort"
+ "strconv"
+ "sync"
+ "time"
+)
+
+// heartbeatInterval 心跳上报的时间间隔,每 5 秒向控制服务器发送一次心跳。
+const heartbeatInterval = 5 * time.Second
+
+// heartbeatRequest 对应控制服务器 POST /api/v1/heartbeat 的请求体结构。
+type heartbeatRequest struct {
+ // InternalIP 节点内网 IP。
+ InternalIP string `json:"internal_ip"`
+ // NodeName 节点名称,与控制服务器中的记录对应。
+ NodeName string `json:"node_name"`
+ // ListenPort 本节点的服务监听端口。
+ ListenPort int `json:"listen_port"`
+ // CPUUsage CPU 使用率百分比,可选。
+ CPUUsage float64 `json:"cpu_usage,omitempty"`
+ // MemUsage 内存使用率百分比,可选。
+ MemUsage float64 `json:"mem_usage,omitempty"`
+ // xiugai 添加节点功能
+ // UploadBandwidth 当前系统上行带宽,单位 MB/s,可选。
+ UploadBandwidth float64 `json:"upload_bandwidth,omitempty"`
+ // DownloadBandwidth 当前系统下行带宽,单位 MB/s,可选。
+ DownloadBandwidth float64 `json:"download_bandwidth,omitempty"`
+ // end
+}
+
+// accountStatus 对应控制服务器账号列表中单个账号的状态信息。
+type accountStatus struct {
+ // ID 账号唯一标识(本地数据库主键转字符串)。
+ ID string `json:"id"`
+ // Name 账号显示名称。
+ Name string `json:"name"`
+ // Status 账号当前状态,如 active、disabled、error 等。
+ Status string `json:"status"`
+}
+
+// accountsStatusRequest 对应控制服务器 POST /api/v1/accounts/status 的请求体结构。
+type accountsStatusRequest struct {
+ // NodeName 节点名称,用于在控制服务器端定位节点。
+ NodeName string `json:"node_name"`
+ // Accounts 本节点当前所有账号状态的完整快照,会覆盖服务器端旧数据。
+ Accounts []accountStatus `json:"accounts"`
+}
+
+// NodeAccount 是上报服务所需的最小账号信息结构,由 AccountLister 返回。
+type NodeAccount struct {
+ // ID 账号数据库主键。
+ ID int64
+ // Name 账号显示名称。
+ Name string
+ // Status 账号当前状态字符串。
+ Status string
+}
+
+// AccountLister 定义获取本地全量账号的接口,由 accountRepoLister 适配器实现。
+type AccountLister interface {
+ // ListAll 返回本节点所有账号,无论状态如何。
+ // 参数:
+ // - ctx:上下文,用于超时和取消控制。
+ // 返回值:
+ // - []NodeAccount:所有账号的快照切片。
+ // - error:查询失败时返回错误。
+ ListAll(ctx context.Context) ([]NodeAccount, error)
+}
+
+// Reporter 负责定期向控制服务器发送心跳,并在账号状态发生变化时上传完整快照。
+// 创建后调用 Start(ctx) 启动,ctx 取消时自动停止。
+type Reporter struct {
+ // cfg 节点配置,包含节点名称、控制服务器地址等信息。
+ cfg *Config
+ // client 向控制服务器发送 HTTP 请求的客户端。
+ client *controlClient
+ // lister 提供本地账号列表的接口实现。
+ lister AccountLister
+ // internalIP 上报心跳时携带的节点内网 IP。
+ internalIP string
+
+ mu sync.Mutex
+ // lastSnapshot 上次成功上传时的账号状态哈希字符串,用于变更检测。
+ lastSnapshot string
+ // pendingUpload 标记是否需要在下次心跳后立即上传账号状态(首次或上次上传失败时为 true)。
+ pendingUpload bool
+
+ // xiugai 添加节点功能
+ // bandwidth 独立后台带宽采样器,每 5 秒测量一次 1 秒窗口内的网络收发速率。
+ bandwidth *bandwidthSampler
+ // end
+}
+
+// NewReporter 创建并初始化 Reporter。
+// 节点内网 IP 始终通过 detectInternalIP() 从系统网络接口自动获取。
+//
+// 参数:
+// - cfg:节点配置对象,包含控制服务器地址和 TLS 证书路径等。
+// - lister:提供本地账号列表的 AccountLister 实现。
+//
+// 返回值:
+// - *Reporter:初始化完成的上报器实例。
+// - error:HTTP 客户端构建失败时返回错误。
+func NewReporter(cfg *Config, lister AccountLister) (*Reporter, error) {
+ client, err := newControlClient(cfg)
+ if err != nil {
+ return nil, fmt.Errorf("node reporter: build client: %w", err)
+ }
+
+ return &Reporter{
+ cfg: cfg,
+ client: client,
+ lister: lister,
+ internalIP: detectInternalIP(),
+ pendingUpload: true, // 首次启动时必须上传一次账号状态。
+ // xiugai 添加节点功能
+ bandwidth: newBandwidthSampler(),
+ // end
+ }, nil
+}
+
+// Start 启动心跳上报循环:立即执行一次,之后每 5 秒执行一次。
+// 该方法会阻塞,直到 ctx 被取消后退出。
+//
+// 参数:
+// - ctx:上下文,取消后上报循环停止。
+func (r *Reporter) Start(ctx context.Context) {
+ slog.Info("node reporter started",
+ "node", r.cfg.NodeName,
+ "control", r.cfg.ControlAddr,
+ "interval", heartbeatInterval,
+ )
+
+ // xiugai 添加节点功能
+ // 启动带宽采样后台 goroutine,与主心跳循环共享同一 ctx。
+ r.bandwidth.Start(ctx)
+ // end
+
+ ticker := time.NewTicker(heartbeatInterval)
+ defer ticker.Stop()
+
+ // 立即执行第一次上报,无需等待第一个 tick。
+ r.tick(ctx)
+
+ for {
+ select {
+ case <-ctx.Done():
+ slog.Info("node reporter stopped", "node", r.cfg.NodeName)
+ return
+ case <-ticker.C:
+ r.tick(ctx)
+ }
+ }
+}
+
+// tick 执行单次心跳上报,并在必要时上传账号状态。
+// 若心跳请求失败,则跳过账号上传(控制服务器要求先注册心跳才能接受账号状态)。
+//
+// 参数:
+// - ctx:父上下文,tick 内部会派生带超时的子上下文。
+func (r *Reporter) tick(ctx context.Context) {
+ // 为单次 tick 设置超时,预留 500ms 给网络 I/O,避免阻塞下次心跳。
+ tickCtx, cancel := context.WithTimeout(ctx, heartbeatInterval-500*time.Millisecond)
+ defer cancel()
+
+ metrics := collectSystemMetrics(tickCtx)
+
+ // xiugai 添加节点功能
+ // 读取最近一次带宽采样结果,随心跳一起上报。
+ bw := r.bandwidth.Latest()
+ // end
+
+ if err := r.client.post(tickCtx, "/heartbeat", heartbeatRequest{
+ InternalIP: r.internalIP,
+ NodeName: r.cfg.NodeName,
+ ListenPort: r.cfg.NodePort,
+ CPUUsage: metrics.CPUUsage,
+ MemUsage: metrics.MemUsage,
+ UploadBandwidth: bw.UploadMBps,
+ DownloadBandwidth: bw.DownloadMBps,
+ }); err != nil {
+ slog.Warn("node heartbeat failed", "node", r.cfg.NodeName, "error", err)
+ // 心跳失败时不尝试上传账号状态,因为控制服务器要求先有心跳记录。
+ return
+ }
+
+ r.maybeUploadAccounts(tickCtx)
+}
+
+// maybeUploadAccounts 从本地数据库读取全量账号,与上次快照对比。
+// 若内容发生变化(或标记了 pendingUpload),则向控制服务器上传完整账号状态列表。
+// 上传失败时设置 pendingUpload=true,下次 tick 时重试。
+//
+// 参数:
+// - ctx:请求上下文,用于超时控制。
+func (r *Reporter) maybeUploadAccounts(ctx context.Context) {
+ accounts, err := r.lister.ListAll(ctx)
+ if err != nil {
+ slog.Warn("node: list accounts failed", "error", err)
+ return
+ }
+
+ // 将 NodeAccount 转换为 accountStatus 上报结构。
+ statuses := make([]accountStatus, 0, len(accounts))
+ for _, a := range accounts {
+ statuses = append(statuses, accountStatus{
+ ID: strconv.FormatInt(a.ID, 10),
+ Name: a.Name,
+ Status: a.Status,
+ })
+ }
+
+ snapshot := buildSnapshot(statuses)
+
+ r.mu.Lock()
+ shouldUpload := r.pendingUpload || snapshot != r.lastSnapshot
+ r.mu.Unlock()
+
+ if !shouldUpload {
+ return
+ }
+
+ if err := r.client.post(ctx, "/accounts/status", accountsStatusRequest{
+ NodeName: r.cfg.NodeName,
+ Accounts: statuses,
+ }); err != nil {
+ slog.Warn("node: account status upload failed",
+ "node", r.cfg.NodeName, "error", err)
+ // 标记上传待重试,下次 tick 会再次尝试。
+ r.mu.Lock()
+ r.pendingUpload = true
+ r.mu.Unlock()
+ return
+ }
+
+ slog.Info("node: account status uploaded",
+ "node", r.cfg.NodeName, "count", len(statuses))
+
+ // 上传成功后保存本次快照,清除重试标记。
+ r.mu.Lock()
+ r.lastSnapshot = snapshot
+ r.pendingUpload = false
+ r.mu.Unlock()
+}
+
+// buildSnapshot 将账号状态列表转换为确定性字符串,用于变更检测。
+// 按 id 排序后拼接 "id:status\n",相同账号集合必然产生相同字符串。
+//
+// 参数:
+// - statuses:待生成快照的账号状态切片。
+//
+// 返回值:
+// - string:排序后拼接的快照字符串。
+func buildSnapshot(statuses []accountStatus) string {
+ pairs := make([]string, len(statuses))
+ for i, s := range statuses {
+ pairs[i] = s.ID + ":" + s.Status
+ }
+ sort.Strings(pairs)
+ out := ""
+ for _, p := range pairs {
+ out += p + "\n"
+ }
+ return out
+}
+
+// end
diff --git "a/\350\212\202\347\202\271\346\216\245\345\217\243\346\226\207\346\241\243.md" "b/\350\212\202\347\202\271\346\216\245\345\217\243\346\226\207\346\241\243.md"
new file mode 100644
index 00000000000..ee66cfd2b35
--- /dev/null
+++ "b/\350\212\202\347\202\271\346\216\245\345\217\243\346\226\207\346\241\243.md"
@@ -0,0 +1,515 @@
+# 节点控制服务接口文档
+
+## 1. 通用说明
+
+### 1.1 基础地址
+
+默认服务地址来自 `.env` 中的 `LISTEN_ADDR`,示例:
+
+```text
+https://127.0.0.1:8443
+```
+
+所有接口统一前缀:
+
+```text
+/api/v1
+```
+
+### 1.2 认证方式
+
+服务使用 mTLS 双向证书认证。
+
+客户端请求时需要携带客户端证书和私钥,并使用 CA 证书校验服务端证书。
+
+示例证书文件:
+
+```text
+certs/client.crt
+certs/client.key
+certs/ca.crt
+```
+
+如果客户端没有携带证书,或证书不是服务端信任的 CA 签发,TLS 握手会失败,请求不会进入 HTTP 接口。
+
+### 1.3 数据格式
+
+请求体和响应体均使用 JSON。
+
+请求头建议设置:
+
+```http
+Content-Type: application/json
+```
+
+### 1.4 节点唯一标识
+
+服务端使用下面两个字段共同定位一个节点:
+
+```text
+public_ip + node_name
+```
+
+其中:
+
+- `public_ip`:服务端从 TCP 连接的 `RemoteAddr` 中提取,不接收客户端传入值,也不会读取代理头。
+- `node_name`:客户端在请求体或路径参数中提供。
+
+Redis 中节点字段名格式:
+
+```text
+{public_ip}:{node_name}
+```
+
+### 1.5 节点状态
+
+节点状态字段为 `status`:
+
+| 值 | 说明 |
+|---|---|
+| `online` | 节点最近有心跳上报 |
+| `offline` | 节点超过离线阈值未上报心跳 |
+
+当前离线阈值为 60 秒。离线检查器每 10 秒扫描一次节点。
+
+---
+
+## 2. 上报节点心跳
+
+### 2.1 接口说明
+
+节点调用该接口上报基础运行状态。服务端收到后会把节点状态设置为 `online`,并刷新 `last_seen`。
+
+该接口不接收账号列表信息,账号状态需要调用单独的账号状态上传接口。
+
+### 2.2 请求信息
+
+```http
+POST /api/v1/heartbeat
+```
+
+### 2.3 请求参数
+
+请求体 JSON:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|---|---|---|---|
+| `internal_ip` | string | 是 | 节点内网 IP |
+| `node_name` | string | 是 | 节点名称 |
+| `listen_port` | number | 是 | 节点监听端口,范围 1 到 65535 |
+| `cpu_usage` | number | 否 | CPU 使用率 |
+| `mem_usage` | number | 否 | 内存使用率 |
+| `upload_bandwidth` | number | 否 | 当前系统上行带宽 |
+| `download_bandwidth` | number | 否 | 当前系统下行带宽 |
+| `today_requests` | number | 否 | 今日请求数 |
+| `total_requests` | number | 否 | 总请求数 |
+| `today_consumption` | number | 否 | 今日消费 |
+| `total_consumption` | number | 否 | 总消费 |
+| `rpm` | number | 否 | RPM 指标,每分钟请求数 |
+| `avg_response_time` | number | 否 | 平均响应时间 |
+
+### 2.4 请求示例
+
+```json
+{
+ "internal_ip": "10.0.0.12",
+ "node_name": "node-001",
+ "listen_port": 20001,
+ "cpu_usage": 23.5,
+ "mem_usage": 61.2,
+ "upload_bandwidth": 128.5,
+ "download_bandwidth": 512.3,
+ "today_requests": 1200,
+ "total_requests": 98000,
+ "today_consumption": 32.45,
+ "total_consumption": 2048.8,
+ "rpm": 86.5,
+ "avg_response_time": 315.2
+}
+```
+
+### 2.5 成功响应
+
+状态码:`200`
+
+```json
+{
+ "status": "ok"
+}
+```
+
+### 2.6 错误响应
+
+| 状态码 | 场景 | 响应示例 |
+|---|---|---|
+| `400` | JSON 格式错误或必填参数缺失 | `{"error":"..."}` |
+| `500` | 保存节点信息失败 | `{"error":"failed to save node info"}` |
+
+---
+
+## 3. 上传账号状态
+
+### 3.1 接口说明
+
+节点调用该接口上传账号状态列表。
+
+上传内容是该节点账号状态的完整快照。相同节点再次上传时,新的账号列表会整体覆盖 Redis 中旧的账号列表。
+
+节点必须先调用心跳接口完成注册,否则该接口返回 `404`。
+
+### 3.2 请求信息
+
+```http
+POST /api/v1/accounts/status
+```
+
+### 3.3 请求参数
+
+请求体 JSON:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|---|---|---|---|
+| `node_name` | string | 是 | 节点名称,用于和连接公网 IP 一起定位节点 |
+| `accounts` | array | 是 | 账号状态列表。传空数组表示该节点当前没有账号 |
+
+`accounts` 数组对象:
+
+| 参数名 | 类型 | 必填 | 说明 |
+|---|---|---|---|
+| `id` | string | 是 | 账号唯一标识 |
+| `name` | string | 否 | 账号显示名称 |
+| `status` | string | 是 | 账号当前状态,由节点侧定义 |
+
+### 3.4 请求示例
+
+```json
+{
+ "node_name": "node-001",
+ "accounts": [
+ {
+ "id": "account-001",
+ "name": "账号001",
+ "status": "online"
+ },
+ {
+ "id": "account-002",
+ "name": "账号002",
+ "status": "offline"
+ }
+ ]
+}
+```
+
+清空该节点账号列表:
+
+```json
+{
+ "node_name": "node-001",
+ "accounts": []
+}
+```
+
+### 3.5 成功响应
+
+状态码:`200`
+
+```json
+{
+ "status": "ok"
+}
+```
+
+### 3.6 错误响应
+
+| 状态码 | 场景 | 响应示例 |
+|---|---|---|
+| `400` | JSON 格式错误、必填参数缺失或 `node_name` 为空 | `{"error":"..."}` |
+| `404` | 节点不存在,需要先上报心跳 | `{"error":"node not found, send heartbeat first"}` |
+| `500` | 保存账号状态失败 | `{"error":"failed to save account status"}` |
+
+---
+
+## 4. 获取节点列表
+
+### 4.1 接口说明
+
+消费端调用该接口获取当前保存的全部节点基础信息。
+
+该接口只返回节点信息,不返回账号列表。账号列表需要调用“获取指定节点账号列表”接口。
+
+返回结果按 `public_ip`、`node_name` 升序排序。
+
+### 4.2 请求信息
+
+```http
+GET /api/v1/nodes
+```
+
+### 4.3 请求参数
+
+无请求体参数。
+
+### 4.4 成功响应
+
+状态码:`200`
+
+```json
+{
+ "nodes": [
+ {
+ "public_ip": "127.0.0.1",
+ "internal_ip": "10.0.0.12",
+ "node_name": "node-001",
+ "listen_port": 20001,
+ "cpu_usage": 23.5,
+ "mem_usage": 61.2,
+ "upload_bandwidth": 128.5,
+ "download_bandwidth": 512.3,
+ "today_requests": 1200,
+ "total_requests": 98000,
+ "today_consumption": 32.45,
+ "total_consumption": 2048.8,
+ "rpm": 86.5,
+ "avg_response_time": 315.2,
+ "status": "online",
+ "last_seen": "2026-05-27T17:00:00+08:00"
+ }
+ ]
+}
+```
+
+节点对象字段:
+
+| 字段名 | 类型 | 说明 |
+|---|---|---|
+| `public_ip` | string | 服务端从连接中识别出的节点公网 IP |
+| `internal_ip` | string | 节点上报的内网 IP |
+| `node_name` | string | 节点名称 |
+| `listen_port` | number | 节点监听端口 |
+| `cpu_usage` | number | CPU 使用率 |
+| `mem_usage` | number | 内存使用率 |
+| `upload_bandwidth` | number | 当前系统上行带宽 |
+| `download_bandwidth` | number | 当前系统下行带宽 |
+| `today_requests` | number | 今日请求数 |
+| `total_requests` | number | 总请求数 |
+| `today_consumption` | number | 今日消费 |
+| `total_consumption` | number | 总消费 |
+| `rpm` | number | RPM 指标,每分钟请求数 |
+| `avg_response_time` | number | 平均响应时间 |
+| `status` | string | 节点状态,`online` 或 `offline` |
+| `last_seen` | string | 服务端最近一次收到该节点心跳的时间 |
+
+### 4.5 错误响应
+
+| 状态码 | 场景 | 响应示例 |
+|---|---|---|
+| `500` | 获取节点列表失败 | `{"error":"failed to list nodes"}` |
+
+---
+
+## 5. 获取指定节点账号列表
+
+### 5.1 接口说明
+
+消费端调用该接口获取指定节点最近一次上传的账号状态列表。
+
+如果节点存在,但还没有上传过账号状态,接口返回空账号列表。
+
+### 5.2 请求信息
+
+```http
+GET /api/v1/nodes/:public_ip/:node_name/accounts
+```
+
+### 5.3 路径参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|---|---|---|---|
+| `public_ip` | string | 是 | 节点公网 IP |
+| `node_name` | string | 是 | 节点名称 |
+
+路径参数中如果包含特殊字符,需要进行 URL 编码。
+
+### 5.4 请求示例
+
+```http
+GET /api/v1/nodes/127.0.0.1/node-001/accounts
+```
+
+### 5.5 成功响应
+
+状态码:`200`
+
+```json
+{
+ "public_ip": "127.0.0.1",
+ "node_name": "node-001",
+ "accounts": [
+ {
+ "id": "account-001",
+ "name": "账号001",
+ "status": "online"
+ }
+ ],
+ "updated_at": "2026-05-27T17:01:00+08:00"
+}
+```
+
+响应字段:
+
+| 字段名 | 类型 | 说明 |
+|---|---|---|
+| `public_ip` | string | 节点公网 IP |
+| `node_name` | string | 节点名称 |
+| `accounts` | array | 节点最近一次上传的账号状态列表 |
+| `updated_at` | string | 服务端最近一次收到该节点账号状态的时间;未上传过时为空时间 |
+
+`accounts` 数组对象:
+
+| 字段名 | 类型 | 说明 |
+|---|---|---|
+| `id` | string | 账号唯一标识 |
+| `name` | string | 账号显示名称 |
+| `status` | string | 账号当前状态 |
+
+### 5.6 错误响应
+
+| 状态码 | 场景 | 响应示例 |
+|---|---|---|
+| `400` | 路径参数为空 | `{"error":"public_ip and node_name are required"}` |
+| `404` | 节点不存在 | `{"error":"node not found"}` |
+| `500` | 账号状态数据损坏 | `{"error":"account status data invalid"}` |
+| `500` | 查询账号状态失败 | `{"error":"failed to get account status"}` |
+
+---
+
+## 6. 删除离线节点
+
+### 6.1 接口说明
+
+删除指定节点,但只允许删除 `offline` 状态的节点。
+
+删除成功时,会同时删除该节点的账号状态数据。
+
+在线节点不能删除,会返回 `409`。
+
+### 6.2 请求信息
+
+```http
+DELETE /api/v1/nodes/:public_ip/:node_name
+```
+
+### 6.3 路径参数
+
+| 参数名 | 类型 | 必填 | 说明 |
+|---|---|---|---|
+| `public_ip` | string | 是 | 节点公网 IP |
+| `node_name` | string | 是 | 节点名称 |
+
+路径参数中如果包含特殊字符,需要进行 URL 编码。
+
+### 6.4 请求示例
+
+```http
+DELETE /api/v1/nodes/127.0.0.1/node-001
+```
+
+### 6.5 成功响应
+
+状态码:`200`
+
+```json
+{
+ "status": "deleted"
+}
+```
+
+### 6.6 错误响应
+
+| 状态码 | 场景 | 响应示例 |
+|---|---|---|
+| `400` | 路径参数为空 | `{"error":"public_ip and node_name are required"}` |
+| `404` | 节点不存在 | `{"error":"node not found"}` |
+| `409` | 节点不是离线状态,不能删除 | `{"error":"only offline nodes can be deleted"}` |
+| `500` | 删除节点失败 | `{"error":"failed to delete node"}` |
+
+---
+
+## 7. 调用示例
+
+### 7.1 curl 上报心跳
+
+```bash
+curl --cert certs/client.crt \
+ --key certs/client.key \
+ --cacert certs/ca.crt \
+ -X POST https://127.0.0.1:8443/api/v1/heartbeat \
+ -H "Content-Type: application/json" \
+ -d '{
+ "internal_ip": "10.0.0.12",
+ "node_name": "node-001",
+ "listen_port": 20001,
+ "cpu_usage": 23.5,
+ "mem_usage": 61.2,
+ "upload_bandwidth": 128.5,
+ "download_bandwidth": 512.3,
+ "today_requests": 1200,
+ "total_requests": 98000,
+ "today_consumption": 32.45,
+ "total_consumption": 2048.8,
+ "rpm": 86.5,
+ "avg_response_time": 315.2
+ }'
+```
+
+### 7.2 curl 上传账号状态
+
+```bash
+curl --cert certs/client.crt \
+ --key certs/client.key \
+ --cacert certs/ca.crt \
+ -X POST https://127.0.0.1:8443/api/v1/accounts/status \
+ -H "Content-Type: application/json" \
+ -d '{
+ "node_name": "node-001",
+ "accounts": [
+ {
+ "id": "account-001",
+ "name": "账号001",
+ "status": "online"
+ }
+ ]
+ }'
+```
+
+### 7.3 curl 获取节点列表
+
+```bash
+curl --cert certs/client.crt \
+ --key certs/client.key \
+ --cacert certs/ca.crt \
+ https://127.0.0.1:8443/api/v1/nodes
+```
+
+### 7.4 curl 获取节点账号列表
+
+```bash
+curl --cert certs/client.crt \
+ --key certs/client.key \
+ --cacert certs/ca.crt \
+ https://127.0.0.1:8443/api/v1/nodes/127.0.0.1/node-001/accounts
+```
+
+### 7.5 Python 模拟脚本
+
+模拟节点上报:
+
+```bash
+python pytest/simulate_nodes.py --count 5 --accounts-per-node 3 --once
+```
+
+模拟消费端读取:
+
+```bash
+python pytest/simulate_consumer.py
+```
From 8c3a5afcb47a8888dd77392fb1c4de7cef40fa2d Mon Sep 17 00:00:00 2001
From: lanlingxiawu <374397721@qq.com>
Date: Fri, 29 May 2026 12:18:31 +0800
Subject: [PATCH 5/6] =?UTF-8?q?=E5=90=8C=E6=AD=A5=E6=9C=80=E6=96=B0?=
=?UTF-8?q?=E4=BB=A3=E7=A0=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
同步最新代码
---
.../internal/handler/admin/group_handler.go | 14 --
backend/internal/handler/dto/mappers.go | 1 -
backend/internal/handler/dto/types.go | 1 -
backend/internal/handler/gateway_handler.go | 17 +--
.../gateway_handler_chat_completions.go | 12 +-
.../handler/gateway_handler_responses.go | 12 +-
.../handler/openai_chat_completions.go | 9 +-
.../handler/openai_gateway_handler.go | 33 +----
backend/internal/handler/openai_images.go | 23 +--
backend/internal/repository/api_key_repo.go | 24 +---
.../api_key_repo_last_used_unit_test.go | 3 +-
backend/internal/repository/group_repo.go | 130 +----------------
backend/internal/service/account.go | 73 +++-------
.../internal/service/account_wildcard_test.go | 30 ----
backend/internal/service/admin_service.go | 24 ----
.../internal/service/api_key_auth_cache.go | 1 -
.../service/api_key_auth_cache_impl.go | 2 -
.../service/api_key_service_cache_test.go | 8 --
backend/internal/service/channel_service.go | 118 ++--------------
.../internal/service/channel_service_test.go | 35 +----
backend/internal/service/group.go | 131 ------------------
backend/internal/service/group_test.go | 122 ----------------
.../openai_gateway_chat_completions.go | 5 +-
frontend/src/types/index.ts | 1 -
24 files changed, 64 insertions(+), 765 deletions(-)
diff --git a/backend/internal/handler/admin/group_handler.go b/backend/internal/handler/admin/group_handler.go
index 9dac422935c..dbf6f709a49 100644
--- a/backend/internal/handler/admin/group_handler.go
+++ b/backend/internal/handler/admin/group_handler.go
@@ -116,10 +116,6 @@ type CreateGroupRequest struct {
ModelsListConfig service.GroupModelsListConfig `json:"models_list_config"`
// 分组 RPM 上限(0 = 不限制)
RPMLimit int `json:"rpm_limit"`
- // xiugai 修改自动映射功能
- // 分组级模型映射(支持通配符 * 和正则 ~ 前缀),如 {"gpt-4*": "gpt-4o", "~^claude-.*": "claude-3-5-sonnet-20241022"}
- ModelMapping map[string]string `json:"model_mapping"`
- // xiugai end
// 从指定分组复制账号(创建后自动绑定)
CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"`
}
@@ -161,10 +157,6 @@ type UpdateGroupRequest struct {
ModelsListConfig *service.GroupModelsListConfig `json:"models_list_config"`
// 分组 RPM 上限(0 = 不限制);nil 表示未提供不改动
RPMLimit *int `json:"rpm_limit"`
- // xiugai 修改自动映射功能
- // 分组级模型映射(支持通配符 * 和正则 ~ 前缀),nil 表示不改动,空对象 {} 表示清除
- ModelMapping map[string]string `json:"model_mapping"`
- // xiugai end
// 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号)
CopyAccountsFromGroupIDs []int64 `json:"copy_accounts_from_group_ids"`
}
@@ -309,9 +301,6 @@ func (h *GroupHandler) Create(c *gin.Context) {
MessagesDispatchModelConfig: req.MessagesDispatchModelConfig,
ModelsListConfig: req.ModelsListConfig,
RPMLimit: req.RPMLimit,
- // xiugai 修改自动映射功能
- ModelMapping: req.ModelMapping,
- // xiugai end
CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs,
})
if err != nil {
@@ -368,9 +357,6 @@ func (h *GroupHandler) Update(c *gin.Context) {
MessagesDispatchModelConfig: req.MessagesDispatchModelConfig,
ModelsListConfig: req.ModelsListConfig,
RPMLimit: req.RPMLimit,
- // xiugai 修改自动映射功能
- ModelMapping: req.ModelMapping,
- // xiugai end
CopyAccountsFromGroupIDs: req.CopyAccountsFromGroupIDs,
})
if err != nil {
diff --git a/backend/internal/handler/dto/mappers.go b/backend/internal/handler/dto/mappers.go
index e5f9d7ae82b..51a11ea7782 100644
--- a/backend/internal/handler/dto/mappers.go
+++ b/backend/internal/handler/dto/mappers.go
@@ -144,7 +144,6 @@ func GroupFromServiceAdmin(g *service.Group) *AdminGroup {
Group: groupFromServiceBase(g),
ModelRouting: g.ModelRouting,
ModelRoutingEnabled: g.ModelRoutingEnabled,
- ModelMapping: g.ModelMapping,
MCPXMLInject: g.MCPXMLInject,
DefaultMappedModel: g.DefaultMappedModel,
MessagesDispatchModelConfig: g.MessagesDispatchModelConfig,
diff --git a/backend/internal/handler/dto/types.go b/backend/internal/handler/dto/types.go
index 82279d00d71..b1841c622eb 100644
--- a/backend/internal/handler/dto/types.go
+++ b/backend/internal/handler/dto/types.go
@@ -131,7 +131,6 @@ type AdminGroup struct {
// 模型路由配置(仅 anthropic 平台使用)
ModelRouting map[string][]int64 `json:"model_routing"`
ModelRoutingEnabled bool `json:"model_routing_enabled"`
- ModelMapping map[string]string `json:"model_mapping"`
// MCP XML 协议注入(仅 antigravity 平台使用)
MCPXMLInject bool `json:"mcp_xml_inject"`
diff --git a/backend/internal/handler/gateway_handler.go b/backend/internal/handler/gateway_handler.go
index 8427a553b27..a24611f91c7 100644
--- a/backend/internal/handler/gateway_handler.go
+++ b/backend/internal/handler/gateway_handler.go
@@ -160,19 +160,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
return
}
reqModel := parsedReq.Model
- clientReqModel := reqModel
reqStream := parsedReq.Stream
-
- // xiugai 修改自动映射功能
- // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
- if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
- body = service.ReplaceModelInBody(body, mapped)
- parsedReq.Body = body // 更新解析后的请求体,确保后续处理使用映射后的模型名
- parsedReq.Model = mapped
- reqModel = mapped
- }
- // xiugai end
-
reqLog = reqLog.With(zap.String("model", reqModel), zap.Bool("stream", reqStream))
// 解析渠道级模型映射
@@ -538,7 +526,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
RequestPayloadHash: requestPayloadHash,
ForceCacheBilling: fs.ForceCacheBilling,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFields(reqModel, result.UpstreamModel),
}); err != nil {
logger.L().With(
zap.String("component", "handler.gateway.messages"),
@@ -759,7 +747,6 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
if channelMapping.Mapped {
parsedReq.Model = channelMapping.MappedModel
parsedReq.Body = h.gatewayService.ReplaceModelInBody(parsedReq.Body, channelMapping.MappedModel)
- body = h.gatewayService.ReplaceModelInBody(body, channelMapping.MappedModel)
}
// Bedrock CC 兼容:渠道模型映射后,清理 Anthropic API 专有字段、注入 Bedrock 必需字段
parsedReq.Body = h.gatewayService.ApplyBedrockCCCompat(c.Request.Context(), parsedReq.Body, parsedReq.Model, account, apiKey.GroupID)
@@ -934,7 +921,7 @@ func (h *GatewayHandler) Messages(c *gin.Context) {
RequestPayloadHash: requestPayloadHash,
ForceCacheBilling: fs.ForceCacheBilling,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFields(reqModel, result.UpstreamModel),
}); err != nil {
logger.L().With(
zap.String("component", "handler.gateway.messages"),
diff --git a/backend/internal/handler/gateway_handler_chat_completions.go b/backend/internal/handler/gateway_handler_chat_completions.go
index fd4a5d9a8f1..acbdc261d20 100644
--- a/backend/internal/handler/gateway_handler_chat_completions.go
+++ b/backend/internal/handler/gateway_handler_chat_completions.go
@@ -75,16 +75,6 @@ func (h *GatewayHandler) ChatCompletions(c *gin.Context) {
return
}
reqModel := modelResult.String()
- clientReqModel := reqModel
-
- // xiugai 修改自动映射功能
- // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
- if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
- body = service.ReplaceModelInBody(body, mapped)
- reqModel = mapped
- }
- // xiugai end
-
reqStream := gjson.GetBytes(body, "stream").Bool()
reqLog = reqLog.With(zap.String("model", reqModel), zap.Bool("stream", reqStream))
@@ -316,7 +306,7 @@ func (h *GatewayHandler) ChatCompletions(c *gin.Context) {
IPAddress: clientIP,
RequestPayloadHash: requestPayloadHash,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFields(reqModel, result.UpstreamModel),
}); err != nil {
reqLog.Error("gateway.cc.record_usage_failed",
zap.Int64("account_id", account.ID),
diff --git a/backend/internal/handler/gateway_handler_responses.go b/backend/internal/handler/gateway_handler_responses.go
index 95a262bc2e6..6a083f317b9 100644
--- a/backend/internal/handler/gateway_handler_responses.go
+++ b/backend/internal/handler/gateway_handler_responses.go
@@ -75,16 +75,6 @@ func (h *GatewayHandler) Responses(c *gin.Context) {
return
}
reqModel := modelResult.String()
- clientReqModel := reqModel
-
- // xiugai 修改自动映射功能
- // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
- if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
- body = service.ReplaceModelInBody(body, mapped)
- reqModel = mapped
- }
- // xiugai end
-
reqStream := gjson.GetBytes(body, "stream").Bool()
reqLog = reqLog.With(zap.String("model", reqModel), zap.Bool("stream", reqStream))
@@ -291,7 +281,7 @@ func (h *GatewayHandler) Responses(c *gin.Context) {
IPAddress: clientIP,
RequestPayloadHash: requestPayloadHash,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFields(reqModel, result.UpstreamModel),
}); err != nil {
reqLog.Error("gateway.responses.record_usage_failed",
zap.Int64("account_id", account.ID),
diff --git a/backend/internal/handler/openai_chat_completions.go b/backend/internal/handler/openai_chat_completions.go
index c5d6b080ab0..9805bf8a703 100644
--- a/backend/internal/handler/openai_chat_completions.go
+++ b/backend/internal/handler/openai_chat_completions.go
@@ -74,13 +74,6 @@ func (h *OpenAIGatewayHandler) ChatCompletions(c *gin.Context) {
return
}
reqModel := modelResult.String()
- clientReqModel := reqModel
-
- if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
- body = service.ReplaceModelInBody(body, mapped)
- reqModel = mapped
- }
-
reqStream := gjson.GetBytes(body, "stream").Bool()
reqLog = reqLog.With(zap.String("model", reqModel), zap.Bool("stream", reqStream))
@@ -293,7 +286,7 @@ func (h *OpenAIGatewayHandler) ChatCompletions(c *gin.Context) {
UserAgent: userAgent,
IPAddress: clientIP,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFields(reqModel, result.UpstreamModel),
}); err != nil {
logger.L().With(
zap.String("component", "handler.openai_gateway.chat_completions"),
diff --git a/backend/internal/handler/openai_gateway_handler.go b/backend/internal/handler/openai_gateway_handler.go
index 336da21d231..979aaa1c484 100644
--- a/backend/internal/handler/openai_gateway_handler.go
+++ b/backend/internal/handler/openai_gateway_handler.go
@@ -159,7 +159,6 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
return
}
reqModel := modelResult.String()
- clientReqModel := reqModel
streamResult := gjson.GetBytes(body, "stream")
if streamResult.Exists() && streamResult.Type != gjson.True && streamResult.Type != gjson.False {
@@ -190,14 +189,6 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
return
}
- // xiugai 修改自动映射功能
- // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
- if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
- body = service.ReplaceModelInBody(body, mapped)
- reqModel = mapped
- }
- // xiugai end
-
setOpsRequestContext(c, reqModel, reqStream)
setOpsEndpointContext(c, "", int16(service.RequestTypeFromLegacy(reqStream, false)))
@@ -460,7 +451,7 @@ func (h *OpenAIGatewayHandler) Responses(c *gin.Context) {
IPAddress: clientIP,
RequestPayloadHash: requestPayloadHash,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFields(reqModel, result.UpstreamModel),
}); err != nil {
logger.L().With(
zap.String("component", "handler.openai_gateway.responses"),
@@ -623,16 +614,6 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
return
}
reqModel := modelResult.String()
- clientReqModel := reqModel
-
- // xiugai 修改自动映射功能
- // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
- if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
- body = service.ReplaceModelInBody(body, mapped)
- reqModel = mapped
- }
- // xiugai end
-
routingModel := service.NormalizeOpenAICompatRequestedModel(reqModel)
preferredMappedModel := resolveOpenAIMessagesDispatchMappedModel(apiKey, reqModel)
reqStream := gjson.GetBytes(body, "stream").Bool()
@@ -855,7 +836,7 @@ func (h *OpenAIGatewayHandler) Messages(c *gin.Context) {
IPAddress: clientIP,
RequestPayloadHash: requestPayloadHash,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMappingMsg.ToUsageFieldsFromClient(clientReqModel, reqModel, result.UpstreamModel),
+ ChannelUsageFields: channelMappingMsg.ToUsageFields(reqModel, result.UpstreamModel),
}); err != nil {
logger.L().With(
zap.String("component", "handler.openai_gateway.messages"),
@@ -1195,20 +1176,10 @@ func (h *OpenAIGatewayHandler) ResponsesWebSocket(c *gin.Context) {
}
reqModel := strings.TrimSpace(gjson.GetBytes(firstMessage, "model").String())
- clientReqModel := reqModel
if reqModel == "" {
closeOpenAIClientWS(wsConn, coderws.StatusPolicyViolation, "model is required in first response.create payload")
return
}
-
- // xiugai 修改自动映射功能
- // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
- if mapped, ok := apiKey.Group.ResolveGroupMappedModel(reqModel); ok {
- firstMessage = service.ReplaceModelInBody(firstMessage, mapped)
- reqModel = mapped
- }
- // xiugai end
-
previousResponseID := strings.TrimSpace(gjson.GetBytes(firstMessage, "previous_response_id").String())
previousResponseIDKind := service.ClassifyOpenAIPreviousResponseIDKind(previousResponseID)
if previousResponseID != "" && previousResponseIDKind == service.OpenAIPreviousResponseIDKindMessageID {
diff --git a/backend/internal/handler/openai_images.go b/backend/internal/handler/openai_images.go
index aab9842d4d5..bbb080149c2 100644
--- a/backend/internal/handler/openai_images.go
+++ b/backend/internal/handler/openai_images.go
@@ -62,20 +62,17 @@ func (h *OpenAIGatewayHandler) Images(c *gin.Context) {
return
}
- setOpsRequestContext(c, "", false)
+ if isMultipartImagesContentType(c.GetHeader("Content-Type")) {
+ setOpsRequestContext(c, "", false)
+ } else {
+ setOpsRequestContext(c, "", false)
+ }
+
parsed, err := h.gatewayService.ParseOpenAIImagesRequest(c, body)
if err != nil {
h.errorResponse(c, http.StatusBadRequest, "invalid_request_error", err.Error())
return
}
- clientReqModel := parsed.Model
-
- // xiugai 修改自动映射功能
- // 应用分组级模型映射(优先于渠道级,透明重写请求模型名)
- if mapped, ok := apiKey.Group.ResolveGroupMappedModel(parsed.Model); ok {
- parsed.Model = mapped
- }
- // xiugai end
reqLog = reqLog.With(
zap.String("model", parsed.Model),
@@ -100,7 +97,11 @@ func (h *OpenAIGatewayHandler) Images(c *gin.Context) {
defer imageReleaseFunc()
}
- setOpsRequestContext(c, parsed.Model, parsed.Stream)
+ if parsed.Multipart {
+ setOpsRequestContext(c, parsed.Model, parsed.Stream)
+ } else {
+ setOpsRequestContext(c, parsed.Model, parsed.Stream)
+ }
setOpsEndpointContext(c, "", int16(service.RequestTypeFromLegacy(parsed.Stream, false)))
channelMapping, _ := h.gatewayService.ResolveChannelMappingAndRestrict(c.Request.Context(), apiKey.GroupID, parsed.Model)
@@ -323,7 +324,7 @@ func (h *OpenAIGatewayHandler) Images(c *gin.Context) {
IPAddress: clientIP,
RequestPayloadHash: requestPayloadHash,
APIKeyService: h.apiKeyService,
- ChannelUsageFields: channelMapping.ToUsageFieldsFromClient(clientReqModel, parsed.Model, upstreamModel),
+ ChannelUsageFields: channelMapping.ToUsageFields(parsed.Model, upstreamModel),
}); err != nil {
logger.L().With(
zap.String("component", "handler.openai_gateway.images"),
diff --git a/backend/internal/repository/api_key_repo.go b/backend/internal/repository/api_key_repo.go
index a6a4b8843be..bfe09283002 100644
--- a/backend/internal/repository/api_key_repo.go
+++ b/backend/internal/repository/api_key_repo.go
@@ -81,13 +81,7 @@ func (r *apiKeyRepository) GetByID(ctx context.Context, id int64) (*service.APIK
}
return nil, err
}
- out := apiKeyEntityToService(m)
- // xiugai 修改自动映射功能
- if out.Group != nil {
- out.Group.ModelMapping = loadGroupModelMappingSQL(ctx, r.sql, out.Group.ID)
- }
- // xiugai end
- return out, nil
+ return apiKeyEntityToService(m), nil
}
// GetKeyAndOwnerID 根据 API Key ID 获取其 key 与所有者(用户)ID。
@@ -121,13 +115,7 @@ func (r *apiKeyRepository) GetByKey(ctx context.Context, key string) (*service.A
}
return nil, err
}
- out := apiKeyEntityToService(m)
- // xiugai 修改自动映射功能
- if out.Group != nil {
- out.Group.ModelMapping = loadGroupModelMappingSQL(ctx, r.sql, out.Group.ID)
- }
- // xiugai end
- return out, nil
+ return apiKeyEntityToService(m), nil
}
func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*service.APIKey, error) {
@@ -206,13 +194,7 @@ func (r *apiKeyRepository) GetByKeyForAuth(ctx context.Context, key string) (*se
}
return nil, err
}
- out := apiKeyEntityToService(m)
- // xiugai 修改自动映射功能
- if out.Group != nil {
- out.Group.ModelMapping = loadGroupModelMappingSQL(ctx, r.sql, out.Group.ID)
- }
- // xiugai end
- return out, nil
+ return apiKeyEntityToService(m), nil
}
func (r *apiKeyRepository) Update(ctx context.Context, key *service.APIKey) error {
diff --git a/backend/internal/repository/api_key_repo_last_used_unit_test.go b/backend/internal/repository/api_key_repo_last_used_unit_test.go
index 65fdf73a51f..7c6e2850e8e 100644
--- a/backend/internal/repository/api_key_repo_last_used_unit_test.go
+++ b/backend/internal/repository/api_key_repo_last_used_unit_test.go
@@ -30,8 +30,7 @@ func newAPIKeyRepoSQLite(t *testing.T) (*apiKeyRepository, *dbent.Client) {
client := enttest.NewClient(t, enttest.WithOptions(dbent.Driver(drv)))
t.Cleanup(func() { _ = client.Close() })
- // xiugai 修复 sql 字段为 nil 导致 loadGroupModelMappingSQL 空指针 panic
- return &apiKeyRepository{client: client, sql: db}, client // end
+ return &apiKeyRepository{client: client}, client
}
func mustCreateAPIKeyRepoUser(t *testing.T, ctx context.Context, client *dbent.Client, email string) *service.User {
diff --git a/backend/internal/repository/group_repo.go b/backend/internal/repository/group_repo.go
index 9fa0a233630..ac8669abdd9 100644
--- a/backend/internal/repository/group_repo.go
+++ b/backend/internal/repository/group_repo.go
@@ -3,7 +3,6 @@ package repository
import (
"context"
"database/sql"
- "encoding/json"
"errors"
"fmt"
"sort"
@@ -83,12 +82,6 @@ func (r *groupRepository) Create(ctx context.Context, groupIn *service.Group) er
groupIn.ID = created.ID
groupIn.CreatedAt = created.CreatedAt
groupIn.UpdatedAt = created.UpdatedAt
- // xiugai 修改自动映射功能
- if setErr := r.setGroupModelMapping(ctx, groupIn.ID, groupIn.ModelMapping); setErr != nil {
- logger.LegacyPrintf("repository.group", "[ModelMapping] set failed on create: group=%d err=%v", groupIn.ID, setErr)
- return fmt.Errorf("set group model mapping: %w", setErr)
- }
- // xiugai end
if err := enqueueSchedulerOutbox(ctx, r.sql, service.SchedulerOutboxEventGroupChanged, nil, &groupIn.ID, nil); err != nil {
logger.LegacyPrintf("repository.group", "[SchedulerOutbox] enqueue group create failed: group=%d err=%v", groupIn.ID, err)
}
@@ -96,95 +89,6 @@ func (r *groupRepository) Create(ctx context.Context, groupIn *service.Group) er
return translatePersistenceError(err, nil, service.ErrGroupExists)
}
-// setGroupModelMapping 用 raw SQL 写入 model_mapping 列(ENT 不感知此列)。
-func (r *groupRepository) setGroupModelMapping(ctx context.Context, id int64, mapping map[string]string) error {
- return setGroupModelMappingSQL(ctx, r.sql, id, mapping)
-}
-
-// loadGroupModelMapping 用 raw SQL 读取单个分组的 model_mapping。
-func (r *groupRepository) loadGroupModelMapping(ctx context.Context, id int64) map[string]string {
- return loadGroupModelMappingSQL(ctx, r.sql, id)
-}
-
-// loadGroupModelMappings 批量读取多个分组的 model_mapping,返回 map[groupID]mapping。
-func (r *groupRepository) loadGroupModelMappings(ctx context.Context, ids []int64) map[int64]map[string]string {
- return loadGroupModelMappingsSQL(ctx, r.sql, ids)
-}
-
-// xiugai 修改自动映射功能
-// setGroupModelMappingSQL 是包级辅助函数,可被同包其他 repo 调用。
-func setGroupModelMappingSQL(ctx context.Context, db sqlExecutor, id int64, mapping map[string]string) error {
- data := mapping
- if data == nil {
- data = map[string]string{}
- }
- b, err := json.Marshal(data)
- if err != nil {
- return err
- }
- _, err = db.ExecContext(ctx, `UPDATE groups SET model_mapping = $1 WHERE id = $2`, string(b), id)
- return err
-}
-
-// loadGroupModelMappingSQL 是包级辅助函数,可被同包其他 repo 调用。
-func loadGroupModelMappingSQL(ctx context.Context, db sqlExecutor, id int64) map[string]string {
- rows, err := db.QueryContext(ctx, `SELECT model_mapping FROM groups WHERE id = $1 AND deleted_at IS NULL`, id)
- if err != nil {
- return nil
- }
- defer rows.Close()
- if !rows.Next() {
- return nil
- }
- var raw string
- if err := rows.Scan(&raw); err != nil {
- return nil
- }
- var m map[string]string
- if err := json.Unmarshal([]byte(raw), &m); err != nil {
- return nil
- }
- return m
-}
-
-// loadGroupModelMappingsSQL 是包级辅助函数,批量读取多个分组的 model_mapping。
-func loadGroupModelMappingsSQL(ctx context.Context, db sqlExecutor, ids []int64) map[int64]map[string]string {
- if len(ids) == 0 {
- return nil
- }
- placeholders := make([]string, len(ids))
- args := make([]any, len(ids))
- for i, id := range ids {
- placeholders[i] = fmt.Sprintf("$%d", i+1)
- args[i] = id
- }
- query := fmt.Sprintf(
- `SELECT id, model_mapping FROM groups WHERE id IN (%s) AND deleted_at IS NULL`,
- strings.Join(placeholders, ","),
- )
- rows, err := db.QueryContext(ctx, query, args...)
- if err != nil {
- return nil
- }
- defer rows.Close()
- result := make(map[int64]map[string]string, len(ids))
- for rows.Next() {
- var gid int64
- var raw string
- if err := rows.Scan(&gid, &raw); err != nil {
- continue
- }
- var m map[string]string
- if err := json.Unmarshal([]byte(raw), &m); err != nil {
- continue
- }
- result[gid] = m
- }
- return result
-}
-
-// xiugai end
-
func (r *groupRepository) GetByID(ctx context.Context, id int64) (*service.Group, error) {
out, err := r.GetByIDLite(ctx, id)
if err != nil {
@@ -208,11 +112,7 @@ func (r *groupRepository) GetByIDLite(ctx context.Context, id int64) (*service.G
if err != nil {
return nil, translatePersistenceError(err, service.ErrGroupNotFound, nil)
}
- out := groupEntityToService(m)
- // xiugai 修改自动映射功能
- out.ModelMapping = r.loadGroupModelMapping(ctx, id)
- // xiugai end
- return out, nil
+ return groupEntityToService(m), nil
}
func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) error {
@@ -305,12 +205,6 @@ func (r *groupRepository) Update(ctx context.Context, groupIn *service.Group) er
return translatePersistenceError(err, service.ErrGroupNotFound, service.ErrGroupExists)
}
groupIn.UpdatedAt = updated.UpdatedAt
- // xiugai 修改自动映射功能
- if setErr := r.setGroupModelMapping(ctx, groupIn.ID, groupIn.ModelMapping); setErr != nil {
- logger.LegacyPrintf("repository.group", "[ModelMapping] set failed on update: group=%d err=%v", groupIn.ID, setErr)
- return fmt.Errorf("set group model mapping: %w", setErr)
- }
- // xiugai end
if err := enqueueSchedulerOutbox(ctx, r.sql, service.SchedulerOutboxEventGroupChanged, nil, &groupIn.ID, nil); err != nil {
logger.LegacyPrintf("repository.group", "[SchedulerOutbox] enqueue group update failed: group=%d err=%v", groupIn.ID, err)
}
@@ -389,12 +283,6 @@ func (r *groupRepository) ListWithFilters(ctx context.Context, params pagination
outGroups[i].RateLimitedAccountCount = c.RateLimited
}
}
- // xiugai 修改自动映射功能
- mappings := r.loadGroupModelMappings(ctx, groupIDs)
- for i := range outGroups {
- outGroups[i].ModelMapping = mappings[outGroups[i].ID]
- }
- // xiugai end
return outGroups, paginationResultFromTotal(int64(total), params), nil
}
@@ -472,20 +360,16 @@ func (r *groupRepository) listWithAccountCountSort(ctx context.Context, q *dbent
}
outGroups := make([]service.Group, len(page))
- // xiugai 修改自动映射功能
- mappings := r.loadGroupModelMappings(ctx, pageIDs)
for i := range groups {
g := groupEntityToService(groups[i])
c := counts[g.ID]
g.AccountCount = c.Total
g.ActiveAccountCount = c.Active
g.RateLimitedAccountCount = c.RateLimited
- g.ModelMapping = mappings[g.ID]
if idx, ok := pageIdx[g.ID]; ok {
outGroups[idx] = *g
}
}
- // xiugai end
return outGroups, paginationResultFromTotal(int64(total), params), nil
}
@@ -570,12 +454,6 @@ func (r *groupRepository) ListActive(ctx context.Context) ([]service.Group, erro
outGroups[i].RateLimitedAccountCount = c.RateLimited
}
}
- // xiugai 修改自动映射功能
- mappings := r.loadGroupModelMappings(ctx, groupIDs)
- for i := range outGroups {
- outGroups[i].ModelMapping = mappings[outGroups[i].ID]
- }
- // xiugai end
return outGroups, nil
}
@@ -606,12 +484,6 @@ func (r *groupRepository) ListActiveByPlatform(ctx context.Context, platform str
outGroups[i].RateLimitedAccountCount = c.RateLimited
}
}
- // xiugai 修改自动映射功能
- mappings := r.loadGroupModelMappings(ctx, groupIDs)
- for i := range outGroups {
- outGroups[i].ModelMapping = mappings[outGroups[i].ID]
- }
- // xiugai end
return outGroups, nil
}
diff --git a/backend/internal/service/account.go b/backend/internal/service/account.go
index e2e6c5074f7..9884a83904f 100644
--- a/backend/internal/service/account.go
+++ b/backend/internal/service/account.go
@@ -453,23 +453,28 @@ func stringMappingFromRaw(raw any) map[string]string {
}
func (a *Account) GetModelMapping() map[string]string {
- credentialsPtr := mapPointer(a.Credentials)
- var rawMapping any
- if a.Credentials != nil {
- rawMapping = a.Credentials["model_mapping"]
- }
- rawPtr, rawLen, rawSig := modelMappingRawState(rawMapping)
+ credentialsPtr := mapPtr(a.Credentials)
+ rawMapping, _ := a.Credentials["model_mapping"].(map[string]any)
+ rawPtr := mapPtr(rawMapping)
+ rawLen := len(rawMapping)
+ rawSig := uint64(0)
+ rawSigReady := false
if a.modelMappingCacheReady &&
a.modelMappingCacheCredentialsPtr == credentialsPtr &&
a.modelMappingCacheRawPtr == rawPtr &&
a.modelMappingCacheRawLen == rawLen {
+ rawSig = modelMappingSignature(rawMapping)
+ rawSigReady = true
if a.modelMappingCacheRawSig == rawSig {
return a.modelMappingCache
}
}
- mapping := a.resolveModelMapping(stringMappingFromRaw(rawMapping))
+ mapping := a.resolveModelMapping(rawMapping)
+ if !rawSigReady {
+ rawSig = modelMappingSignature(rawMapping)
+ }
a.modelMappingCache = mapping
a.modelMappingCacheReady = true
@@ -480,7 +485,7 @@ func (a *Account) GetModelMapping() map[string]string {
return mapping
}
-func (a *Account) resolveModelMapping(rawMapping map[string]string) map[string]string {
+func (a *Account) resolveModelMapping(rawMapping map[string]any) map[string]string {
if a.Credentials == nil {
// Antigravity 平台使用默认映射
if a.Platform == domain.PlatformAntigravity {
@@ -499,8 +504,8 @@ func (a *Account) resolveModelMapping(rawMapping map[string]string) map[string]s
result := make(map[string]string)
for k, v := range rawMapping {
- if v != "" {
- result[k] = v
+ if s, ok := v.(string); ok {
+ result[k] = s
}
}
if len(result) > 0 {
@@ -521,29 +526,14 @@ func (a *Account) resolveModelMapping(rawMapping map[string]string) map[string]s
return nil
}
-func mapPointer(raw any) uintptr {
- if raw == nil {
- return 0
- }
- v := reflect.ValueOf(raw)
- if v.Kind() != reflect.Map || v.IsNil() {
+func mapPtr(m map[string]any) uintptr {
+ if m == nil {
return 0
}
- return v.Pointer()
-}
-
-func modelMappingRawState(raw any) (uintptr, int, uint64) {
- switch mapping := raw.(type) {
- case map[string]any:
- return mapPointer(mapping), len(mapping), modelMappingAnySignature(mapping)
- case map[string]string:
- return mapPointer(mapping), len(mapping), modelMappingStringSignature(mapping)
- default:
- return 0, 0, 0
- }
+ return reflect.ValueOf(m).Pointer()
}
-func modelMappingAnySignature(rawMapping map[string]any) uint64 {
+func modelMappingSignature(rawMapping map[string]any) uint64 {
if len(rawMapping) == 0 {
return 0
}
@@ -567,26 +557,6 @@ func modelMappingAnySignature(rawMapping map[string]any) uint64 {
return h.Sum64()
}
-func modelMappingStringSignature(rawMapping map[string]string) uint64 {
- if len(rawMapping) == 0 {
- return 0
- }
- keys := make([]string, 0, len(rawMapping))
- for k := range rawMapping {
- keys = append(keys, k)
- }
- sort.Strings(keys)
-
- h := fnv.New64a()
- for _, k := range keys {
- _, _ = h.Write([]byte(k))
- _, _ = h.Write([]byte{0})
- _, _ = h.Write([]byte(rawMapping[k]))
- _, _ = h.Write([]byte{0xff})
- }
- return h.Sum64()
-}
-
func ensureAntigravityDefaultPassthrough(mapping map[string]string, model string) {
if mapping == nil || model == "" {
return
@@ -641,7 +611,7 @@ func resolveRequestedModelInMapping(mapping map[string]string, requestedModel st
if requestedModel == "" {
return "", false
}
- if mappedModel, exists := mapping[requestedModel]; exists && mappedModel != "" {
+ if mappedModel, exists := mapping[requestedModel]; exists {
return mappedModel, true
}
return matchWildcardMappingResult(mapping, requestedModel)
@@ -841,9 +811,6 @@ func matchWildcardMappingResult(mapping map[string]string, requestedModel string
var matches []patternMatch
for pattern, target := range mapping {
- if target == "" {
- continue
- }
if matchWildcard(pattern, requestedModel) {
matches = append(matches, patternMatch{pattern, target})
}
diff --git a/backend/internal/service/account_wildcard_test.go b/backend/internal/service/account_wildcard_test.go
index b139780976c..d903b940a51 100644
--- a/backend/internal/service/account_wildcard_test.go
+++ b/backend/internal/service/account_wildcard_test.go
@@ -4,8 +4,6 @@ package service
import (
"testing"
-
- "github.com/stretchr/testify/require"
)
func TestMatchWildcard(t *testing.T) {
@@ -527,31 +525,3 @@ func TestAccountGetModelMapping_CacheInvalidatesOnInPlaceValueChange(t *testing.
t.Fatalf("expected cache invalidated after in-place value change, got: %v", second)
}
}
-
-func TestAccountGetModelMapping_AcceptsStringMap(t *testing.T) {
- account := &Account{
- Credentials: map[string]any{
- "model_mapping": map[string]string{
- "gpt-5": "gpt-5.4",
- },
- },
- }
-
- require.Equal(t, "gpt-5.4", account.GetMappedModel("gpt-5"))
-}
-
-func TestAccountGetMappedModel_EmptyTargetSkipped(t *testing.T) {
- account := &Account{
- Credentials: map[string]any{
- "model_mapping": map[string]any{
- "gpt-5": "",
- "gpt-*": "gpt-5.4",
- "bad-*": "",
- "other": "other-upstream",
- },
- },
- }
-
- require.Equal(t, "gpt-5.4", account.GetMappedModel("gpt-5"))
- require.Equal(t, "bad-model", account.GetMappedModel("bad-model"))
-}
diff --git a/backend/internal/service/admin_service.go b/backend/internal/service/admin_service.go
index de4829b0335..d46b636f2cb 100644
--- a/backend/internal/service/admin_service.go
+++ b/backend/internal/service/admin_service.go
@@ -223,10 +223,6 @@ type CreateGroupInput struct {
ModelsListConfig GroupModelsListConfig
// RPMLimit 分组 RPM 上限(0 = 不限制)
RPMLimit int
- // xiugai 修改自动映射功能
- // 分组级模型映射(支持通配符和正则)
- ModelMapping map[string]string
- // xiugai end
// 从指定分组复制账号(创建分组后在同一事务内绑定)
CopyAccountsFromGroupIDs []int64
}
@@ -268,10 +264,6 @@ type UpdateGroupInput struct {
ModelsListConfig *GroupModelsListConfig
// RPMLimit 分组 RPM 上限(0 = 不限制),nil 表示未提供不改动。
RPMLimit *int
- // xiugai 修改自动映射功能
- // 分组级模型映射(支持通配符和正则),nil 表示未提供不改动
- ModelMapping map[string]string
- // xiugai end
// 从指定分组复制账号(同步操作:先清空当前分组的账号绑定,再绑定源分组的账号)
CopyAccountsFromGroupIDs []int64
}
@@ -1725,10 +1717,6 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
if input.MCPXMLInject != nil {
mcpXMLInject = *input.MCPXMLInject
}
- modelMapping, err := NormalizeGroupModelMapping(input.ModelMapping)
- if err != nil {
- return nil, err
- }
// 如果指定了复制账号的源分组,先获取账号 ID 列表
var accountIDsToCopy []int64
@@ -1792,9 +1780,6 @@ func (s *adminServiceImpl) CreateGroup(ctx context.Context, input *CreateGroupIn
MessagesDispatchModelConfig: normalizeOpenAIMessagesDispatchModelConfig(input.MessagesDispatchModelConfig),
ModelsListConfig: normalizeGroupModelsListConfig(input.ModelsListConfig),
RPMLimit: input.RPMLimit,
- // xiugai 修改自动映射功能
- ModelMapping: modelMapping,
- // xiugai end
}
sanitizeGroupMessagesDispatchFields(group)
if err := s.groupRepo.Create(ctx, group); err != nil {
@@ -2047,15 +2032,6 @@ func (s *adminServiceImpl) UpdateGroup(ctx context.Context, id int64, input *Upd
if input.RPMLimit != nil {
group.RPMLimit = *input.RPMLimit
}
- // xiugai 修改自动映射功能
- if input.ModelMapping != nil {
- modelMapping, err := NormalizeGroupModelMapping(input.ModelMapping)
- if err != nil {
- return nil, err
- }
- group.ModelMapping = modelMapping
- }
- // xiugai end
sanitizeGroupMessagesDispatchFields(group)
if err := s.groupRepo.Update(ctx, group); err != nil {
diff --git a/backend/internal/service/api_key_auth_cache.go b/backend/internal/service/api_key_auth_cache.go
index 4fd609dfd2e..74163179c8f 100644
--- a/backend/internal/service/api_key_auth_cache.go
+++ b/backend/internal/service/api_key_auth_cache.go
@@ -78,7 +78,6 @@ type APIKeyAuthGroupSnapshot struct {
// Only anthropic groups use these fields; others may leave them empty.
ModelRouting map[string][]int64 `json:"model_routing,omitempty"`
ModelRoutingEnabled bool `json:"model_routing_enabled"`
- ModelMapping map[string]string `json:"model_mapping,omitempty"`
MCPXMLInject bool `json:"mcp_xml_inject"`
// 支持的模型系列(仅 antigravity 平台使用)
diff --git a/backend/internal/service/api_key_auth_cache_impl.go b/backend/internal/service/api_key_auth_cache_impl.go
index 956cea12d3e..69c6086f75d 100644
--- a/backend/internal/service/api_key_auth_cache_impl.go
+++ b/backend/internal/service/api_key_auth_cache_impl.go
@@ -267,7 +267,6 @@ func (s *APIKeyService) snapshotFromAPIKey(ctx context.Context, apiKey *APIKey)
FallbackGroupIDOnInvalidRequest: apiKey.Group.FallbackGroupIDOnInvalidRequest,
ModelRouting: apiKey.Group.ModelRouting,
ModelRoutingEnabled: apiKey.Group.ModelRoutingEnabled,
- ModelMapping: apiKey.Group.ModelMapping,
MCPXMLInject: apiKey.Group.MCPXMLInject,
SupportedModelScopes: apiKey.Group.SupportedModelScopes,
AllowMessagesDispatch: apiKey.Group.AllowMessagesDispatch,
@@ -339,7 +338,6 @@ func (s *APIKeyService) snapshotToAPIKey(key string, snapshot *APIKeyAuthSnapsho
FallbackGroupIDOnInvalidRequest: snapshot.Group.FallbackGroupIDOnInvalidRequest,
ModelRouting: snapshot.Group.ModelRouting,
ModelRoutingEnabled: snapshot.Group.ModelRoutingEnabled,
- ModelMapping: snapshot.Group.ModelMapping,
MCPXMLInject: snapshot.Group.MCPXMLInject,
SupportedModelScopes: snapshot.Group.SupportedModelScopes,
AllowMessagesDispatch: snapshot.Group.AllowMessagesDispatch,
diff --git a/backend/internal/service/api_key_service_cache_test.go b/backend/internal/service/api_key_service_cache_test.go
index 9a04fcfb9b2..eaac9a1c898 100644
--- a/backend/internal/service/api_key_service_cache_test.go
+++ b/backend/internal/service/api_key_service_cache_test.go
@@ -211,9 +211,6 @@ func TestAPIKeyService_GetByKey_UsesL2Cache(t *testing.T) {
ModelRouting: map[string][]int64{
"claude-opus-*": {1, 2},
},
- ModelMapping: map[string]string{
- "gpt-5": "gpt-5.4",
- },
},
},
}
@@ -228,7 +225,6 @@ func TestAPIKeyService_GetByKey_UsesL2Cache(t *testing.T) {
require.Equal(t, groupID, apiKey.Group.ID)
require.True(t, apiKey.Group.ModelRoutingEnabled)
require.Equal(t, map[string][]int64{"claude-opus-*": {1, 2}}, apiKey.Group.ModelRouting)
- require.Equal(t, map[string]string{"gpt-5": "gpt-5.4"}, apiKey.Group.ModelMapping)
}
func TestAPIKeyService_SnapshotRoundTrip_PreservesMessagesDispatchModelConfig(t *testing.T) {
@@ -257,9 +253,6 @@ func TestAPIKeyService_SnapshotRoundTrip_PreservesMessagesDispatchModelConfig(t
RateMultiplier: 1,
AllowMessagesDispatch: true,
DefaultMappedModel: "gpt-5.4",
- ModelMapping: map[string]string{
- "gpt-5": "gpt-5.4",
- },
MessagesDispatchModelConfig: OpenAIMessagesDispatchModelConfig{
OpusMappedModel: "gpt-5.4-nano",
SonnetMappedModel: "gpt-5.3-codex",
@@ -277,7 +270,6 @@ func TestAPIKeyService_SnapshotRoundTrip_PreservesMessagesDispatchModelConfig(t
require.NotNil(t, roundTrip)
require.Equal(t, apiKey.Name, roundTrip.Name)
require.NotNil(t, roundTrip.Group)
- require.Equal(t, apiKey.Group.ModelMapping, roundTrip.Group.ModelMapping)
require.Equal(t, apiKey.Group.MessagesDispatchModelConfig, roundTrip.Group.MessagesDispatchModelConfig)
}
diff --git a/backend/internal/service/channel_service.go b/backend/internal/service/channel_service.go
index 31f345f840a..4bf0147f38c 100644
--- a/backend/internal/service/channel_service.go
+++ b/backend/internal/service/channel_service.go
@@ -4,7 +4,6 @@ import (
"context"
"fmt"
"log/slog"
- "sort"
"strings"
"sync/atomic"
"time"
@@ -106,59 +105,31 @@ type ChannelMappingResult struct {
// upstreamModel: 上游实际使用的模型名(ForwardResult.UpstreamModel)。
// 返回空字符串表示无映射。
func (r ChannelMappingResult) BuildModelMappingChain(reqModel, upstreamModel string) string {
- channelReqModel := reqModel
- return r.buildModelMappingChainFromClient(reqModel, channelReqModel, upstreamModel)
+ if !r.Mapped {
+ if upstreamModel != "" && upstreamModel != reqModel {
+ return reqModel + "→" + upstreamModel
+ }
+ return ""
+ }
+ if upstreamModel != "" && upstreamModel != r.MappedModel {
+ return reqModel + "→" + r.MappedModel + "→" + upstreamModel
+ }
+ return reqModel + "→" + r.MappedModel
}
// ToUsageFields 将渠道映射结果转为使用记录字段
func (r ChannelMappingResult) ToUsageFields(reqModel, upstreamModel string) ChannelUsageFields {
- return r.ToUsageFieldsFromClient(reqModel, reqModel, upstreamModel)
-}
-
-func (r ChannelMappingResult) ToUsageFieldsFromClient(clientReqModel, channelReqModel, upstreamModel string) ChannelUsageFields {
- channelMappedModel := channelReqModel
+ channelMappedModel := reqModel
if r.Mapped {
channelMappedModel = r.MappedModel
}
return ChannelUsageFields{
ChannelID: r.ChannelID,
- OriginalModel: clientReqModel,
+ OriginalModel: reqModel,
ChannelMappedModel: channelMappedModel,
BillingModelSource: r.BillingModelSource,
- ModelMappingChain: r.buildModelMappingChainFromClient(clientReqModel, channelReqModel, upstreamModel),
- }
-}
-
-func (r ChannelMappingResult) buildModelMappingChainFromClient(clientReqModel, channelReqModel, upstreamModel string) string {
- models := []string{clientReqModel}
- if channelReqModel != "" && channelReqModel != clientReqModel {
- models = append(models, channelReqModel)
- }
- if r.Mapped {
- models = append(models, r.MappedModel)
+ ModelMappingChain: r.BuildModelMappingChain(reqModel, upstreamModel),
}
- if upstreamModel != "" {
- models = append(models, upstreamModel)
- }
- return compactModelMappingChain(models...)
-}
-
-func compactModelMappingChain(models ...string) string {
- cleaned := make([]string, 0, len(models))
- for _, model := range models {
- model = strings.TrimSpace(model)
- if model == "" {
- continue
- }
- if len(cleaned) > 0 && cleaned[len(cleaned)-1] == model {
- continue
- }
- cleaned = append(cleaned, model)
- }
- if len(cleaned) < 2 {
- return ""
- }
- return strings.Join(cleaned, "\u2192")
}
const (
@@ -354,23 +325,10 @@ func populateChannelCache(channels []Channel, groupPlatforms map[int64]string) *
expandMappingToCache(cache, ch, gid, platform)
}
}
- sortChannelWildcardMappings(cache)
return cache
}
-func sortChannelWildcardMappings(cache *channelCache) {
- for key := range cache.wildcardMappingByGP {
- wildcards := cache.wildcardMappingByGP[key]
- sort.SliceStable(wildcards, func(i, j int) bool {
- if len(wildcards[i].prefix) != len(wildcards[j].prefix) {
- return len(wildcards[i].prefix) > len(wildcards[j].prefix)
- }
- return wildcards[i].prefix < wildcards[j].prefix
- })
- }
-}
-
// invalidateCache 使缓存失效,让下次读取时自然重建
// isPlatformPricingMatch 判断定价条目的平台是否匹配分组平台。
@@ -720,10 +678,6 @@ func (s *ChannelService) Create(ctx context.Context, input *CreateChannelInput)
if err := s.checkGroupConflicts(ctx, 0, input.GroupIDs); err != nil {
return nil, err
}
- modelMapping, err := normalizeChannelModelMapping(input.ModelMapping)
- if err != nil {
- return nil, err
- }
channel := &Channel{
Name: input.Name,
@@ -733,7 +687,7 @@ func (s *ChannelService) Create(ctx context.Context, input *CreateChannelInput)
RestrictModels: input.RestrictModels,
GroupIDs: input.GroupIDs,
ModelPricing: input.ModelPricing,
- ModelMapping: modelMapping,
+ ModelMapping: input.ModelMapping,
Features: input.Features,
FeaturesConfig: input.FeaturesConfig,
ApplyPricingToAccountStats: input.ApplyPricingToAccountStats,
@@ -845,11 +799,7 @@ func (s *ChannelService) applyUpdateInput(ctx context.Context, channel *Channel,
channel.ModelPricing = *input.ModelPricing
}
if input.ModelMapping != nil {
- modelMapping, err := normalizeChannelModelMapping(input.ModelMapping)
- if err != nil {
- return err
- }
- channel.ModelMapping = modelMapping
+ channel.ModelMapping = input.ModelMapping
}
if input.BillingModelSource != "" {
channel.BillingModelSource = input.BillingModelSource
@@ -985,44 +935,6 @@ func validateNoConflictingModels(pricingList []ChannelModelPricing) error {
return nil
}
-func normalizeChannelModelMapping(mapping map[string]map[string]string) (map[string]map[string]string, error) {
- if mapping == nil {
- return nil, nil
- }
- normalized := make(map[string]map[string]string, len(mapping))
- for platform, platformMapping := range mapping {
- platform = strings.TrimSpace(platform)
- if platform == "" {
- return nil, infraerrors.BadRequest("INVALID_MAPPING", "mapping platform cannot be empty")
- }
- dstMapping := make(map[string]string, len(platformMapping))
- for source, target := range platformMapping {
- source = strings.TrimSpace(source)
- target = strings.TrimSpace(target)
- if source == "" {
- return nil, infraerrors.BadRequest("INVALID_MAPPING", fmt.Sprintf("mapping source cannot be empty in platform '%s'", platform))
- }
- if target == "" {
- return nil, infraerrors.BadRequest("INVALID_MAPPING", fmt.Sprintf("mapping target for '%s' cannot be empty in platform '%s'", source, platform))
- }
- if strings.Contains(target, "*") {
- return nil, infraerrors.BadRequest("INVALID_MAPPING", fmt.Sprintf("mapping target for '%s' cannot contain wildcard in platform '%s'", source, platform))
- }
- if count := strings.Count(source, "*"); count > 0 {
- if count > 1 || !strings.HasSuffix(source, "*") {
- return nil, infraerrors.BadRequest("INVALID_MAPPING", fmt.Sprintf("mapping wildcard source '%s' must use a single trailing * in platform '%s'", source, platform))
- }
- }
- if _, exists := dstMapping[source]; exists {
- return nil, infraerrors.BadRequest("INVALID_MAPPING", fmt.Sprintf("duplicate mapping source '%s' in platform '%s'", source, platform))
- }
- dstMapping[source] = target
- }
- normalized[platform] = dstMapping
- }
- return normalized, nil
-}
-
// validateNoConflictingMappings 检查模型映射中是否有冲突的源模式
func validateNoConflictingMappings(mapping map[string]map[string]string) error {
for platform, platformMapping := range mapping {
diff --git a/backend/internal/service/channel_service_test.go b/backend/internal/service/channel_service_test.go
index 9991af74dc7..e737a21125b 100644
--- a/backend/internal/service/channel_service_test.go
+++ b/backend/internal/service/channel_service_test.go
@@ -876,7 +876,10 @@ func TestResolveChannelMapping_WildcardFirstMatch(t *testing.T) {
result := svc.ResolveChannelMapping(context.Background(), 10, "claude-sonnet-4")
require.True(t, result.Mapped)
- require.Equal(t, "target1", result.MappedModel)
+ // map iteration order is non-deterministic, so the first-match depends on
+ // insertion order which Go maps don't guarantee; verify that one of the
+ // wildcard targets matched
+ require.Contains(t, []string{"target1", "target2"}, result.MappedModel)
}
func TestResolveChannelMapping_NoMapping(t *testing.T) {
@@ -2243,20 +2246,6 @@ func TestToUsageFields_WithUpstreamDifference(t *testing.T) {
require.Equal(t, "my-alias→claude-sonnet-4→claude-sonnet-4-20250514", fields.ModelMappingChain)
}
-func TestToUsageFieldsFromClient_IncludesGroupMappingStep(t *testing.T) {
- r := ChannelMappingResult{
- MappedModel: "upstream-channel",
- ChannelID: 4,
- Mapped: true,
- BillingModelSource: BillingModelSourceChannelMapped,
- }
-
- fields := r.ToUsageFieldsFromClient("client-alias", "group-alias", "account-upstream")
- require.Equal(t, "client-alias", fields.OriginalModel)
- require.Equal(t, "upstream-channel", fields.ChannelMappedModel)
- require.Equal(t, "client-alias\u2192group-alias\u2192upstream-channel\u2192account-upstream", fields.ModelMappingChain)
-}
-
// ---------------------------------------------------------------------------
// 11. validatePricingBillingMode (moved from handler tests)
// ---------------------------------------------------------------------------
@@ -2389,22 +2378,6 @@ func TestCreate_MappingConflict(t *testing.T) {
require.Contains(t, err.Error(), "MAPPING_PATTERN_CONFLICT")
}
-func TestCreate_MappingRejectsInvalidTarget(t *testing.T) {
- repo := &mockChannelRepository{}
- svc := newTestChannelService(repo)
-
- _, err := svc.Create(context.Background(), &CreateChannelInput{
- Name: "test",
- ModelMapping: map[string]map[string]string{
- PlatformAnthropic: {
- "claude-*": "target-*",
- },
- },
- })
- require.Error(t, err)
- require.Contains(t, err.Error(), "INVALID_MAPPING")
-}
-
func TestUpdate_MappingConflict(t *testing.T) {
existingChannel := &Channel{
ID: 1,
diff --git a/backend/internal/service/group.go b/backend/internal/service/group.go
index 483f775c906..9aa2a52f4aa 100644
--- a/backend/internal/service/group.go
+++ b/backend/internal/service/group.go
@@ -1,74 +1,12 @@
package service
import (
- "fmt"
- "regexp"
- "sort"
"strings"
- "sync"
"time"
"github.com/Wei-Shaw/sub2api/internal/domain"
)
-// groupMappingRegexCache 缓存分组模型映射中已编译的正则表达式(自动添加全串锚定)。
-// key: 去掉 "~" 前缀后的原始正则字符串,value: 编译后的 *regexp.Regexp。
-var groupMappingRegexCache sync.Map
-
-// getGroupMappingRegex 返回对应 rawPattern 的编译正则,自动添加 ^(?:...)$ 全串锚定。
-// 编译结果在进程生命周期内缓存,避免热路径重复 Compile。
-func getGroupMappingRegex(rawPattern string) (*regexp.Regexp, error) {
- anchored := `^(?:` + rawPattern + `)$`
- if v, ok := groupMappingRegexCache.Load(anchored); ok {
- return v.(*regexp.Regexp), nil
- }
- re, err := regexp.Compile(anchored)
- if err != nil {
- return nil, err
- }
- actual, _ := groupMappingRegexCache.LoadOrStore(anchored, re)
- return actual.(*regexp.Regexp), nil
-}
-
-func NormalizeGroupModelMapping(mapping map[string]string) (map[string]string, error) {
- if mapping == nil {
- return nil, nil
- }
- normalized := make(map[string]string, len(mapping))
- for source, target := range mapping {
- source = strings.TrimSpace(source)
- target = strings.TrimSpace(target)
- if source == "" {
- return nil, fmt.Errorf("model_mapping source cannot be empty")
- }
- if target == "" {
- return nil, fmt.Errorf("model_mapping target for %q cannot be empty", source)
- }
- if strings.Contains(target, "*") {
- return nil, fmt.Errorf("model_mapping target for %q cannot contain wildcard", source)
- }
- if strings.HasPrefix(source, "~") {
- rawPattern := strings.TrimSpace(strings.TrimPrefix(source, "~"))
- if rawPattern == "" {
- return nil, fmt.Errorf("model_mapping regex source cannot be empty")
- }
- if _, err := getGroupMappingRegex(rawPattern); err != nil {
- return nil, fmt.Errorf("invalid model_mapping regex %q: %w", source, err)
- }
- source = "~" + rawPattern
- } else if count := strings.Count(source, "*"); count > 0 {
- if count > 1 || !strings.HasSuffix(source, "*") {
- return nil, fmt.Errorf("model_mapping wildcard source %q must use a single trailing *", source)
- }
- }
- if _, exists := normalized[source]; exists {
- return nil, fmt.Errorf("duplicate model_mapping source %q", source)
- }
- normalized[source] = target
- }
- return normalized, nil
-}
-
type OpenAIMessagesDispatchModelConfig = domain.OpenAIMessagesDispatchModelConfig
type GroupModelsListConfig = domain.GroupModelsListConfig
@@ -130,16 +68,6 @@ type Group struct {
// 一旦设置即接管该分组用户的限流(覆盖用户级 rpm_limit),可被 user-group rpm_override 进一步覆盖。
RPMLimit int
- // xiugai 修改自动映射功能
- // 分组级模型映射(分组维度,优先于渠道/账号级映射)。
- // key: 匹配模式,value: 目标模型名。
- // 匹配规则(优先级由高到低):
- // 1. 精确匹配
- // 2. 正则匹配:pattern 以 "~" 开头,去掉前缀后作为 Go regexp 编译匹配
- // 3. 通配符匹配:pattern 以 "*" 结尾,匹配对应前缀
- ModelMapping map[string]string
- // xiugai end
-
CreatedAt time.Time
UpdatedAt time.Time
@@ -239,62 +167,3 @@ func matchModelPattern(pattern, model string) bool {
return false
}
-
-// xiugai 修改自动映射功能
-// ResolveGroupMappedModel 查找分组级别的模型映射。
-// matched=true 表示命中规则(即使映射结果与原模型名相同)。
-// 匹配优先级:精确 > 正则(~ 前缀)> 通配符(* 后缀),同级按 pattern 长度降序取最长匹配。
-// 目标值为空字符串的规则视为未配置,不会命中(避免产生误导性的 "model is required" 错误)。
-// 正则匹配自动添加全串锚定(^(?:...)$),防止子串误匹配,并在进程级缓存编译结果。
-func (g *Group) ResolveGroupMappedModel(requestedModel string) (mappedModel string, matched bool) {
- if g == nil || len(g.ModelMapping) == 0 || requestedModel == "" {
- return requestedModel, false
- }
- // 1. 精确匹配(Bug4: 跳过空目标)
- if target, ok := g.ModelMapping[requestedModel]; ok && target != "" {
- return target, true
- }
- // 2. 正则匹配(pattern 以 "~" 开头)
- type candidate struct {
- pattern string
- target string
- }
- var regexCandidates, wildcardCandidates []candidate
- for pattern, target := range g.ModelMapping {
- if target == "" {
- continue // Bug4: 跳过空目标,不加入候选
- }
- if strings.HasPrefix(pattern, "~") {
- regexCandidates = append(regexCandidates, candidate{pattern, target})
- } else if strings.HasSuffix(pattern, "*") {
- wildcardCandidates = append(wildcardCandidates, candidate{pattern, target})
- }
- }
- // 正则:按 pattern 长度降序,取第一个匹配
- sort.Slice(regexCandidates, func(i, j int) bool {
- return len(regexCandidates[i].pattern) > len(regexCandidates[j].pattern)
- })
- for _, c := range regexCandidates {
- // Bug2+3: 使用进程级缓存的全串锚定正则(^(?:...)$),避免子串误匹配和重复编译
- re, err := getGroupMappingRegex(c.pattern[1:])
- if err != nil {
- continue
- }
- if re.MatchString(requestedModel) {
- return c.target, true
- }
- }
- // 3. 通配符:按 pattern 长度降序(最长前缀优先)
- sort.Slice(wildcardCandidates, func(i, j int) bool {
- return len(wildcardCandidates[i].pattern) > len(wildcardCandidates[j].pattern)
- })
- for _, c := range wildcardCandidates {
- prefix := c.pattern[:len(c.pattern)-1]
- if strings.HasPrefix(requestedModel, prefix) {
- return c.target, true
- }
- }
- return requestedModel, false
-}
-
-// xiugai end
diff --git a/backend/internal/service/group_test.go b/backend/internal/service/group_test.go
index 1a4d5a0da43..a0f9672c755 100644
--- a/backend/internal/service/group_test.go
+++ b/backend/internal/service/group_test.go
@@ -8,128 +8,6 @@ import (
"github.com/stretchr/testify/require"
)
-// --- ResolveGroupMappedModel 测试 ---
-
-func TestResolveGroupMappedModel_ExactMatch(t *testing.T) {
- g := &Group{ModelMapping: map[string]string{"gpt-4": "gpt-4o"}}
- model, ok := g.ResolveGroupMappedModel("gpt-4")
- require.True(t, ok)
- require.Equal(t, "gpt-4o", model)
-}
-
-func TestResolveGroupMappedModel_NoMatch(t *testing.T) {
- g := &Group{ModelMapping: map[string]string{"gpt-4": "gpt-4o"}}
- model, ok := g.ResolveGroupMappedModel("gpt-5")
- require.False(t, ok)
- require.Equal(t, "gpt-5", model)
-}
-
-func TestResolveGroupMappedModel_WildcardMatch(t *testing.T) {
- g := &Group{ModelMapping: map[string]string{"gpt-4*": "gpt-4o"}}
- model, ok := g.ResolveGroupMappedModel("gpt-4-turbo")
- require.True(t, ok)
- require.Equal(t, "gpt-4o", model)
-}
-
-// Bug2: 正则全串锚定——"~4" 不应命中 "gpt-4o",只应命中 "4"
-func TestResolveGroupMappedModel_Bug2_RegexFullMatch(t *testing.T) {
- g := &Group{ModelMapping: map[string]string{"~4": "target"}}
-
- // 字符串 "4" 完整匹配正则 "4"
- model, ok := g.ResolveGroupMappedModel("4")
- require.True(t, ok)
- require.Equal(t, "target", model)
-
- // "gpt-4o" 不应被子串匹配命中
- _, ok = g.ResolveGroupMappedModel("gpt-4o")
- require.False(t, ok, "正则 '~4' 不应子串匹配 'gpt-4o'")
-
- // "gpt-4" 也不应被命中
- _, ok = g.ResolveGroupMappedModel("gpt-4")
- require.False(t, ok, "正则 '~4' 不应子串匹配 'gpt-4'")
-}
-
-// Bug2: 用户自行写全串锚定的正则仍然正确工作
-func TestResolveGroupMappedModel_Bug2_RegexFullPatternWorks(t *testing.T) {
- g := &Group{ModelMapping: map[string]string{"~^gpt-4.*$": "gpt-4o"}}
- model, ok := g.ResolveGroupMappedModel("gpt-4-turbo")
- require.True(t, ok)
- require.Equal(t, "gpt-4o", model)
-
- _, ok = g.ResolveGroupMappedModel("openai-gpt-4")
- require.False(t, ok, "^gpt-4.*$ 不应匹配 'openai-gpt-4'")
-}
-
-// Bug3: 多次调用不因重复编译 panic,且结果一致(缓存正确)
-func TestResolveGroupMappedModel_Bug3_RegexCachedConsistency(t *testing.T) {
- g := &Group{ModelMapping: map[string]string{"~^claude-.*": "claude-3-5-sonnet-20241022"}}
- for range 10 {
- model, ok := g.ResolveGroupMappedModel("claude-opus-4")
- require.True(t, ok)
- require.Equal(t, "claude-3-5-sonnet-20241022", model)
- }
-}
-
-// Bug4: 空目标不命中,不产生 "model is required" 的误导性错误
-func TestResolveGroupMappedModel_Bug4_EmptyTargetSkipped(t *testing.T) {
- g := &Group{ModelMapping: map[string]string{
- "gpt-4": "", // 空目标:不应命中
- "gpt-4*": "", // 空通配符目标:不应命中
- "~^gpt-5": "", // 空正则目标:不应命中
- "gpt-3": "gpt-3o", // 正常规则作为对照
- }}
-
- // 空目标的精确匹配不应命中
- _, ok := g.ResolveGroupMappedModel("gpt-4")
- require.False(t, ok, "空目标的精确规则不应命中")
-
- // 空目标的通配符不应命中
- _, ok = g.ResolveGroupMappedModel("gpt-4-turbo")
- require.False(t, ok, "空目标的通配符规则不应命中")
-
- // 空目标的正则不应命中
- _, ok = g.ResolveGroupMappedModel("gpt-5")
- require.False(t, ok, "空目标的正则规则不应命中")
-
- // 正常规则正常命中
- model, ok := g.ResolveGroupMappedModel("gpt-3")
- require.True(t, ok)
- require.Equal(t, "gpt-3o", model)
-}
-
-// Bug4: 空目标精确匹配不阻断后续通配符匹配
-func TestResolveGroupMappedModel_Bug4_EmptyExactFallsThrough(t *testing.T) {
- g := &Group{ModelMapping: map[string]string{
- "gpt-4": "", // 空精确匹配,应跳过
- "gpt-4*": "gpt-4o", // 通配符应继续生效
- }}
- model, ok := g.ResolveGroupMappedModel("gpt-4")
- require.True(t, ok, "空精确规则跳过后应命中通配符规则")
- require.Equal(t, "gpt-4o", model)
-}
-
-func TestNormalizeGroupModelMapping_RejectsInvalidRegex(t *testing.T) {
- _, err := NormalizeGroupModelMapping(map[string]string{
- "~[": "target",
- })
- require.Error(t, err)
- require.Contains(t, err.Error(), "invalid model_mapping regex")
-}
-
-func TestNormalizeGroupModelMapping_RejectsEmptyAndWildcardTarget(t *testing.T) {
- _, err := NormalizeGroupModelMapping(map[string]string{
- "gpt-5": "",
- })
- require.Error(t, err)
- require.Contains(t, err.Error(), "cannot be empty")
-
- _, err = NormalizeGroupModelMapping(map[string]string{
- "gpt-*": "target-*",
- })
- require.Error(t, err)
- require.Contains(t, err.Error(), "cannot contain wildcard")
-}
-
// TestGroup_GetImagePrice_1K 测试 1K 尺寸返回正确价格
func TestGroup_GetImagePrice_1K(t *testing.T) {
price := 0.10
diff --git a/backend/internal/service/openai_gateway_chat_completions.go b/backend/internal/service/openai_gateway_chat_completions.go
index 5045fdabd10..807ff43afc8 100644
--- a/backend/internal/service/openai_gateway_chat_completions.go
+++ b/backend/internal/service/openai_gateway_chat_completions.go
@@ -213,12 +213,9 @@ func (s *OpenAIGatewayService) ForwardAsChatCompletions(
return nil, fmt.Errorf("build upstream request: %w", err)
}
- // xiugai 修复 session 隔离缺失:用 isolateOpenAISessionID 将 apiKeyID 混入 session_id,
- // 防止不同 API Key 使用相同 promptCacheKey 时共享上游 session
if promptCacheKey != "" {
- upstreamReq.Header.Set("session_id", generateSessionUUID(isolateOpenAISessionID(getAPIKeyIDFromContext(c), promptCacheKey)))
+ upstreamReq.Header.Set("session_id", generateSessionUUID(promptCacheKey))
}
- // end
// 7. Send request
proxyURL := ""
diff --git a/frontend/src/types/index.ts b/frontend/src/types/index.ts
index c1b6eb3be8d..02fa12f0aee 100644
--- a/frontend/src/types/index.ts
+++ b/frontend/src/types/index.ts
@@ -533,7 +533,6 @@ export interface AdminGroup extends Group {
// 模型路由配置(仅管理员可见,内部信息)
model_routing: Record | null
model_routing_enabled: boolean
- model_mapping?: Record
// MCP XML 协议注入(仅 antigravity 平台使用)
mcp_xml_inject: boolean
From 8055c2ae99b497db6dea294ab9663df302d6c3aa Mon Sep 17 00:00:00 2001
From: lanlingxiawu <374397721@qq.com>
Date: Fri, 29 May 2026 16:18:55 +0800
Subject: [PATCH 6/6] =?UTF-8?q?=E4=BF=AE=E6=94=B9=E8=B4=A6=E5=8F=B7?=
=?UTF-8?q?=E4=B8=8A=E4=BC=A0=E7=8A=B6=E6=80=81?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
修改账号上传状态
---
backend/internal/node/adapter.go | 11 ++++++--
backend/internal/node/reporter.go | 46 ++++++++++++++++++++++++++++---
2 files changed, 50 insertions(+), 7 deletions(-)
diff --git a/backend/internal/node/adapter.go b/backend/internal/node/adapter.go
index b7471999864..eb58b0f1181 100644
--- a/backend/internal/node/adapter.go
+++ b/backend/internal/node/adapter.go
@@ -54,9 +54,14 @@ func (l *accountRepoLister) ListAll(ctx context.Context) ([]NodeAccount, error)
for _, a := range rows {
all = append(all, NodeAccount{
- ID: a.ID,
- Name: a.Name,
- Status: a.Status,
+ ID: a.ID,
+ Name: a.Name,
+ Status: a.Status,
+ Schedulable: a.Schedulable,
+ RateLimitResetAt: a.RateLimitResetAt,
+ OverloadUntil: a.OverloadUntil,
+ TempUnschedulableUntil: a.TempUnschedulableUntil,
+ QuotaExceeded: a.IsQuotaExceeded(),
})
}
diff --git a/backend/internal/node/reporter.go b/backend/internal/node/reporter.go
index 180d6964945..df3cbd91d86 100644
--- a/backend/internal/node/reporter.go
+++ b/backend/internal/node/reporter.go
@@ -53,14 +53,52 @@ type accountsStatusRequest struct {
Accounts []accountStatus `json:"accounts"`
}
-// NodeAccount 是上报服务所需的最小账号信息结构,由 AccountLister 返回。
+// NodeAccount 是上报服务所需的账号信息结构,由 AccountLister 返回。
type NodeAccount struct {
// ID 账号数据库主键。
ID int64
// Name 账号显示名称。
Name string
- // Status 账号当前状态字符串。
+ // Status 账号原始数据库状态(active/error/disabled)。
Status string
+ // Schedulable 账号是否被管理员标记为可调度。
+ Schedulable bool
+ // RateLimitResetAt 速率限制(429)解除时间,nil 表示未限流。
+ RateLimitResetAt *time.Time
+ // OverloadUntil 过载(529)解除时间,nil 表示未过载。
+ OverloadUntil *time.Time
+ // TempUnschedulableUntil 临时不可调度解除时间,nil 表示正常。
+ TempUnschedulableUntil *time.Time
+ // QuotaExceeded 是否已超出配额限制。
+ QuotaExceeded bool
+}
+
+// effectiveStatus 综合多个字段计算账号的有效展示状态,与前端 AccountStatusIndicator 逻辑一致。
+// 优先级(从高到低):rate_limited > overloaded > error > temp_unschedulable > disabled > quota_exceeded > paused > active
+func effectiveStatus(a NodeAccount) string {
+ now := time.Now()
+ if a.RateLimitResetAt != nil && now.Before(*a.RateLimitResetAt) {
+ return "速率限制"
+ }
+ if a.OverloadUntil != nil && now.Before(*a.OverloadUntil) {
+ return "过载"
+ }
+ if a.Status == "error" {
+ return "错误"
+ }
+ if a.TempUnschedulableUntil != nil && now.Before(*a.TempUnschedulableUntil) {
+ return "临时不可调度"
+ }
+ if a.Status != "active" {
+ return a.Status
+ }
+ if a.QuotaExceeded {
+ return "配额超限"
+ }
+ if !a.Schedulable {
+ return "关闭调度"
+ }
+ return "正常"
}
// AccountLister 定义获取本地全量账号的接口,由 accountRepoLister 适配器实现。
@@ -207,13 +245,13 @@ func (r *Reporter) maybeUploadAccounts(ctx context.Context) {
return
}
- // 将 NodeAccount 转换为 accountStatus 上报结构。
+ // 将 NodeAccount 转换为 accountStatus 上报结构,使用综合状态。
statuses := make([]accountStatus, 0, len(accounts))
for _, a := range accounts {
statuses = append(statuses, accountStatus{
ID: strconv.FormatInt(a.ID, 10),
Name: a.Name,
- Status: a.Status,
+ Status: effectiveStatus(a),
})
}