mirror of
https://github.com/QuantumNous/new-api.git
synced 2026-09-07 01:56:53 +00:00
Add a Bun script to apply and normalize AGPL copyright headers across the default frontend source files. The script keeps headers idempotent, upgrades existing headers to the 2023-2026 QuantumNous range, and is exposed through `bun run copyright` for future maintenance.
162 lines
4.6 KiB
TypeScript
Vendored
162 lines
4.6 KiB
TypeScript
Vendored
/*
|
|
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 type { Channel } from '../types'
|
|
|
|
export type PullProgress = {
|
|
status?: string
|
|
completed?: number
|
|
total?: number
|
|
// backend may include extra fields
|
|
[k: string]: unknown
|
|
}
|
|
|
|
export type OllamaModel = {
|
|
id: string
|
|
owned_by?: string
|
|
size?: number
|
|
digest?: string
|
|
modified_at?: string
|
|
details?: unknown
|
|
}
|
|
|
|
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
return typeof value === 'object' && value !== null
|
|
}
|
|
|
|
function getString(value: unknown): string | undefined {
|
|
return typeof value === 'string' ? value : undefined
|
|
}
|
|
|
|
function getNumber(value: unknown): number | undefined {
|
|
return typeof value === 'number' ? value : undefined
|
|
}
|
|
|
|
function parseMaybeJSON(value: unknown) {
|
|
if (!value) return null
|
|
if (typeof value === 'object') return value
|
|
if (typeof value === 'string') {
|
|
try {
|
|
return JSON.parse(value)
|
|
} catch {
|
|
return null
|
|
}
|
|
}
|
|
return null
|
|
}
|
|
|
|
/**
|
|
* Resolve Ollama base URL from channel fields (supports legacy/alternate fields).
|
|
*/
|
|
export function resolveOllamaBaseUrl(channel: Channel | null) {
|
|
if (!channel) return ''
|
|
|
|
const direct =
|
|
typeof channel.base_url === 'string' ? channel.base_url.trim() : ''
|
|
if (direct) return direct
|
|
|
|
const alt =
|
|
typeof (channel as unknown as { ollama_base_url?: unknown })
|
|
?.ollama_base_url === 'string'
|
|
? String(
|
|
(channel as unknown as { ollama_base_url?: string }).ollama_base_url
|
|
).trim()
|
|
: ''
|
|
if (alt) return alt
|
|
|
|
const parsed = parseMaybeJSON(channel.other_info)
|
|
if (isRecord(parsed)) {
|
|
const baseUrl = getString(parsed.base_url)?.trim()
|
|
if (baseUrl) return baseUrl
|
|
const publicUrl = getString(parsed.public_url)?.trim()
|
|
if (publicUrl) return publicUrl
|
|
const apiUrl = getString(parsed.api_url)?.trim()
|
|
if (apiUrl) return apiUrl
|
|
}
|
|
|
|
return ''
|
|
}
|
|
|
|
export function normalizeOllamaModels(items: unknown): OllamaModel[] {
|
|
if (!Array.isArray(items)) return []
|
|
|
|
return items
|
|
.map((item) => {
|
|
if (!item) return null
|
|
|
|
if (typeof item === 'string') {
|
|
return { id: item, owned_by: 'ollama' } satisfies OllamaModel
|
|
}
|
|
|
|
if (isRecord(item)) {
|
|
const candidateId =
|
|
getString(item.id) ||
|
|
getString(item.ID) ||
|
|
getString(item.name) ||
|
|
getString(item.model) ||
|
|
getString(item.Model)
|
|
if (!candidateId) return null
|
|
|
|
const metadata = item.metadata ?? item.Metadata
|
|
const normalized: OllamaModel = {
|
|
...item,
|
|
id: candidateId,
|
|
owned_by:
|
|
getString(item.owned_by) || getString(item.ownedBy) || 'ollama',
|
|
}
|
|
|
|
const itemSize = getNumber(item.size)
|
|
if (typeof itemSize === 'number' && !normalized.size) {
|
|
normalized.size = itemSize
|
|
}
|
|
if (isRecord(metadata)) {
|
|
const metaSize = getNumber(metadata.size)
|
|
if (typeof metaSize === 'number' && !normalized.size) {
|
|
normalized.size = metaSize
|
|
}
|
|
const metaDigest = getString(metadata.digest)
|
|
if (!normalized.digest && metaDigest) {
|
|
normalized.digest = metaDigest
|
|
}
|
|
const metaModifiedAt = getString(metadata.modified_at)
|
|
if (!normalized.modified_at && metaModifiedAt) {
|
|
normalized.modified_at = metaModifiedAt
|
|
}
|
|
if (metadata.details && !normalized.details) {
|
|
normalized.details = metadata.details
|
|
}
|
|
}
|
|
return normalized
|
|
}
|
|
|
|
return null
|
|
})
|
|
.filter(Boolean) as OllamaModel[]
|
|
}
|
|
|
|
export function formatBytes(bytes?: number) {
|
|
if (typeof bytes !== 'number' || Number.isNaN(bytes)) return '-'
|
|
if (bytes < 1024) return `${bytes} B`
|
|
const kb = bytes / 1024
|
|
if (kb < 1024) return `${kb.toFixed(1)} KB`
|
|
const mb = kb / 1024
|
|
if (mb < 1024) return `${mb.toFixed(1)} MB`
|
|
const gb = mb / 1024
|
|
return `${gb.toFixed(2)} GB`
|
|
}
|