feat: publish v1.0.0-rc.36-custom.1

This commit is contained in:
Codex committed 2026-09-11 05:07:48 +08:00
commit 3a7e8f3b54
2381 files changed
+551494

No files matched your search

+159
View File
@@ -0,0 +1,159 @@
/*
Copyright (C) 2023-2026 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 {
useMutation,
useQuery,
useQueryClient,
type QueryClient,
} from '@tanstack/react-query'
import { isAxiosError } from 'axios'
import { t } from 'i18next'
import type { BillingUsageSchema } from '@/features/pricing/types'
import { api } from '@/lib/api'
import { ROLE } from '@/lib/roles'
import { useAuthStore } from '@/stores/auth-store'
import {
PRICING_KEYS,
pricingValuesByModel,
type PricingOptions,
type PricingValues,
} from './pricing'
export type ModelPricingEntry = {
model_name: string
version: string
configured: PricingValues
effective: PricingValues
usage_schema?: BillingUsageSchema
}
export type ModelPricingConfig = {
entries: ModelPricingEntry[]
options: PricingOptions
empty_version: string
}
export type ModelPricingChange = {
model_name: string
expected_version: string
pricing: PricingValues
reset?: boolean
}
export function useCanEditModelPricing() {
return useAuthStore((state) => state.auth.user?.role === ROLE.SUPER_ADMIN)
}
export async function getModelPricing(
names: string[] = []
): Promise<ModelPricingConfig> {
const params = new URLSearchParams()
for (const name of names) params.append('model', name)
const res = await api.get('/api/option/model_pricing', { params })
if (!res.data.success) {
throw new Error(res.data.message || t('Failed to load model pricing'))
}
return res.data.data
}
export function useModelPricing(names: string[] = [], enabled = true) {
const canEdit = useCanEditModelPricing()
return useQuery({
queryKey: ['model-pricing-config', ...names],
queryFn: () => getModelPricing(names),
enabled: enabled && canEdit,
refetchOnWindowFocus: false,
})
}
export async function invalidateModelPricing(client: QueryClient) {
await Promise.all([
client.invalidateQueries({ queryKey: ['model-pricing-config'] }),
client.invalidateQueries({ queryKey: ['system-options'] }),
client.invalidateQueries({ queryKey: ['pricing'] }),
client.invalidateQueries({ queryKey: ['models'] }),
])
}
export async function saveModelPricing(changes: ModelPricingChange[]) {
if (!changes.length) return
try {
const res = await api.patch('/api/option/model_pricing', { changes })
if (!res.data.success) {
throw new Error(res.data.message || t('Failed to save model pricing'))
}
} catch (error) {
if (
isAxiosError<{ message?: string }>(error) &&
error.response?.data.message
) {
throw new Error(error.response.data.message, { cause: error })
}
throw error
}
}
export function useSaveModelPricing() {
const client = useQueryClient()
return useMutation({
mutationFn: saveModelPricing,
onSuccess: () => invalidateModelPricing(client),
})
}
// Only dirty model fields are applied to stored configuration. Display-only
// built-in expressions for other models never become administrator overrides.
export function buildPricingChanges(
snapshot: ModelPricingConfig,
before: PricingOptions,
after: PricingOptions
): ModelPricingChange[] {
const previous = pricingValuesByModel(before)
const next = pricingValuesByModel(after)
const entries = new Map(
snapshot.entries.map((entry) => [entry.model_name, entry])
)
const changes: ModelPricingChange[] = []
for (const name of new Set([...previous.keys(), ...next.keys()])) {
const oldValues = previous.get(name) ?? {}
const newValues = next.get(name) ?? {}
const dirty = PRICING_KEYS.filter(
(key) => oldValues[key] !== newValues[key]
)
if (!dirty.length) continue
const entry = entries.get(name)
const pricing = { ...entry?.configured }
for (const key of dirty) {
delete pricing[key]
if (newValues[key] !== undefined) pricing[key] = newValues[key]
}
if (newValues['billing_setting.billing_mode'] === 'tiered_expr') {
pricing['billing_setting.billing_mode'] = 'tiered_expr'
pricing['billing_setting.billing_expr'] =
newValues['billing_setting.billing_expr']
}
changes.push({
model_name: name,
expected_version: entry?.version ?? snapshot.empty_version,
pricing,
})
}
return changes
}