feat: publish v1.0.0-rc.36-custom.1
This commit is contained in:
commit
3a7e8f3b54
2381 files changed
+551494
No files matched your search
@@ -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
|
||||
}
|
||||
Reference in new issue
Block a user