Compare commits
14 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| d92d1fadaf | |||
| 0ff9c35e62 | |||
| 0bbcaa8999 | |||
| 1e9ff8a0de | |||
| 9a2e60dff2 | |||
| b596de739d | |||
| 45d54c1613 | |||
| 086044650d | |||
| 0c7aceb831 | |||
| b2e25b7df2 | |||
| 230a3592f8 | |||
| afb470e405 | |||
| 1588027084 | |||
| 38bf2d8daa |
@@ -33,16 +33,18 @@ jobs:
|
||||
env:
|
||||
CI: ""
|
||||
run: |
|
||||
cd web/default
|
||||
bun install
|
||||
cd web
|
||||
bun install --frozen-lockfile
|
||||
cd default
|
||||
DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$VERSION bun run build
|
||||
cd ../..
|
||||
- name: Build Frontend (classic)
|
||||
env:
|
||||
CI: ""
|
||||
run: |
|
||||
cd web/classic
|
||||
bun install
|
||||
cd web
|
||||
bun install --frozen-lockfile
|
||||
cd classic
|
||||
VITE_REACT_APP_VERSION=$VERSION bun run build
|
||||
cd ../..
|
||||
- name: Set up Go
|
||||
@@ -91,16 +93,18 @@ jobs:
|
||||
CI: ""
|
||||
NODE_OPTIONS: "--max-old-space-size=4096"
|
||||
run: |
|
||||
cd web/default
|
||||
bun install
|
||||
cd web
|
||||
bun install --frozen-lockfile
|
||||
cd default
|
||||
DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$VERSION bun run build
|
||||
cd ../..
|
||||
- name: Build Frontend (classic)
|
||||
env:
|
||||
CI: ""
|
||||
run: |
|
||||
cd web/classic
|
||||
bun install
|
||||
cd web
|
||||
bun install --frozen-lockfile
|
||||
cd classic
|
||||
VITE_REACT_APP_VERSION=$VERSION bun run build
|
||||
cd ../..
|
||||
- name: Set up Go
|
||||
@@ -146,16 +150,18 @@ jobs:
|
||||
env:
|
||||
CI: ""
|
||||
run: |
|
||||
cd web/default
|
||||
bun install
|
||||
cd web
|
||||
bun install --frozen-lockfile
|
||||
cd default
|
||||
DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$VERSION bun run build
|
||||
cd ../..
|
||||
- name: Build Frontend (classic)
|
||||
env:
|
||||
CI: ""
|
||||
run: |
|
||||
cd web/classic
|
||||
bun install
|
||||
cd web
|
||||
bun install --frozen-lockfile
|
||||
cd classic
|
||||
VITE_REACT_APP_VERSION=$VERSION bun run build
|
||||
cd ../..
|
||||
- name: Set up Go
|
||||
|
||||
+18
-16
@@ -1,22 +1,24 @@
|
||||
FROM oven/bun:1@sha256:0733e50325078969732ebe3b15ce4c4be5082f18c4ac1a0f0ca4839c2e4e42a7 AS builder
|
||||
|
||||
WORKDIR /build
|
||||
COPY web/default/package.json .
|
||||
COPY web/default/bun.lock .
|
||||
RUN bun install
|
||||
COPY ./web/default .
|
||||
COPY ./VERSION .
|
||||
RUN DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat VERSION) bun run build
|
||||
WORKDIR /build/web
|
||||
COPY web/package.json web/bun.lock ./
|
||||
COPY web/default/package.json ./default/package.json
|
||||
COPY web/classic/package.json ./classic/package.json
|
||||
RUN bun install --frozen-lockfile
|
||||
COPY ./web/default ./default
|
||||
COPY ./VERSION /build/VERSION
|
||||
RUN cd default && DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat /build/VERSION) bun run build
|
||||
|
||||
FROM oven/bun:1@sha256:0733e50325078969732ebe3b15ce4c4be5082f18c4ac1a0f0ca4839c2e4e42a7 AS builder-classic
|
||||
|
||||
WORKDIR /build
|
||||
COPY web/classic/package.json .
|
||||
COPY web/classic/bun.lock .
|
||||
RUN bun install
|
||||
COPY ./web/classic .
|
||||
COPY ./VERSION .
|
||||
RUN VITE_REACT_APP_VERSION=$(cat VERSION) bun run build
|
||||
WORKDIR /build/web
|
||||
COPY web/package.json web/bun.lock ./
|
||||
COPY web/default/package.json ./default/package.json
|
||||
COPY web/classic/package.json ./classic/package.json
|
||||
RUN bun install --frozen-lockfile
|
||||
COPY ./web/classic ./classic
|
||||
COPY ./VERSION /build/VERSION
|
||||
RUN cd classic && VITE_REACT_APP_VERSION=$(cat /build/VERSION) bun run build
|
||||
|
||||
FROM golang:1.26.1-alpine@sha256:2389ebfa5b7f43eeafbd6be0c3700cc46690ef842ad962f6c5bd6be49ed82039 AS builder2
|
||||
ENV GO111MODULE=on CGO_ENABLED=0
|
||||
@@ -32,8 +34,8 @@ ADD go.mod go.sum ./
|
||||
RUN go mod download
|
||||
|
||||
COPY . .
|
||||
COPY --from=builder /build/dist ./web/default/dist
|
||||
COPY --from=builder-classic /build/dist ./web/classic/dist
|
||||
COPY --from=builder /build/web/default/dist ./web/default/dist
|
||||
COPY --from=builder-classic /build/web/classic/dist ./web/classic/dist
|
||||
RUN go build -ldflags "-s -w -X 'github.com/QuantumNous/new-api/common.Version=$(cat VERSION)'" -o new-api
|
||||
|
||||
FROM debian:bookworm-slim@sha256:f06537653ac770703bc45b4b113475bd402f451e85223f0f2837acbf89ab020a
|
||||
|
||||
@@ -41,6 +41,7 @@ func GetSubscriptionPlans(c *gin.Context) {
|
||||
}
|
||||
result := make([]SubscriptionPlanDTO, 0, len(plans))
|
||||
for _, p := range plans {
|
||||
p.NormalizeDefaults()
|
||||
result = append(result, SubscriptionPlanDTO{
|
||||
Plan: p,
|
||||
})
|
||||
@@ -125,6 +126,7 @@ func AdminListSubscriptionPlans(c *gin.Context) {
|
||||
}
|
||||
result := make([]SubscriptionPlanDTO, 0, len(plans))
|
||||
for _, p := range plans {
|
||||
p.NormalizeDefaults()
|
||||
result = append(result, SubscriptionPlanDTO{
|
||||
Plan: p,
|
||||
})
|
||||
@@ -163,6 +165,9 @@ func AdminCreateSubscriptionPlan(c *gin.Context) {
|
||||
req.Plan.Currency = "USD"
|
||||
}
|
||||
req.Plan.Currency = "USD"
|
||||
if req.Plan.AllowBalancePay == nil {
|
||||
req.Plan.AllowBalancePay = common.GetPointer(true)
|
||||
}
|
||||
if req.Plan.DurationUnit == "" {
|
||||
req.Plan.DurationUnit = model.SubscriptionDurationMonth
|
||||
}
|
||||
@@ -279,6 +284,9 @@ func AdminUpdateSubscriptionPlan(c *gin.Context) {
|
||||
"quota_reset_custom_seconds": req.Plan.QuotaResetCustomSeconds,
|
||||
"updated_at": common.GetTimestamp(),
|
||||
}
|
||||
if req.Plan.AllowBalancePay != nil {
|
||||
updateMap["allow_balance_pay"] = *req.Plan.AllowBalancePay
|
||||
}
|
||||
if err := tx.Model(&model.SubscriptionPlan{}).Where("id = ?", id).Updates(updateMap).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
FRONTEND_DIR = ./web/default
|
||||
FRONTEND_CLASSIC_DIR = ./web/classic
|
||||
BACKEND_DIR = .
|
||||
DEV_FRONTEND_DEFAULT_PORT ?= 5173
|
||||
DEV_FRONTEND_CLASSIC_PORT ?= 5174
|
||||
DEV_COMPOSE_FILE = docker-compose.dev.yml
|
||||
DEV_POSTGRES_SERVICE = postgres
|
||||
DEV_BACKEND_SERVICE = new-api
|
||||
@@ -14,11 +16,13 @@ all: build-all-frontends start-backend
|
||||
|
||||
build-frontend:
|
||||
@echo "Building default frontend..."
|
||||
@cd $(FRONTEND_DIR) && bun install && DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat ../../VERSION) bun run build
|
||||
@cd ./web && bun install --frozen-lockfile
|
||||
@cd $(FRONTEND_DIR) && DISABLE_ESLINT_PLUGIN='true' VITE_REACT_APP_VERSION=$(cat ../../VERSION) bun run build
|
||||
|
||||
build-frontend-classic:
|
||||
@echo "Building classic frontend..."
|
||||
@cd $(FRONTEND_CLASSIC_DIR) && bun install && VITE_REACT_APP_VERSION=$(cat ../../VERSION) bun run build
|
||||
@cd ./web && bun install --frozen-lockfile
|
||||
@cd $(FRONTEND_CLASSIC_DIR) && VITE_REACT_APP_VERSION=$(cat ../../VERSION) bun run build
|
||||
|
||||
build-all-frontends: build-frontend build-frontend-classic
|
||||
|
||||
@@ -35,12 +39,35 @@ dev-api-rebuild:
|
||||
@docker compose -f $(DEV_COMPOSE_FILE) up -d --build $(DEV_BACKEND_SERVICE)
|
||||
|
||||
dev-web:
|
||||
@echo "Starting frontend dev server..."
|
||||
@cd $(FRONTEND_DIR) && bun install && bun run dev
|
||||
@echo "Starting both frontend dev servers..."
|
||||
@echo "Default frontend: http://localhost:$(DEV_FRONTEND_DEFAULT_PORT)"
|
||||
@echo "Classic frontend: http://localhost:$(DEV_FRONTEND_CLASSIC_PORT)"
|
||||
@cd ./web && bun install
|
||||
@(cd $(FRONTEND_DIR) && bun run dev -- --host 0.0.0.0 --port $(DEV_FRONTEND_DEFAULT_PORT)) & \
|
||||
default_pid=$$!; \
|
||||
(cd $(FRONTEND_CLASSIC_DIR) && bun run dev -- --host 0.0.0.0 --port $(DEV_FRONTEND_CLASSIC_PORT)) & \
|
||||
classic_pid=$$!; \
|
||||
trap 'kill $$default_pid $$classic_pid 2>/dev/null; wait $$default_pid $$classic_pid 2>/dev/null; exit 130' INT TERM; \
|
||||
while kill -0 $$default_pid 2>/dev/null && kill -0 $$classic_pid 2>/dev/null; do \
|
||||
sleep 1; \
|
||||
done; \
|
||||
if ! kill -0 $$default_pid 2>/dev/null; then \
|
||||
wait $$default_pid; \
|
||||
status=$$?; \
|
||||
kill $$classic_pid 2>/dev/null; \
|
||||
wait $$classic_pid 2>/dev/null; \
|
||||
exit $$status; \
|
||||
fi; \
|
||||
wait $$classic_pid; \
|
||||
status=$$?; \
|
||||
kill $$default_pid 2>/dev/null; \
|
||||
wait $$default_pid 2>/dev/null; \
|
||||
exit $$status
|
||||
|
||||
dev-web-classic:
|
||||
@echo "Starting classic frontend dev server..."
|
||||
@cd $(FRONTEND_CLASSIC_DIR) && bun install && bun run dev
|
||||
@cd ./web && bun install
|
||||
@cd $(FRONTEND_CLASSIC_DIR) && bun run dev -- --host 0.0.0.0 --port $(DEV_FRONTEND_CLASSIC_PORT)
|
||||
|
||||
dev: dev-api dev-web
|
||||
|
||||
|
||||
+3
-3
@@ -32,9 +32,9 @@ func applyExplicitLogTextFilter(tx *gorm.DB, column string, value string) (*gorm
|
||||
}
|
||||
|
||||
type Log struct {
|
||||
Id int `json:"id" gorm:"index:idx_created_at_id,priority:1;index:idx_user_id_id,priority:2"`
|
||||
Id int `json:"id" gorm:"index:idx_created_at_id,priority:2;index:idx_user_id_id,priority:2"`
|
||||
UserId int `json:"user_id" gorm:"index;index:idx_user_id_id,priority:1"`
|
||||
CreatedAt int64 `json:"created_at" gorm:"bigint;index:idx_created_at_id,priority:2;index:idx_created_at_type"`
|
||||
CreatedAt int64 `json:"created_at" gorm:"bigint;index:idx_created_at_id,priority:1;index:idx_created_at_type"`
|
||||
Type int `json:"type" gorm:"index:idx_created_at_type"`
|
||||
Content string `json:"content"`
|
||||
Username string `json:"username" gorm:"index;index:index_username_model_name,priority:2;default:''"`
|
||||
@@ -354,7 +354,7 @@ func GetAllLogs(logType int, startTimestamp int64, endTimestamp int64, modelName
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
err = tx.Order("logs.id desc").Limit(num).Offset(startIdx).Find(&logs).Error
|
||||
err = tx.Order("logs.created_at desc, logs.id desc").Limit(num).Offset(startIdx).Find(&logs).Error
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
@@ -397,6 +397,7 @@ func ensureSubscriptionPlanTableSQLite() error {
|
||||
` + "`custom_seconds`" + ` bigint NOT NULL DEFAULT 0,
|
||||
` + "`enabled`" + ` numeric DEFAULT 1,
|
||||
` + "`sort_order`" + ` integer DEFAULT 0,
|
||||
` + "`allow_balance_pay`" + ` numeric DEFAULT 1,
|
||||
` + "`stripe_price_id`" + ` varchar(128) DEFAULT '',
|
||||
` + "`creem_product_id`" + ` varchar(128) DEFAULT '',
|
||||
` + "`waffo_pancake_product_id`" + ` varchar(128) DEFAULT '',
|
||||
@@ -431,6 +432,7 @@ PRIMARY KEY (` + "`id`" + `)
|
||||
{Name: "custom_seconds", DDL: "`custom_seconds` bigint NOT NULL DEFAULT 0"},
|
||||
{Name: "enabled", DDL: "`enabled` numeric DEFAULT 1"},
|
||||
{Name: "sort_order", DDL: "`sort_order` integer DEFAULT 0"},
|
||||
{Name: "allow_balance_pay", DDL: "`allow_balance_pay` numeric DEFAULT 1"},
|
||||
{Name: "stripe_price_id", DDL: "`stripe_price_id` varchar(128) DEFAULT ''"},
|
||||
{Name: "creem_product_id", DDL: "`creem_product_id` varchar(128) DEFAULT ''"},
|
||||
{Name: "waffo_pancake_product_id", DDL: "`waffo_pancake_product_id` varchar(128) DEFAULT ''"},
|
||||
|
||||
@@ -160,6 +160,8 @@ type SubscriptionPlan struct {
|
||||
Enabled bool `json:"enabled" gorm:"default:true"`
|
||||
SortOrder int `json:"sort_order" gorm:"type:int;default:0"`
|
||||
|
||||
AllowBalancePay *bool `json:"allow_balance_pay" gorm:"default:true"`
|
||||
|
||||
StripePriceId string `json:"stripe_price_id" gorm:"type:varchar(128);default:''"`
|
||||
CreemProductId string `json:"creem_product_id" gorm:"type:varchar(128);default:''"`
|
||||
WaffoPancakeProductId string `json:"waffo_pancake_product_id" gorm:"type:varchar(128);default:''"`
|
||||
@@ -193,6 +195,12 @@ func (p *SubscriptionPlan) BeforeUpdate(tx *gorm.DB) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *SubscriptionPlan) NormalizeDefaults() {
|
||||
if p.AllowBalancePay == nil {
|
||||
p.AllowBalancePay = common.GetPointer(true)
|
||||
}
|
||||
}
|
||||
|
||||
// Subscription order (payment -> webhook -> create UserSubscription)
|
||||
type SubscriptionOrder struct {
|
||||
Id int `json:"id"`
|
||||
@@ -360,6 +368,7 @@ func getSubscriptionPlanByIdTx(tx *gorm.DB, id int) (*SubscriptionPlan, error) {
|
||||
key := subscriptionPlanCacheKey(id)
|
||||
if key != "" {
|
||||
if cached, found, err := getSubscriptionPlanCache().Get(key); err == nil && found {
|
||||
cached.NormalizeDefaults()
|
||||
return &cached, nil
|
||||
}
|
||||
}
|
||||
@@ -371,6 +380,7 @@ func getSubscriptionPlanByIdTx(tx *gorm.DB, id int) (*SubscriptionPlan, error) {
|
||||
if err := query.Where("id = ?", id).First(&plan).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
plan.NormalizeDefaults()
|
||||
_ = getSubscriptionPlanCache().SetWithTTL(key, plan, subscriptionPlanCacheTTL())
|
||||
return &plan, nil
|
||||
}
|
||||
@@ -701,6 +711,9 @@ func PurchaseSubscriptionWithBalance(userId int, planId int) error {
|
||||
if plan.PriceAmount < 0 {
|
||||
return errors.New("套餐价格不能为负数")
|
||||
}
|
||||
if plan.AllowBalancePay != nil && !*plan.AllowBalancePay {
|
||||
return errors.New("该套餐不允许使用余额兑换")
|
||||
}
|
||||
|
||||
requiredQuota, err := calcSubscriptionBalanceQuota(plan.PriceAmount)
|
||||
if err != nil {
|
||||
|
||||
@@ -19,6 +19,7 @@ var awsModelIDMap = map[string]string{
|
||||
"claude-opus-4-5-20251101": "anthropic.claude-opus-4-5-20251101-v1:0",
|
||||
"claude-opus-4-6": "anthropic.claude-opus-4-6-v1",
|
||||
"claude-opus-4-7": "anthropic.claude-opus-4-7",
|
||||
"claude-opus-4-8": "anthropic.claude-opus-4-8",
|
||||
// Nova models
|
||||
"nova-micro-v1:0": "amazon.nova-micro-v1:0",
|
||||
"nova-lite-v1:0": "amazon.nova-lite-v1:0",
|
||||
@@ -97,6 +98,11 @@ var awsModelCanCrossRegionMap = map[string]map[string]bool{
|
||||
"ap": true,
|
||||
"eu": true,
|
||||
},
|
||||
"anthropic.claude-opus-4-8": {
|
||||
"us": true,
|
||||
"ap": true,
|
||||
"eu": true,
|
||||
},
|
||||
"anthropic.claude-haiku-4-5-20251001-v1:0": {
|
||||
"us": true,
|
||||
"ap": true,
|
||||
|
||||
@@ -33,6 +33,13 @@ var ModelList = []string{
|
||||
"claude-opus-4-7-medium",
|
||||
"claude-opus-4-7-low",
|
||||
"claude-opus-4-7-thinking",
|
||||
"claude-opus-4-8",
|
||||
"claude-opus-4-8-max",
|
||||
"claude-opus-4-8-xhigh",
|
||||
"claude-opus-4-8-high",
|
||||
"claude-opus-4-8-medium",
|
||||
"claude-opus-4-8-low",
|
||||
"claude-opus-4-8-thinking",
|
||||
}
|
||||
|
||||
var ChannelName = "claude"
|
||||
|
||||
@@ -154,14 +154,17 @@ func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRe
|
||||
}
|
||||
|
||||
if baseModel, effortLevel, ok := reasoning.TrimEffortSuffix(textRequest.Model); ok && effortLevel != "" &&
|
||||
(strings.HasPrefix(textRequest.Model, "claude-opus-4-6") || strings.HasPrefix(textRequest.Model, "claude-opus-4-7")) {
|
||||
(strings.HasPrefix(textRequest.Model, "claude-opus-4-6") ||
|
||||
strings.HasPrefix(textRequest.Model, "claude-opus-4-7") ||
|
||||
strings.HasPrefix(textRequest.Model, "claude-opus-4-8")) {
|
||||
claudeRequest.Model = baseModel
|
||||
claudeRequest.Thinking = &dto.Thinking{
|
||||
Type: "adaptive",
|
||||
}
|
||||
claudeRequest.OutputConfig = json.RawMessage(fmt.Sprintf(`{"effort":"%s"}`, effortLevel))
|
||||
if strings.HasPrefix(baseModel, "claude-opus-4-7") {
|
||||
// Opus 4.7 rejects non-default temperature/top_p/top_k with 400
|
||||
if strings.HasPrefix(baseModel, "claude-opus-4-7") ||
|
||||
strings.HasPrefix(baseModel, "claude-opus-4-8") {
|
||||
// Opus 4.7/4.8 reject non-default temperature/top_p/top_k with 400
|
||||
// and defaults display to "omitted"; restore the 4.6 visible summary.
|
||||
claudeRequest.Thinking.Display = "summarized"
|
||||
claudeRequest.Temperature = nil
|
||||
@@ -175,8 +178,9 @@ func RequestOpenAI2ClaudeMessage(c *gin.Context, textRequest dto.GeneralOpenAIRe
|
||||
strings.HasSuffix(textRequest.Model, "-thinking") {
|
||||
|
||||
trimmedModel := strings.TrimSuffix(textRequest.Model, "-thinking")
|
||||
if strings.HasPrefix(trimmedModel, "claude-opus-4-7") {
|
||||
// Opus 4.7 rejects thinking.type="enabled"; use adaptive at high effort.
|
||||
if strings.HasPrefix(trimmedModel, "claude-opus-4-7") ||
|
||||
strings.HasPrefix(trimmedModel, "claude-opus-4-8") {
|
||||
// Opus 4.7/4.8 reject thinking.type="enabled"; use adaptive at high effort.
|
||||
claudeRequest.Thinking = &dto.Thinking{Type: "adaptive", Display: "summarized"}
|
||||
claudeRequest.OutputConfig = json.RawMessage(`{"effort":"high"}`)
|
||||
claudeRequest.Temperature = nil
|
||||
|
||||
@@ -9,6 +9,10 @@ import (
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func commonPointer[T any](value T) *T {
|
||||
return &value
|
||||
}
|
||||
|
||||
func TestFormatClaudeResponseInfo_MessageStart(t *testing.T) {
|
||||
claudeInfo := &ClaudeResponseInfo{
|
||||
Usage: &dto.Usage{},
|
||||
@@ -310,6 +314,58 @@ func TestRequestOpenAI2ClaudeMessage_IgnoresUnsupportedFileContent(t *testing.T)
|
||||
require.Equal(t, "see attachment", *content[0].Text)
|
||||
}
|
||||
|
||||
func TestRequestOpenAI2ClaudeMessage_ClaudeOpus48HighUsesAdaptiveThinking(t *testing.T) {
|
||||
request := dto.GeneralOpenAIRequest{
|
||||
Model: "claude-opus-4-8-high",
|
||||
Temperature: commonPointer(0.7),
|
||||
TopP: commonPointer(0.9),
|
||||
TopK: commonPointer(40),
|
||||
Messages: []dto.Message{
|
||||
{
|
||||
Role: "user",
|
||||
Content: "hello",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
claudeRequest, err := RequestOpenAI2ClaudeMessage(nil, request)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "claude-opus-4-8", claudeRequest.Model)
|
||||
require.NotNil(t, claudeRequest.Thinking)
|
||||
require.Equal(t, "adaptive", claudeRequest.Thinking.Type)
|
||||
require.Equal(t, "summarized", claudeRequest.Thinking.Display)
|
||||
require.JSONEq(t, `{"effort":"high"}`, string(claudeRequest.OutputConfig))
|
||||
require.Nil(t, claudeRequest.Temperature)
|
||||
require.Nil(t, claudeRequest.TopP)
|
||||
require.Nil(t, claudeRequest.TopK)
|
||||
}
|
||||
|
||||
func TestRequestOpenAI2ClaudeMessage_ClaudeOpus48ThinkingUsesAdaptiveHighEffort(t *testing.T) {
|
||||
request := dto.GeneralOpenAIRequest{
|
||||
Model: "claude-opus-4-8-thinking",
|
||||
Temperature: commonPointer(0.7),
|
||||
TopP: commonPointer(0.9),
|
||||
TopK: commonPointer(40),
|
||||
Messages: []dto.Message{
|
||||
{
|
||||
Role: "user",
|
||||
Content: "hello",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
claudeRequest, err := RequestOpenAI2ClaudeMessage(nil, request)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "claude-opus-4-8", claudeRequest.Model)
|
||||
require.NotNil(t, claudeRequest.Thinking)
|
||||
require.Equal(t, "adaptive", claudeRequest.Thinking.Type)
|
||||
require.Equal(t, "summarized", claudeRequest.Thinking.Display)
|
||||
require.JSONEq(t, `{"effort":"high"}`, string(claudeRequest.OutputConfig))
|
||||
require.Nil(t, claudeRequest.Temperature)
|
||||
require.Nil(t, claudeRequest.TopP)
|
||||
require.Nil(t, claudeRequest.TopK)
|
||||
}
|
||||
|
||||
func TestRequestOpenAI2ClaudeMessage_SupportsPDFFileContent(t *testing.T) {
|
||||
request := dto.GeneralOpenAIRequest{
|
||||
Model: "claude-3-5-sonnet",
|
||||
|
||||
@@ -45,6 +45,7 @@ var claudeModelMap = map[string]string{
|
||||
"claude-opus-4-5-20251101": "claude-opus-4-5@20251101",
|
||||
"claude-opus-4-6": "claude-opus-4-6",
|
||||
"claude-opus-4-7": "claude-opus-4-7",
|
||||
"claude-opus-4-8": "claude-opus-4-8",
|
||||
}
|
||||
|
||||
const anthropicVersion = "vertex-2023-10-16"
|
||||
|
||||
@@ -53,14 +53,17 @@ func ClaudeHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ
|
||||
}
|
||||
|
||||
if baseModel, effortLevel, ok := reasoning.TrimEffortSuffix(request.Model); ok && effortLevel != "" &&
|
||||
(strings.HasPrefix(request.Model, "claude-opus-4-6") || strings.HasPrefix(request.Model, "claude-opus-4-7")) {
|
||||
(strings.HasPrefix(request.Model, "claude-opus-4-6") ||
|
||||
strings.HasPrefix(request.Model, "claude-opus-4-7") ||
|
||||
strings.HasPrefix(request.Model, "claude-opus-4-8")) {
|
||||
request.Model = baseModel
|
||||
request.Thinking = &dto.Thinking{
|
||||
Type: "adaptive",
|
||||
}
|
||||
request.OutputConfig = json.RawMessage(fmt.Sprintf(`{"effort":"%s"}`, effortLevel))
|
||||
if strings.HasPrefix(request.Model, "claude-opus-4-7") {
|
||||
// Opus 4.7 rejects non-default temperature/top_p/top_k with 400
|
||||
if strings.HasPrefix(request.Model, "claude-opus-4-7") ||
|
||||
strings.HasPrefix(request.Model, "claude-opus-4-8") {
|
||||
// Opus 4.7/4.8 reject non-default temperature/top_p/top_k with 400
|
||||
// and defaults display to "omitted"; restore the 4.6 visible summary.
|
||||
request.Thinking.Display = "summarized"
|
||||
request.Temperature = nil
|
||||
@@ -74,8 +77,9 @@ func ClaudeHelper(c *gin.Context, info *relaycommon.RelayInfo) (newAPIError *typ
|
||||
strings.HasSuffix(request.Model, "-thinking") {
|
||||
if request.Thinking == nil {
|
||||
baseModel := strings.TrimSuffix(request.Model, "-thinking")
|
||||
if strings.HasPrefix(baseModel, "claude-opus-4-7") {
|
||||
// Opus 4.7 rejects thinking.type="enabled"; use adaptive at high effort.
|
||||
if strings.HasPrefix(baseModel, "claude-opus-4-7") ||
|
||||
strings.HasPrefix(baseModel, "claude-opus-4-8") {
|
||||
// Opus 4.7/4.8 reject thinking.type="enabled"; use adaptive at high effort.
|
||||
request.Thinking = &dto.Thinking{Type: "adaptive", Display: "summarized"}
|
||||
request.OutputConfig = json.RawMessage(`{"effort":"high"}`)
|
||||
request.Temperature = nil
|
||||
|
||||
@@ -71,6 +71,13 @@ var defaultCacheRatio = map[string]float64{
|
||||
"claude-opus-4-7-high": 0.1,
|
||||
"claude-opus-4-7-medium": 0.1,
|
||||
"claude-opus-4-7-low": 0.1,
|
||||
"claude-opus-4-8": 0.1,
|
||||
"claude-opus-4-8-thinking": 0.1,
|
||||
"claude-opus-4-8-max": 0.1,
|
||||
"claude-opus-4-8-xhigh": 0.1,
|
||||
"claude-opus-4-8-high": 0.1,
|
||||
"claude-opus-4-8-medium": 0.1,
|
||||
"claude-opus-4-8-low": 0.1,
|
||||
}
|
||||
|
||||
var defaultCreateCacheRatio = map[string]float64{
|
||||
@@ -106,6 +113,13 @@ var defaultCreateCacheRatio = map[string]float64{
|
||||
"claude-opus-4-7-high": 1.25,
|
||||
"claude-opus-4-7-medium": 1.25,
|
||||
"claude-opus-4-7-low": 1.25,
|
||||
"claude-opus-4-8": 1.25,
|
||||
"claude-opus-4-8-thinking": 1.25,
|
||||
"claude-opus-4-8-max": 1.25,
|
||||
"claude-opus-4-8-xhigh": 1.25,
|
||||
"claude-opus-4-8-high": 1.25,
|
||||
"claude-opus-4-8-medium": 1.25,
|
||||
"claude-opus-4-8-low": 1.25,
|
||||
}
|
||||
|
||||
//var defaultCreateCacheRatio = map[string]float64{}
|
||||
|
||||
@@ -152,6 +152,12 @@ var defaultModelRatio = map[string]float64{
|
||||
"claude-opus-4-7-high": 2.5,
|
||||
"claude-opus-4-7-medium": 2.5,
|
||||
"claude-opus-4-7-low": 2.5,
|
||||
"claude-opus-4-8": 2.5,
|
||||
"claude-opus-4-8-max": 2.5,
|
||||
"claude-opus-4-8-xhigh": 2.5,
|
||||
"claude-opus-4-8-high": 2.5,
|
||||
"claude-opus-4-8-medium": 2.5,
|
||||
"claude-opus-4-8-low": 2.5,
|
||||
"claude-3-opus-20240229": 7.5, // $15 / 1M tokens
|
||||
"claude-opus-4-20250514": 7.5,
|
||||
"claude-opus-4-1-20250805": 7.5,
|
||||
|
||||
Vendored
+1136
-371
File diff suppressed because it is too large
Load Diff
Vendored
-2379
File diff suppressed because it is too large
Load Diff
Vendored
-1
@@ -24,6 +24,5 @@
|
||||
<body>
|
||||
<noscript>You need to enable JavaScript to run this app.</noscript>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/index.jsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
Vendored
+21
-20
@@ -4,30 +4,32 @@
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"dependencies": {
|
||||
"@douyinfe/semi-illustrations": "^2.69.1",
|
||||
"@douyinfe/semi-icons": "^2.63.1",
|
||||
"@douyinfe/semi-ui": "^2.69.1",
|
||||
"@lobehub/icons": "^2.0.0",
|
||||
"@lobehub/icons": "catalog:",
|
||||
"@visactor/react-vchart": "~1.8.8",
|
||||
"@visactor/vchart": "~1.8.8",
|
||||
"@visactor/vchart-semi-theme": "~1.8.8",
|
||||
"axios": "1.15.2",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.11",
|
||||
"axios": "catalog:",
|
||||
"clsx": "catalog:",
|
||||
"dayjs": "catalog:",
|
||||
"history": "^5.3.0",
|
||||
"highlight.js": "^11.11.1",
|
||||
"i18next": "^23.16.8",
|
||||
"i18next-browser-languagedetector": "^7.2.0",
|
||||
"katex": "^0.16.22",
|
||||
"lucide-react": "^0.511.0",
|
||||
"marked": "^4.1.1",
|
||||
"mermaid": "^11.6.0",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"qrcode.react": "catalog:",
|
||||
"react": "^19.2.6",
|
||||
"react-dom": "^19.2.6",
|
||||
"react-dropzone": "^14.2.3",
|
||||
"react-fireworks": "^1.0.4",
|
||||
"react-i18next": "^13.0.0",
|
||||
"react-icons": "^5.5.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-icons": "catalog:",
|
||||
"react-markdown": "catalog:",
|
||||
"react-router-dom": "^6.3.0",
|
||||
"react-telegram-login": "^1.1.2",
|
||||
"react-toastify": "^9.0.8",
|
||||
@@ -35,20 +37,20 @@
|
||||
"rehype-highlight": "^7.0.2",
|
||||
"rehype-katex": "^7.0.1",
|
||||
"remark-breaks": "^4.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-gfm": "catalog:",
|
||||
"remark-math": "^6.0.0",
|
||||
"sse.js": "^2.6.0",
|
||||
"sse.js": "catalog:",
|
||||
"unist-util-visit": "^5.0.0",
|
||||
"use-debounce": "^10.0.4"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "vite build",
|
||||
"dev": "rsbuild dev",
|
||||
"build": "rsbuild build",
|
||||
"lint": "prettier . --check",
|
||||
"lint:fix": "prettier . --write",
|
||||
"eslint": "bunx eslint \"**/*.{js,jsx}\" --cache",
|
||||
"eslint:fix": "bunx eslint \"**/*.{js,jsx}\" --fix --cache",
|
||||
"preview": "vite preview",
|
||||
"preview": "rsbuild preview",
|
||||
"i18n:extract": "bunx i18next-cli extract",
|
||||
"i18n:status": "bunx i18next-cli status",
|
||||
"i18n:sync": "bunx i18next-cli sync",
|
||||
@@ -73,20 +75,19 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@douyinfe/vite-plugin-semi": "^2.74.0-alpha.6",
|
||||
"@rsbuild/core": "^2.0.7",
|
||||
"@rsbuild/plugin-react": "^2.0.0",
|
||||
"@so1ve/prettier-config": "^3.1.0",
|
||||
"@vitejs/plugin-react": "^4.2.1",
|
||||
"autoprefixer": "^10.4.21",
|
||||
"code-inspector-plugin": "^1.3.3",
|
||||
"eslint": "8.57.0",
|
||||
"eslint-plugin-header": "^3.1.1",
|
||||
"eslint-plugin-react-hooks": "^5.2.0",
|
||||
"i18next-cli": "^1.10.3",
|
||||
"postcss": "^8.5.3",
|
||||
"prettier": "^3.0.0",
|
||||
"prop-types": "^15.8.1",
|
||||
"prettier": "catalog:",
|
||||
"tailwindcss": "^3",
|
||||
"typescript": "4.4.2",
|
||||
"vite": "^5.2.0"
|
||||
"typescript": "4.4.2"
|
||||
},
|
||||
"prettier": {
|
||||
"singleQuote": true,
|
||||
|
||||
Vendored
+106
@@ -0,0 +1,106 @@
|
||||
import path from 'path'
|
||||
import { createRequire } from 'module'
|
||||
import { fileURLToPath } from 'url'
|
||||
import { defineConfig, loadEnv } from '@rsbuild/core'
|
||||
import { pluginReact } from '@rsbuild/plugin-react'
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
||||
const require = createRequire(import.meta.url)
|
||||
const semiUiDir = path.resolve(
|
||||
path.dirname(require.resolve('@douyinfe/semi-ui')),
|
||||
'../..',
|
||||
)
|
||||
|
||||
export default defineConfig(({ envMode }) => {
|
||||
const env = loadEnv({ mode: envMode, prefixes: ['VITE_'] })
|
||||
const clientServerUrl =
|
||||
process.env.VITE_REACT_APP_SERVER_URL ||
|
||||
env.rawPublicVars.VITE_REACT_APP_SERVER_URL ||
|
||||
''
|
||||
const proxyServerUrl =
|
||||
clientServerUrl ||
|
||||
'http://localhost:3000'
|
||||
const isProd = envMode === 'production'
|
||||
const devProxy = Object.fromEntries(
|
||||
(['/api', '/mj', '/pg'] as const).map((key) => [
|
||||
key,
|
||||
{ target: proxyServerUrl, changeOrigin: true },
|
||||
]),
|
||||
) as Record<string, { target: string; changeOrigin: boolean }>
|
||||
|
||||
return {
|
||||
plugins: [pluginReact()],
|
||||
source: {
|
||||
entry: {
|
||||
index: './src/index.jsx',
|
||||
},
|
||||
define: {
|
||||
'import.meta.env.VITE_REACT_APP_SERVER_URL': JSON.stringify(
|
||||
clientServerUrl,
|
||||
),
|
||||
},
|
||||
},
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
'@douyinfe/semi-ui/dist/css/semi.css': path.resolve(
|
||||
semiUiDir,
|
||||
'dist/css/semi.css',
|
||||
),
|
||||
},
|
||||
},
|
||||
html: {
|
||||
template: './index.html',
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
strictPort: true,
|
||||
proxy: devProxy,
|
||||
},
|
||||
output: {
|
||||
minify: isProd,
|
||||
target: 'web',
|
||||
distPath: {
|
||||
root: 'dist',
|
||||
},
|
||||
},
|
||||
performance: {
|
||||
removeConsole: isProd ? ['log'] : false,
|
||||
buildCache: {
|
||||
cacheDigest: [process.env.VITE_REACT_APP_VERSION],
|
||||
},
|
||||
},
|
||||
tools: {
|
||||
rspack: {
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /src[\\/].*\.js$/,
|
||||
type: 'javascript/auto',
|
||||
use: [
|
||||
{
|
||||
loader: 'builtin:swc-loader',
|
||||
options: {
|
||||
jsc: {
|
||||
parser: {
|
||||
syntax: 'ecmascript',
|
||||
jsx: true,
|
||||
},
|
||||
transform: {
|
||||
react: {
|
||||
runtime: 'automatic',
|
||||
development: !isProd,
|
||||
refresh: !isProd,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
})
|
||||
+1
-1
@@ -947,7 +947,7 @@ const LoginForm = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
|
||||
<div className='classic-page-fill relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
|
||||
{/* 背景模糊晕染球 */}
|
||||
<div
|
||||
className='blur-ball blur-ball-indigo'
|
||||
|
||||
@@ -104,7 +104,7 @@ const PasswordResetConfirm = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
|
||||
<div className='classic-page-fill relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
|
||||
{/* 背景模糊晕染球 */}
|
||||
<div
|
||||
className='blur-ball blur-ball-indigo'
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@ const PasswordResetForm = () => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
|
||||
<div className='classic-page-fill relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
|
||||
{/* 背景模糊晕染球 */}
|
||||
<div
|
||||
className='blur-ball blur-ball-indigo'
|
||||
|
||||
+1
-1
@@ -770,7 +770,7 @@ const RegisterForm = () => {
|
||||
};
|
||||
|
||||
return (
|
||||
<div className='relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
|
||||
<div className='classic-page-fill relative overflow-hidden bg-gray-100 flex items-center justify-center py-12 px-4 sm:px-6 lg:px-8'>
|
||||
{/* 背景模糊晕染球 */}
|
||||
<div
|
||||
className='blur-ball blur-ball-indigo'
|
||||
|
||||
@@ -141,7 +141,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
|
||||
// 显示加载状态
|
||||
if (loading) {
|
||||
return (
|
||||
<div className='flex justify-center items-center min-h-screen'>
|
||||
<div className='classic-page-fill flex justify-center items-center'>
|
||||
<Spin size='large' />
|
||||
</div>
|
||||
);
|
||||
@@ -150,7 +150,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
|
||||
// 如果没有内容,显示空状态
|
||||
if (!content || content.trim() === '') {
|
||||
return (
|
||||
<div className='flex justify-center items-center min-h-screen bg-gray-50'>
|
||||
<div className='classic-page-fill flex justify-center items-center bg-gray-50'>
|
||||
<Empty
|
||||
title={t('管理员未设置' + title + '内容')}
|
||||
image={
|
||||
@@ -168,7 +168,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
|
||||
// 如果是 URL,显示链接卡片
|
||||
if (isUrl(content)) {
|
||||
return (
|
||||
<div className='flex justify-center items-center min-h-screen bg-gray-50 p-4'>
|
||||
<div className='classic-page-fill flex justify-center items-center bg-gray-50 p-4'>
|
||||
<Card className='max-w-md w-full'>
|
||||
<div className='text-center'>
|
||||
<Title heading={4} className='mb-4'>
|
||||
@@ -196,7 +196,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
|
||||
// 如果是 HTML 内容,直接渲染
|
||||
if (isHtmlContent(content)) {
|
||||
return (
|
||||
<div className='min-h-screen bg-gray-50'>
|
||||
<div className='classic-page-fill bg-gray-50'>
|
||||
<div className='max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8'>
|
||||
<div className='bg-white rounded-lg shadow-sm p-8'>
|
||||
<Title heading={2} className='text-center mb-8'>
|
||||
@@ -214,7 +214,7 @@ const DocumentRenderer = ({ apiEndpoint, title, cacheKey, emptyMessage }) => {
|
||||
|
||||
// 其他内容统一使用 Markdown 渲染器
|
||||
return (
|
||||
<div className='min-h-screen bg-gray-50'>
|
||||
<div className='classic-page-fill bg-gray-50'>
|
||||
<div className='max-w-4xl mx-auto py-12 px-4 sm:px-6 lg:px-8'>
|
||||
<div className='bg-white rounded-lg shadow-sm p-8'>
|
||||
<Title heading={2} className='text-center mb-8'>
|
||||
|
||||
+10
-5
@@ -71,6 +71,7 @@ const PageLayout = () => {
|
||||
|
||||
const isConsoleRoute = location.pathname.startsWith('/console');
|
||||
const showSider = isConsoleRoute && (!isMobile || drawerOpen);
|
||||
const isFixedLayout = isConsoleRoute || location.pathname === '/pricing';
|
||||
|
||||
useEffect(() => {
|
||||
if (isMobile && drawerOpen && collapsed) {
|
||||
@@ -146,11 +147,11 @@ const PageLayout = () => {
|
||||
|
||||
return (
|
||||
<Layout
|
||||
className='app-layout'
|
||||
className={`app-layout${isFixedLayout ? ' app-layout-fixed' : ''}`}
|
||||
style={{
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
overflow: isMobile ? 'visible' : 'hidden',
|
||||
overflow: isFixedLayout && !isMobile ? 'hidden' : 'visible',
|
||||
}}
|
||||
>
|
||||
<Header
|
||||
@@ -171,9 +172,10 @@ const PageLayout = () => {
|
||||
</Header>
|
||||
<Layout
|
||||
style={{
|
||||
overflow: isMobile ? 'visible' : 'auto',
|
||||
overflow: isFixedLayout && !isMobile ? 'auto' : 'visible',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
flex: '1 1 auto',
|
||||
}}
|
||||
>
|
||||
{showSider && (
|
||||
@@ -206,15 +208,18 @@ const PageLayout = () => {
|
||||
flex: '1 1 auto',
|
||||
display: 'flex',
|
||||
flexDirection: 'column',
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
<Content
|
||||
className={isFixedLayout ? undefined : 'public-page-content'}
|
||||
style={{
|
||||
flex: '1 0 auto',
|
||||
overflowY: isMobile ? 'visible' : 'hidden',
|
||||
flex: isFixedLayout ? '1 0 auto' : '1 1 auto',
|
||||
overflowY: isFixedLayout && !isMobile ? 'hidden' : 'visible',
|
||||
WebkitOverflowScrolling: 'touch',
|
||||
padding: shouldInnerPadding ? (isMobile ? '5px' : '24px') : '0',
|
||||
position: 'relative',
|
||||
minHeight: 0,
|
||||
}}
|
||||
>
|
||||
<ErrorBoundary>
|
||||
|
||||
Vendored
+43
-9
@@ -17,12 +17,46 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
export * from './channel.constants';
|
||||
export * from './user.constants';
|
||||
export * from './toast.constants';
|
||||
export * from './common.constant';
|
||||
export * from './dashboard.constants';
|
||||
export * from './playground.constants';
|
||||
export * from './redemption.constants';
|
||||
export * from './channel-affinity-template.constants';
|
||||
export * from './billing.constants';
|
||||
export {
|
||||
CHANNEL_OPTIONS,
|
||||
MODEL_FETCHABLE_CHANNEL_TYPES,
|
||||
MODEL_TABLE_PAGE_SIZE,
|
||||
} from './channel.constants';
|
||||
export { userConstants } from './user.constants';
|
||||
export { toastConstants } from './toast.constants';
|
||||
export {
|
||||
ITEMS_PER_PAGE,
|
||||
DEFAULT_ENDPOINT,
|
||||
TABLE_COMPACT_MODES_KEY,
|
||||
API_ENDPOINTS,
|
||||
TASK_ACTION_GENERATE,
|
||||
TASK_ACTION_TEXT_GENERATE,
|
||||
TASK_ACTION_FIRST_TAIL_GENERATE,
|
||||
TASK_ACTION_REFERENCE_GENERATE,
|
||||
TASK_ACTION_REMIX_GENERATE,
|
||||
} from './common.constant';
|
||||
export {
|
||||
REDEMPTION_STATUS,
|
||||
REDEMPTION_STATUS_MAP,
|
||||
REDEMPTION_ACTIONS,
|
||||
} from './redemption.constants';
|
||||
export {
|
||||
CODEX_CLI_HEADER_PASSTHROUGH_HEADERS,
|
||||
CLAUDE_CLI_HEADER_PASSTHROUGH_HEADERS,
|
||||
CODEX_CLI_HEADER_PASSTHROUGH_TEMPLATE,
|
||||
CLAUDE_CLI_HEADER_PASSTHROUGH_TEMPLATE,
|
||||
CHANNEL_AFFINITY_RULE_TEMPLATES,
|
||||
cloneChannelAffinityTemplate,
|
||||
} from './channel-affinity-template.constants';
|
||||
export {
|
||||
BILLING_VARS,
|
||||
BILLING_VAR_KEYS,
|
||||
BILLING_PRICING_VARS,
|
||||
BILLING_EXTRA_VARS,
|
||||
BILLING_VAR_KEY_TO_FIELD,
|
||||
BILLING_VAR_FIELD_TO_LABEL,
|
||||
BILLING_VAR_FIELD_TO_SHORT_LABEL,
|
||||
BILLING_CACHE_VAR_MAP,
|
||||
BILLING_VAR_REGEX,
|
||||
BILLING_CONDITION_VARS,
|
||||
} from './billing.constants';
|
||||
|
||||
Vendored
+2
-2
@@ -94,7 +94,6 @@ import {
|
||||
SiGitlab,
|
||||
SiGoogle,
|
||||
SiKeycloak,
|
||||
SiLinkedin,
|
||||
SiNextcloud,
|
||||
SiNotion,
|
||||
SiOkta,
|
||||
@@ -106,6 +105,7 @@ import {
|
||||
SiWechat,
|
||||
SiX,
|
||||
} from 'react-icons/si';
|
||||
import { FaLinkedin } from 'react-icons/fa';
|
||||
|
||||
// 获取侧边栏Lucide图标组件
|
||||
export function getLucideIcon(key, selected = false) {
|
||||
@@ -509,7 +509,7 @@ const oauthProviderIconMap = {
|
||||
google: SiGoogle,
|
||||
discord: SiDiscord,
|
||||
facebook: SiFacebook,
|
||||
linkedin: SiLinkedin,
|
||||
linkedin: FaLinkedin,
|
||||
x: SiX,
|
||||
twitter: SiX,
|
||||
slack: SiSlack,
|
||||
|
||||
Vendored
+1
-1
@@ -123,7 +123,7 @@ export function showError(error) {
|
||||
console.error(error);
|
||||
if (error.message) {
|
||||
if (error.name === 'AxiosError') {
|
||||
switch (error.response.status) {
|
||||
switch (error.response?.status) {
|
||||
case 401:
|
||||
// 清除用户状态
|
||||
localStorage.removeItem('user');
|
||||
|
||||
Vendored
+28
-6
@@ -31,18 +31,40 @@ body {
|
||||
background-color: var(--semi-color-bg-0);
|
||||
}
|
||||
|
||||
/* 桌面端禁止 body 纵向滚动 - 防止 VChart tooltip 触发页面滚动条 */
|
||||
@media (min-width: 768px) {
|
||||
body {
|
||||
overflow-y: hidden;
|
||||
}
|
||||
.app-layout {
|
||||
min-height: 100vh;
|
||||
min-height: 100dvh;
|
||||
}
|
||||
|
||||
.app-layout {
|
||||
.app-layout-fixed {
|
||||
height: 100vh;
|
||||
height: 100dvh;
|
||||
}
|
||||
|
||||
.public-page-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.classic-page-fill {
|
||||
flex: 1 0 auto;
|
||||
min-height: 100%;
|
||||
}
|
||||
|
||||
.classic-home-page,
|
||||
.classic-home-default {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.classic-home-default {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.classic-home-hero {
|
||||
flex: 1 0 auto;
|
||||
}
|
||||
|
||||
.app-sider {
|
||||
height: calc(100vh - 64px);
|
||||
height: calc(100dvh - 64px);
|
||||
|
||||
Vendored
+2
@@ -1,3 +1,5 @@
|
||||
import '@douyinfe/semi-ui/react19-adapter';
|
||||
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
|
||||
+8
-3
@@ -133,9 +133,9 @@ const About = () => {
|
||||
);
|
||||
|
||||
return (
|
||||
<div className='mt-[60px] px-2'>
|
||||
<div className='classic-page-fill flex flex-col pt-[60px] px-2'>
|
||||
{aboutLoaded && about === '' ? (
|
||||
<div className='flex justify-center items-center h-screen p-8'>
|
||||
<div className='flex flex-1 justify-center items-center p-8'>
|
||||
<Empty
|
||||
image={
|
||||
<IllustrationConstruction style={{ width: 150, height: 150 }} />
|
||||
@@ -156,7 +156,12 @@ const About = () => {
|
||||
{about.startsWith('https://') ? (
|
||||
<iframe
|
||||
src={about}
|
||||
style={{ width: '100%', height: '100vh', border: 'none' }}
|
||||
style={{
|
||||
width: '100%',
|
||||
flex: '1 1 auto',
|
||||
minHeight: 0,
|
||||
border: 'none',
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import { useTranslation } from 'react-i18next';
|
||||
const Forbidden = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className='flex justify-center items-center h-screen p-8'>
|
||||
<div className='classic-page-fill flex justify-center items-center p-8'>
|
||||
<Empty
|
||||
image={<IllustrationNoAccess style={{ width: 250, height: 250 }} />}
|
||||
darkModeImage={
|
||||
|
||||
Vendored
+6
-6
@@ -149,20 +149,20 @@ const Home = () => {
|
||||
}, [endpointItems.length]);
|
||||
|
||||
return (
|
||||
<div className='w-full overflow-x-hidden'>
|
||||
<div className='classic-page-fill classic-home-page w-full overflow-x-hidden'>
|
||||
<NoticeModal
|
||||
visible={noticeVisible}
|
||||
onClose={() => setNoticeVisible(false)}
|
||||
isMobile={isMobile}
|
||||
/>
|
||||
{homePageContentLoaded && homePageContent === '' ? (
|
||||
<div className='w-full overflow-x-hidden'>
|
||||
<div className='classic-home-default w-full overflow-x-hidden'>
|
||||
{/* Banner 部分 */}
|
||||
<div className='w-full border-b border-semi-color-border min-h-[500px] md:min-h-[600px] lg:min-h-[700px] relative overflow-x-hidden'>
|
||||
<div className='classic-home-hero w-full border-b border-semi-color-border relative overflow-x-hidden'>
|
||||
{/* 背景模糊晕染球 */}
|
||||
<div className='blur-ball blur-ball-indigo' />
|
||||
<div className='blur-ball blur-ball-teal' />
|
||||
<div className='flex items-center justify-center h-full px-4 py-20 md:py-24 lg:py-32 mt-10'>
|
||||
<div className='flex items-center justify-center px-4 pt-24 pb-8'>
|
||||
{/* 居中内容区 */}
|
||||
<div className='flex flex-col items-center justify-center text-center max-w-4xl mx-auto'>
|
||||
<div className='flex flex-col items-center justify-center mb-6 md:mb-8'>
|
||||
@@ -335,11 +335,11 @@ const Home = () => {
|
||||
</div>
|
||||
</div>
|
||||
) : (
|
||||
<div className='overflow-x-hidden w-full'>
|
||||
<div className='classic-page-fill overflow-x-hidden w-full'>
|
||||
{homePageContent.startsWith('https://') ? (
|
||||
<iframe
|
||||
src={homePageContent}
|
||||
className='w-full h-screen border-none'
|
||||
className='w-full h-full border-none'
|
||||
/>
|
||||
) : (
|
||||
<div
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ import { useTranslation } from 'react-i18next';
|
||||
const NotFound = () => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<div className='flex justify-center items-center h-screen p-8'>
|
||||
<div className='classic-page-fill flex justify-center items-center p-8'>
|
||||
<Empty
|
||||
image={<IllustrationNotFound style={{ width: 250, height: 250 }} />}
|
||||
darkModeImage={
|
||||
|
||||
Vendored
-107
@@ -1,107 +0,0 @@
|
||||
/*
|
||||
Copyright (C) 2025 QuantumNous
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU Affero General Public License as
|
||||
published by the Free Software Foundation, either version 3 of the
|
||||
License, or (at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful,
|
||||
but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
GNU Affero General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU Affero General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
For commercial licensing, please contact support@quantumnous.com
|
||||
*/
|
||||
|
||||
import react from '@vitejs/plugin-react';
|
||||
import { defineConfig, transformWithEsbuild } from 'vite';
|
||||
import pkg from '@douyinfe/vite-plugin-semi';
|
||||
import path from 'path';
|
||||
import { codeInspectorPlugin } from 'code-inspector-plugin';
|
||||
const { vitePluginSemi } = pkg;
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, './src'),
|
||||
},
|
||||
},
|
||||
plugins: [
|
||||
codeInspectorPlugin({
|
||||
bundler: 'vite',
|
||||
}),
|
||||
{
|
||||
name: 'treat-js-files-as-jsx',
|
||||
async transform(code, id) {
|
||||
if (!/src\/.*\.js$/.test(id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Use the exposed transform from vite, instead of directly
|
||||
// transforming with esbuild
|
||||
return transformWithEsbuild(code, id, {
|
||||
loader: 'jsx',
|
||||
jsx: 'automatic',
|
||||
});
|
||||
},
|
||||
},
|
||||
react(),
|
||||
vitePluginSemi({
|
||||
cssLayer: true,
|
||||
}),
|
||||
],
|
||||
optimizeDeps: {
|
||||
force: true,
|
||||
esbuildOptions: {
|
||||
loader: {
|
||||
'.js': 'jsx',
|
||||
'.json': 'json',
|
||||
},
|
||||
},
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
'react-core': ['react', 'react-dom', 'react-router-dom'],
|
||||
'semi-ui': ['@douyinfe/semi-icons', '@douyinfe/semi-ui'],
|
||||
tools: ['axios', 'history', 'marked'],
|
||||
'react-components': [
|
||||
'react-dropzone',
|
||||
'react-fireworks',
|
||||
'react-telegram-login',
|
||||
'react-toastify',
|
||||
'react-turnstile',
|
||||
],
|
||||
i18n: [
|
||||
'i18next',
|
||||
'react-i18next',
|
||||
'i18next-browser-languagedetector',
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/mj': {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
'/pg': {
|
||||
target: 'http://localhost:3000',
|
||||
changeOrigin: true,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
Vendored
+10
-10
@@ -24,7 +24,7 @@
|
||||
"@hookform/resolvers": "^5.4.0",
|
||||
"@hugeicons/core-free-icons": "^4.1.4",
|
||||
"@hugeicons/react": "^1.1.6",
|
||||
"@lobehub/icons": "^5.8.0",
|
||||
"@lobehub/icons": "catalog:",
|
||||
"@tailwindcss/postcss": "^4.3.0",
|
||||
"@tanstack/react-query": "^5.100.14",
|
||||
"@tanstack/react-router": "^1.170.8",
|
||||
@@ -34,12 +34,12 @@
|
||||
"@visactor/vchart": "^2.0.22",
|
||||
"ai": "^6.0.191",
|
||||
"auto-skeleton-react": "^1.0.5",
|
||||
"axios": "^1.16.1",
|
||||
"axios": "catalog:",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"clsx": "catalog:",
|
||||
"cmdk": "^1.1.1",
|
||||
"date-fns": "^4.3.0",
|
||||
"dayjs": "^1.11.20",
|
||||
"dayjs": "catalog:",
|
||||
"i18next": "^26.2.0",
|
||||
"i18next-browser-languagedetector": "^8.2.1",
|
||||
"input-otp": "^1.4.2",
|
||||
@@ -47,22 +47,22 @@
|
||||
"motion": "^12.40.0",
|
||||
"nanoid": "^5.1.11",
|
||||
"next-themes": "^0.4.6",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"qrcode.react": "catalog:",
|
||||
"react": "^19.2.6",
|
||||
"react-day-picker": "^10.0.1",
|
||||
"react-dom": "^19.2.6",
|
||||
"react-hook-form": "^7.76.1",
|
||||
"react-i18next": "^17.0.8",
|
||||
"react-icons": "^5.6.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"react-icons": "catalog:",
|
||||
"react-markdown": "catalog:",
|
||||
"react-resizable-panels": "^4.11.2",
|
||||
"react-top-loading-bar": "^3.0.2",
|
||||
"recharts": "3.8.1",
|
||||
"rehype-raw": "^7.0.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"remark-gfm": "catalog:",
|
||||
"shiki": "^4.1.0",
|
||||
"sonner": "^2.0.7",
|
||||
"sse.js": "^2.8.0",
|
||||
"sse.js": "catalog:",
|
||||
"streamdown": "^2.5.0",
|
||||
"tailwind-merge": "^3.6.0",
|
||||
"tailwindcss": "^4.3.0",
|
||||
@@ -92,7 +92,7 @@
|
||||
"eslint-plugin-react-refresh": "^0.5.2",
|
||||
"globals": "^17.6.0",
|
||||
"knip": "^6.14.2",
|
||||
"prettier": "^3.8.3",
|
||||
"prettier": "catalog:",
|
||||
"prettier-plugin-tailwindcss": "^0.8.0",
|
||||
"shadcn": "^4.8.0",
|
||||
"typescript": "~6.0.3",
|
||||
|
||||
Vendored
+1
@@ -65,6 +65,7 @@ export default defineConfig(({ envMode }) => {
|
||||
},
|
||||
server: {
|
||||
host: '0.0.0.0',
|
||||
strictPort: true,
|
||||
proxy: devProxy,
|
||||
},
|
||||
output: {
|
||||
|
||||
@@ -189,6 +189,7 @@ export function CCSwitchDialog(props: Props) {
|
||||
onValueChange={setName}
|
||||
placeholder={currentConfig.defaultName}
|
||||
emptyText=''
|
||||
allowCustomValue={true}
|
||||
/>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -56,8 +56,9 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
|
||||
const tags = parseTags(props.model.tags)
|
||||
const groups = props.model.enable_groups || []
|
||||
const endpoints = props.model.supported_endpoint_types || []
|
||||
const vendorIcon = props.model.vendor_icon
|
||||
? getLobeIcon(props.model.vendor_icon, 28)
|
||||
const modelIconKey = props.model.icon || props.model.vendor_icon
|
||||
const modelIcon = modelIconKey
|
||||
? getLobeIcon(modelIconKey, 28)
|
||||
: null
|
||||
const initial = props.model.model_name?.charAt(0).toUpperCase() || '?'
|
||||
const isDynamicPricing =
|
||||
@@ -97,7 +98,7 @@ export const ModelCard = memo(function ModelCard(props: ModelCardProps) {
|
||||
<div className='flex items-start justify-between gap-2.5 sm:gap-3'>
|
||||
<div className='flex min-w-0 items-start gap-2.5 sm:gap-3'>
|
||||
<div className='bg-muted/40 flex size-9 shrink-0 items-center justify-center rounded-lg sm:size-10 sm:rounded-xl'>
|
||||
{vendorIcon || (
|
||||
{modelIcon || (
|
||||
<span className='text-muted-foreground text-sm font-bold'>
|
||||
{initial}
|
||||
</span>
|
||||
|
||||
@@ -268,8 +268,9 @@ function OverviewSummaryGrid(props: { model: PricingModel }) {
|
||||
function ModelHeader(props: { model: PricingModel }) {
|
||||
const { t } = useTranslation()
|
||||
const model = props.model
|
||||
const vendorIcon = model.vendor_icon
|
||||
? getLobeIcon(model.vendor_icon, 20)
|
||||
const modelIconKey = model.icon || model.vendor_icon
|
||||
const modelIcon = modelIconKey
|
||||
? getLobeIcon(modelIconKey, 20)
|
||||
: null
|
||||
const description = model.description || model.vendor_description || null
|
||||
const tags = parseTags(model.tags)
|
||||
@@ -281,7 +282,7 @@ function ModelHeader(props: { model: PricingModel }) {
|
||||
return (
|
||||
<header className='pb-4'>
|
||||
<div className='flex items-center gap-2.5'>
|
||||
{vendorIcon}
|
||||
{modelIcon}
|
||||
<h1 className='font-mono text-xl font-bold tracking-tight sm:text-2xl'>
|
||||
{model.model_name}
|
||||
</h1>
|
||||
|
||||
@@ -106,13 +106,14 @@ export function usePricingColumns(
|
||||
),
|
||||
cell: ({ row }) => {
|
||||
const model = row.original
|
||||
const vendorIcon = model.vendor_icon
|
||||
? getLobeIcon(model.vendor_icon, 14)
|
||||
const modelIconKey = model.icon || model.vendor_icon
|
||||
const modelIcon = modelIconKey
|
||||
? getLobeIcon(modelIconKey, 14)
|
||||
: null
|
||||
|
||||
return (
|
||||
<div className='flex min-w-[200px] items-center gap-2'>
|
||||
{vendorIcon}
|
||||
{modelIcon}
|
||||
<span className='truncate font-mono text-sm font-medium'>
|
||||
{model.model_name}
|
||||
</span>
|
||||
|
||||
+1
@@ -31,6 +31,7 @@ export type PricingModel = {
|
||||
id: number
|
||||
model_name: string
|
||||
description?: string
|
||||
icon?: string
|
||||
vendor_id?: number
|
||||
vendor_name?: string
|
||||
vendor_icon?: string
|
||||
|
||||
+20
-3
@@ -111,6 +111,7 @@ export function SubscriptionPurchaseDialog(props: Props) {
|
||||
Math.ceil(Number(plan.price_amount || 0) * quotaPerUnit)
|
||||
)
|
||||
const userQuota = Math.max(0, Number(props.userQuota || 0))
|
||||
const allowBalancePay = plan.allow_balance_pay !== false
|
||||
const insufficientBalance = userQuota < balanceCost
|
||||
const limitReached =
|
||||
(props.purchaseLimit || 0) > 0 &&
|
||||
@@ -232,6 +233,10 @@ export function SubscriptionPurchaseDialog(props: Props) {
|
||||
}
|
||||
|
||||
const handlePayBalance = async () => {
|
||||
if (!allowBalancePay) {
|
||||
toast.error(t('This plan does not allow balance redemption'))
|
||||
return
|
||||
}
|
||||
setPaying(true)
|
||||
try {
|
||||
const res = await paySubscriptionBalance({ plan_id: plan.id })
|
||||
@@ -332,15 +337,27 @@ export function SubscriptionPurchaseDialog(props: Props) {
|
||||
<span className='text-muted-foreground'>{t('Available')}</span>
|
||||
<span>{formatQuota(userQuota)}</span>
|
||||
</div>
|
||||
{insufficientBalance && (
|
||||
{!allowBalancePay ? (
|
||||
<Alert variant='destructive'>
|
||||
<AlertDescription>{t('Insufficient balance')}</AlertDescription>
|
||||
<AlertDescription>
|
||||
{t('This plan does not allow balance redemption')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
) : (
|
||||
insufficientBalance && (
|
||||
<Alert variant='destructive'>
|
||||
<AlertDescription>
|
||||
{t('Insufficient balance')}
|
||||
</AlertDescription>
|
||||
</Alert>
|
||||
)
|
||||
)}
|
||||
<Button
|
||||
variant='outline'
|
||||
onClick={handlePayBalance}
|
||||
disabled={paying || limitReached || insufficientBalance}
|
||||
disabled={
|
||||
paying || limitReached || !allowBalancePay || insufficientBalance
|
||||
}
|
||||
>
|
||||
{t('Pay with Balance')}
|
||||
</Button>
|
||||
|
||||
+18
@@ -461,6 +461,24 @@ export function SubscriptionsMutateDrawer({
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<FormField
|
||||
control={form.control}
|
||||
name='allow_balance_pay'
|
||||
render={({ field }) => (
|
||||
<FormItem className={sideDrawerSwitchItemClassName()}>
|
||||
<FormLabel className='!mt-0'>
|
||||
{t('Allow balance redemption')}
|
||||
</FormLabel>
|
||||
<FormControl>
|
||||
<Switch
|
||||
checked={field.value}
|
||||
onCheckedChange={field.onChange}
|
||||
/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
</SideDrawerSection>
|
||||
|
||||
|
||||
@@ -39,6 +39,7 @@ export function getPlanFormSchema(t: TFunction) {
|
||||
quota_reset_custom_seconds: z.coerce.number().min(0).optional(),
|
||||
enabled: z.boolean(),
|
||||
sort_order: z.coerce.number(),
|
||||
allow_balance_pay: z.boolean(),
|
||||
max_purchase_per_user: z.coerce.number().min(0),
|
||||
total_amount: z.coerce.number().min(0),
|
||||
upgrade_group: z.string().optional(),
|
||||
@@ -61,6 +62,7 @@ export const PLAN_FORM_DEFAULTS: PlanFormValues = {
|
||||
quota_reset_custom_seconds: 0,
|
||||
enabled: true,
|
||||
sort_order: 0,
|
||||
allow_balance_pay: true,
|
||||
max_purchase_per_user: 0,
|
||||
total_amount: 0,
|
||||
upgrade_group: '',
|
||||
@@ -81,6 +83,7 @@ export function planToFormValues(plan: SubscriptionPlan): PlanFormValues {
|
||||
quota_reset_custom_seconds: Number(plan.quota_reset_custom_seconds || 0),
|
||||
enabled: plan.enabled !== false,
|
||||
sort_order: Number(plan.sort_order || 0),
|
||||
allow_balance_pay: plan.allow_balance_pay !== false,
|
||||
max_purchase_per_user: Number(plan.max_purchase_per_user || 0),
|
||||
total_amount: quotaUnitsToDollars(Number(plan.total_amount || 0)),
|
||||
upgrade_group: plan.upgrade_group || '',
|
||||
|
||||
@@ -35,6 +35,7 @@ export const subscriptionPlanSchema = z.object({
|
||||
quota_reset_custom_seconds: z.number().optional(),
|
||||
enabled: z.boolean(),
|
||||
sort_order: z.number(),
|
||||
allow_balance_pay: z.boolean().optional().default(true),
|
||||
max_purchase_per_user: z.number(),
|
||||
total_amount: z.number(),
|
||||
upgrade_group: z.string().optional(),
|
||||
|
||||
@@ -183,6 +183,9 @@ function CommonLogsCard<TData>({
|
||||
|
||||
const modelCell = cells.get('model_name')
|
||||
const quotaCell = cells.get('quota')
|
||||
const original = cells.get('created_at')?.row.original as
|
||||
| Record<string, unknown>
|
||||
| undefined
|
||||
|
||||
return (
|
||||
<div className='space-y-2.5'>
|
||||
@@ -200,8 +203,8 @@ function CommonLogsCard<TData>({
|
||||
{t('Time')}
|
||||
</div>
|
||||
<MobileLogTimeStatus
|
||||
createdAt={cells.get('created_at')?.row.original?.created_at}
|
||||
type={cells.get('created_at')?.row.original?.type}
|
||||
createdAt={original?.created_at}
|
||||
type={original?.type}
|
||||
/>
|
||||
</div>
|
||||
<SummaryField
|
||||
|
||||
Vendored
+4
-2
@@ -268,6 +268,7 @@
|
||||
"All-time": "All-time",
|
||||
"Allocated Memory": "Allocated Memory",
|
||||
"Allow accountFilter parameter": "Allow accountFilter parameter",
|
||||
"Allow balance redemption": "Allow balance redemption",
|
||||
"Allow Claude beta query passthrough": "Allow Claude beta query passthrough",
|
||||
"Allow clients to query configured ratios via `/api/ratio`.": "Allow clients to query configured ratios via `/api/ratio`.",
|
||||
"Allow HTTP image requests": "Allow HTTP image requests",
|
||||
@@ -2853,8 +2854,8 @@
|
||||
"Path Regex (one per line)": "Path Regex (one per line)",
|
||||
"Path:": "Path:",
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Pay-as-you-go with real-time usage monitoring",
|
||||
"Pay with Balance": "Pay with Balance",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Pay-as-you-go with real-time usage monitoring",
|
||||
"Payment": "Payment",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.",
|
||||
"Payment Channel": "Payment Channel",
|
||||
@@ -3767,10 +3768,10 @@
|
||||
"Subscription First": "Subscription First",
|
||||
"Subscription Management": "Subscription Management",
|
||||
"Subscription Only": "Subscription Only",
|
||||
"Subscription purchased successfully": "Subscription purchased successfully",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.",
|
||||
"Subscription Plans": "Subscription Plans",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).",
|
||||
"Subscription purchased successfully": "Subscription purchased successfully",
|
||||
"Subtract": "Subtract",
|
||||
"Success": "Success",
|
||||
"Success rate": "Success rate",
|
||||
@@ -3951,6 +3952,7 @@
|
||||
"This model is not available in any group, or no group pricing information is configured.": "This model is not available in any group, or no group pricing information is configured.",
|
||||
"This month": "This month",
|
||||
"This page has not been created yet.": "This page has not been created yet.",
|
||||
"This plan does not allow balance redemption": "This plan does not allow balance redemption",
|
||||
"This project must be used in compliance with the": "This project must be used in compliance with the",
|
||||
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.",
|
||||
"This site currently has {{count}} models enabled": "This site currently has {{count}} models enabled",
|
||||
|
||||
Vendored
+4
-2
@@ -268,6 +268,7 @@
|
||||
"All-time": "Tous temps",
|
||||
"Allocated Memory": "Mémoire allouée",
|
||||
"Allow accountFilter parameter": "Autoriser le paramètre accountFilter",
|
||||
"Allow balance redemption": "Autoriser le paiement avec le solde",
|
||||
"Allow Claude beta query passthrough": "Autoriser le passage des requêtes bêta Claude",
|
||||
"Allow clients to query configured ratios via `/api/ratio`.": "Autoriser les clients à interroger les ratios configurés via `/api/ratio`.",
|
||||
"Allow HTTP image requests": "Autoriser les requêtes d'images HTTP",
|
||||
@@ -2853,8 +2854,8 @@
|
||||
"Path Regex (one per line)": "Regex du chemin (un par ligne)",
|
||||
"Path:": "Chemin :",
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Paiement à l'usage avec suivi de la consommation en temps réel",
|
||||
"Pay with Balance": "Payer avec le solde",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Paiement à l'usage avec suivi de la consommation en temps réel",
|
||||
"Payment": "Paiement",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "Mode agrégateur de paiement — embarquez avec votre propre société enregistrée (entité offshore). Conçu pour les entreprises.",
|
||||
"Payment Channel": "Canal de paiement",
|
||||
@@ -3767,10 +3768,10 @@
|
||||
"Subscription First": "Abonnement en priorité",
|
||||
"Subscription Management": "Gestion des abonnements",
|
||||
"Subscription Only": "Abonnement uniquement",
|
||||
"Subscription purchased successfully": "Abonnement acheté avec succès",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "La création et la modification des forfaits d’abonnement sont verrouillées jusqu’à ce que l’administrateur confirme les conditions de conformité dans les paramètres de la passerelle de paiement.",
|
||||
"Subscription Plans": "Plans d'abonnement",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "Les forfaits d’abonnement n’utilisent PAS le produit associé : chaque forfait dispose de son propre produit Pancake dédié, défini dans l’administration des abonnements (ou créé automatiquement via le bouton « + Créer »).",
|
||||
"Subscription purchased successfully": "Abonnement acheté avec succès",
|
||||
"Subtract": "Soustraire",
|
||||
"Success": "Succès",
|
||||
"Success rate": "Taux de réussite",
|
||||
@@ -3951,6 +3952,7 @@
|
||||
"This model is not available in any group, or no group pricing information is configured.": "Ce modèle n'est disponible dans aucun groupe, ou aucune information de tarification de groupe n'est configurée.",
|
||||
"This month": "Ce mois-ci",
|
||||
"This page has not been created yet.": "Cette page n'a pas encore été créée.",
|
||||
"This plan does not allow balance redemption": "Ce forfait ne permet pas le paiement avec le solde",
|
||||
"This project must be used in compliance with the": "Ce projet doit être utilisé conformément aux",
|
||||
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "Cet enregistrement provient d’une instance avant la mise à niveau et n’inclut pas d’audits. Mettez à jour l’instance pour enregistrer l’IP du serveur, l’IP de callback, le moyen de paiement et la version du système.",
|
||||
"This site currently has {{count}} models enabled": "Ce site compte actuellement {{count}} modèles activés",
|
||||
|
||||
Vendored
+4
-2
@@ -268,6 +268,7 @@
|
||||
"All-time": "全期間",
|
||||
"Allocated Memory": "割り当て済みメモリ",
|
||||
"Allow accountFilter parameter": "accountFilter パラメータを許可",
|
||||
"Allow balance redemption": "残高での交換を許可",
|
||||
"Allow Claude beta query passthrough": "Claude ベータクエリのパススルーを許可",
|
||||
"Allow clients to query configured ratios via `/api/ratio`.": "クライアントが `/api/ratio` 経由で設定された比率を照会できるようにします。",
|
||||
"Allow HTTP image requests": "HTTP画像リクエストを許可",
|
||||
@@ -2853,8 +2854,8 @@
|
||||
"Path Regex (one per line)": "パス正規表現(1行に1つ)",
|
||||
"Path:": "パス:",
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "リアルタイム使用量監視付き従量課金制",
|
||||
"Pay with Balance": "残高で支払う",
|
||||
"Pay-as-you-go with real-time usage monitoring": "リアルタイム使用量監視付き従量課金制",
|
||||
"Payment": "支払い",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "決済アグリゲーターモード — 自社の登録済み法人(オフショア法人)でオンボーディングします。エンタープライズ向けです。",
|
||||
"Payment Channel": "決済チャネル",
|
||||
@@ -3767,10 +3768,10 @@
|
||||
"Subscription First": "サブスクリプション優先",
|
||||
"Subscription Management": "サブスクリプション管理",
|
||||
"Subscription Only": "サブスクリプションのみ",
|
||||
"Subscription purchased successfully": "サブスクリプションを購入しました",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "管理者が支払いゲートウェイ設定でコンプライアンス条件を確認するまで、サブスクリプションプランの作成と変更はロックされます。",
|
||||
"Subscription Plans": "サブスクリプションプラン",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "サブスクリプションプランは紐付け済み商品を使用しません。各プランには専用の Pancake 商品があり、サブスクリプション管理画面で設定します(または「+ 作成」ボタンで自動作成します)。",
|
||||
"Subscription purchased successfully": "サブスクリプションを購入しました",
|
||||
"Subtract": "減算",
|
||||
"Success": "成功",
|
||||
"Success rate": "成功率",
|
||||
@@ -3951,6 +3952,7 @@
|
||||
"This model is not available in any group, or no group pricing information is configured.": "このモデルはどのグループでも利用できないか、グループの料金情報が設定されていません。",
|
||||
"This month": "今月",
|
||||
"This page has not been created yet.": "このページはまだ作成されていません。",
|
||||
"This plan does not allow balance redemption": "このプランでは残高での交換は許可されていません",
|
||||
"This project must be used in compliance with the": "このプロジェクトは、以下を遵守して使用する必要があります",
|
||||
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "古いバージョンのインスタンスがこの記録を書き込み、監査情報がありません。最新に更新し、サーバーIP・コールバックIP・支払方法・OSバージョンの記録を有効にしてください。",
|
||||
"This site currently has {{count}} models enabled": "このサイトでは現在 {{count}} 個のモデルが有効です",
|
||||
|
||||
Vendored
+4
-2
@@ -268,6 +268,7 @@
|
||||
"All-time": "За всё время",
|
||||
"Allocated Memory": "Выделенная память",
|
||||
"Allow accountFilter parameter": "Разрешить параметр accountFilter",
|
||||
"Allow balance redemption": "Разрешить оплату балансом",
|
||||
"Allow Claude beta query passthrough": "Разрешить проброс бета-запросов Claude",
|
||||
"Allow clients to query configured ratios via `/api/ratio`.": "Разрешить клиентам запрашивать настроенные соотношения через `/api/ratio`.",
|
||||
"Allow HTTP image requests": "Разрешить HTTP-запросы изображений",
|
||||
@@ -2853,8 +2854,8 @@
|
||||
"Path Regex (one per line)": "Регулярное выражение пути (по одному на строку)",
|
||||
"Path:": "Путь:",
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Оплата по мере использования с мониторингом в реальном времени",
|
||||
"Pay with Balance": "Оплатить балансом",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Оплата по мере использования с мониторингом в реальном времени",
|
||||
"Payment": "Платеж",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "Режим платежного агрегатора — подключение через вашу зарегистрированную компанию (офшорное юрлицо). Создано для Enterprise.",
|
||||
"Payment Channel": "Платёжный канал",
|
||||
@@ -3767,10 +3768,10 @@
|
||||
"Subscription First": "Подписка в приоритете",
|
||||
"Subscription Management": "Управление подписками",
|
||||
"Subscription Only": "Только подписка",
|
||||
"Subscription purchased successfully": "Подписка успешно приобретена",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Создание и изменение планов подписки заблокированы, пока администратор не подтвердит условия соответствия в настройках платежного шлюза.",
|
||||
"Subscription Plans": "Планы подписки",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "Планы подписки НЕ используют привязанный продукт — у каждого плана есть собственный продукт Pancake, задаваемый в администрировании подписок (или автоматически создаваемый кнопкой «+ Создать»).",
|
||||
"Subscription purchased successfully": "Подписка успешно приобретена",
|
||||
"Subtract": "Вычесть",
|
||||
"Success": "Успешно",
|
||||
"Success rate": "Доля успешных запросов",
|
||||
@@ -3951,6 +3952,7 @@
|
||||
"This model is not available in any group, or no group pricing information is configured.": "Эта модель недоступна ни в одной группе, или информация о ценах для групп не настроена.",
|
||||
"This month": "В этом месяце",
|
||||
"This page has not been created yet.": "Эта страница еще не создана.",
|
||||
"This plan does not allow balance redemption": "Этот план не разрешает оплату балансом",
|
||||
"This project must be used in compliance with the": "Этот проект должен использоваться в соответствии с",
|
||||
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "Запись создана экземпляром до обновления и не содержит сведений аудита. Обновите экземпляр, чтобы фиксировать IP сервера, IP callback, способ оплаты и версию ОС.",
|
||||
"This site currently has {{count}} models enabled": "На этом сайте сейчас включено моделей: {{count}}",
|
||||
|
||||
Vendored
+4
-2
@@ -268,6 +268,7 @@
|
||||
"All-time": "Mọi thời điểm",
|
||||
"Allocated Memory": "Bộ nhớ đã cấp phát",
|
||||
"Allow accountFilter parameter": "Cho phép tham số accountFilter",
|
||||
"Allow balance redemption": "Cho phép thanh toán bằng số dư",
|
||||
"Allow Claude beta query passthrough": "Cho phép chuyển tiếp truy vấn beta Claude",
|
||||
"Allow clients to query configured ratios via `/api/ratio`.": "Cho phép khách hàng truy vấn các tỷ lệ đã cấu hình thông qua `/api/ratio`.",
|
||||
"Allow HTTP image requests": "Cho phép yêu cầu hình ảnh HTTP",
|
||||
@@ -2853,8 +2854,8 @@
|
||||
"Path Regex (one per line)": "Regex đường dẫn (mỗi dòng một mục)",
|
||||
"Path:": "Đường dẫn:",
|
||||
"Pay": "Pay",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Thanh toán theo mức sử dụng với theo dõi mức sử dụng theo thời gian thực",
|
||||
"Pay with Balance": "Thanh toán bằng số dư",
|
||||
"Pay-as-you-go with real-time usage monitoring": "Thanh toán theo mức sử dụng với theo dõi mức sử dụng theo thời gian thực",
|
||||
"Payment": "Thanh toán",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "Chế độ tổng hợp thanh toán — đăng ký bằng công ty đã đăng ký của bạn (pháp nhân offshore). Dành cho doanh nghiệp.",
|
||||
"Payment Channel": "Kênh thanh toán",
|
||||
@@ -3767,10 +3768,10 @@
|
||||
"Subscription First": "Ưu tiên đăng ký",
|
||||
"Subscription Management": "Quản lý đăng ký",
|
||||
"Subscription Only": "Chỉ đăng ký",
|
||||
"Subscription purchased successfully": "Đã mua gói đăng ký thành công",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "Việc tạo và thay đổi gói đăng ký bị khóa cho đến khi quản trị viên xác nhận điều khoản tuân thủ trong cài đặt Cổng thanh toán.",
|
||||
"Subscription Plans": "Gói đăng ký",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "Gói đăng ký KHÔNG dùng Sản phẩm đã liên kết — mỗi gói có một sản phẩm Pancake riêng, được đặt trong quản trị Đăng ký (hoặc tự động tạo bằng nút \"+ Create\" tại đó).",
|
||||
"Subscription purchased successfully": "Đã mua gói đăng ký thành công",
|
||||
"Subtract": "Trừ",
|
||||
"Success": "Thành công",
|
||||
"Success rate": "Tỷ lệ thành công",
|
||||
@@ -3951,6 +3952,7 @@
|
||||
"This model is not available in any group, or no group pricing information is configured.": "Mô hình này không khả dụng trong bất kỳ nhóm nào, hoặc thông tin giá nhóm chưa được cấu hình.",
|
||||
"This month": "Tháng này",
|
||||
"This page has not been created yet.": "Trang này chưa được tạo.",
|
||||
"This plan does not allow balance redemption": "Gói này không cho phép thanh toán bằng số dư",
|
||||
"This project must be used in compliance with the": "Dự án này phải được sử dụng tuân thủ theo",
|
||||
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "Bản ghi này do bản cũ tạo và thiếu thông tin audit. Nâng cấp bản cài để lưu IP máy chủ, IP callback, hình thức thanh toán và phiên bản hệ thống.",
|
||||
"This site currently has {{count}} models enabled": "Trang này hiện đã bật {{count}} mô hình",
|
||||
|
||||
Vendored
+4
-2
@@ -268,6 +268,7 @@
|
||||
"All-time": "全部时间",
|
||||
"Allocated Memory": "已分配内存",
|
||||
"Allow accountFilter parameter": "允许 accountFilter 参数",
|
||||
"Allow balance redemption": "允许余额兑换",
|
||||
"Allow Claude beta query passthrough": "允许 Claude beta 查询透传",
|
||||
"Allow clients to query configured ratios via `/api/ratio`.": "允许客户端通过 `/api/ratio` 查询配置的比例。",
|
||||
"Allow HTTP image requests": "允许 HTTP 图像请求",
|
||||
@@ -2853,8 +2854,8 @@
|
||||
"Path Regex (one per line)": "路径正则(每行一个)",
|
||||
"Path:": "路径:",
|
||||
"Pay": "支付",
|
||||
"Pay-as-you-go with real-time usage monitoring": "按量付费,实时监控使用情况",
|
||||
"Pay with Balance": "使用余额支付",
|
||||
"Pay-as-you-go with real-time usage monitoring": "按量付费,实时监控使用情况",
|
||||
"Payment": "支付",
|
||||
"Payment aggregator mode — onboard with your own registered company (offshore entity). Built for Enterprise.": "支付聚合模式:使用你自己的注册公司(离岸实体)入驻。面向企业场景构建。",
|
||||
"Payment Channel": "支付渠道",
|
||||
@@ -3767,10 +3768,10 @@
|
||||
"Subscription First": "优先订阅",
|
||||
"Subscription Management": "订阅管理",
|
||||
"Subscription Only": "仅用订阅",
|
||||
"Subscription purchased successfully": "订阅购买成功",
|
||||
"Subscription plan creation and changes are locked until the administrator confirms compliance terms in Payment Gateway settings.": "管理员在支付网关设置中确认合规条款之前,订阅套餐的创建和修改会被锁定。",
|
||||
"Subscription Plans": "订阅套餐",
|
||||
"Subscription plans do NOT use the bound Product — each plan has its own dedicated Pancake product, set in the Subscriptions admin (or auto-minted via the \"+ Create\" button there).": "订阅套餐不会使用已绑定的产品。每个套餐都有独立的 Pancake 产品,可在订阅管理中设置,或通过其中的“+ 创建”按钮自动生成。",
|
||||
"Subscription purchased successfully": "订阅购买成功",
|
||||
"Subtract": "减少",
|
||||
"Success": "成功",
|
||||
"Success rate": "成功率",
|
||||
@@ -3951,6 +3952,7 @@
|
||||
"This model is not available in any group, or no group pricing information is configured.": "此模型在任何分组中均不可用,或未配置分组定价信息。",
|
||||
"This month": "本月获得",
|
||||
"This page has not been created yet.": "此页面尚未创建。",
|
||||
"This plan does not allow balance redemption": "该套餐不允许使用余额兑换",
|
||||
"This project must be used in compliance with the": "此项目的使用必须遵守",
|
||||
"This record was written by a pre-upgrade instance and lacks audit info. Upgrade the instance to record server IP, callback IP, payment method and system version.": "该记录由旧版本实例写入,缺少审计信息,建议将实例升级至最新版本以便记录服务器 IP、回调 IP、支付方式与系统版本等审计字段。",
|
||||
"This site currently has {{count}} models enabled": "本站当前已启用模型,总计 {{count}} 个",
|
||||
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"name": "new-api-web-workspace",
|
||||
"private": true,
|
||||
"workspaces": [
|
||||
"default",
|
||||
"classic"
|
||||
],
|
||||
"catalog": {
|
||||
"@lobehub/icons": "^5.10.0",
|
||||
"axios": "^1.16.1",
|
||||
"clsx": "^2.1.1",
|
||||
"dayjs": "^1.11.20",
|
||||
"prettier": "^3.8.3",
|
||||
"qrcode.react": "^4.2.0",
|
||||
"react-icons": "^5.6.0",
|
||||
"react-markdown": "^10.1.0",
|
||||
"remark-gfm": "^4.0.1",
|
||||
"sse.js": "^2.8.0"
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user