feat: sync deployed v1.0.0-rc.38-custom.42

Sync production source including routing, billing, Astra detection and public uptime. Update custom build instructions and license packaging.
This commit is contained in:
cneicy committed 2026-09-26 04:49:11 +08:00
1 parent aa6c3f50f4
commit ceddfd3d6b
828 files changed
+122253 -13702

No files matched your search

@@ -0,0 +1,88 @@
/*
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 { renderHook } from '@testing-library/react'
import { afterEach, expect, test, vi } from 'vitest'
import { useRedirectPanelPlacement } from '../use-redirect-panel-placement'
function mockSheet(rect: { left: number; right: number }, innerWidth: number) {
Object.defineProperty(window, 'innerWidth', {
configurable: true,
writable: true,
value: innerWidth,
})
const anchor = document.createElement('div')
anchor.dataset.slot = 'sheet-content'
document.body.append(anchor)
vi.spyOn(anchor, 'getBoundingClientRect').mockReturnValue({
x: rect.left,
y: 0,
left: rect.left,
top: 0,
right: rect.right,
bottom: 800,
width: rect.right - rect.left,
height: 800,
toJSON: () => ({}),
} as DOMRect)
return { current: anchor }
}
afterEach(() => {
document.body.innerHTML = ''
Object.defineProperty(window, 'innerWidth', {
configurable: true,
writable: true,
value: 1024,
})
})
test('a left-hand sheet starts the window in the free space on its right', () => {
const ref = mockSheet({ left: 0, right: 900 }, 1920)
const { result } = renderHook(() =>
useRedirectPanelPlacement(ref, true, 'left')
)
expect(result.current).toEqual({ x: 916, y: 16, width: 420 })
})
test('a right-hand sheet starts the window on its left, overlapping the sheet when the viewport has no room beside it', () => {
const roomy = mockSheet({ left: 1000, right: 1920 }, 1920)
expect(
renderHook(() => useRedirectPanelPlacement(roomy, true, 'right')).result
.current
).toEqual({ x: 564, y: 16, width: 420 })
const cramped = mockSheet({ left: 100, right: 1296 }, 1296)
expect(
renderHook(() => useRedirectPanelPlacement(cramped, true, 'right')).result
.current
).toEqual({ x: 16, y: 16, width: 420 })
})
test('there is no placement on narrow viewports or while disabled', () => {
const narrow = mockSheet({ left: 0, right: 700 }, 1200)
expect(
renderHook(() => useRedirectPanelPlacement(narrow, true, 'left')).result
.current
).toBe(null)
const disabled = mockSheet({ left: 0, right: 900 }, 1920)
expect(
renderHook(() => useRedirectPanelPlacement(disabled, false, 'left')).result
.current
).toBe(null)
})
@@ -21,7 +21,9 @@ import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { useSecureVerification } from '@/features/auth/secure-verification'
import { handleServerError } from '@/lib/handle-server-error'
import { AuthOperationError } from '@/lib/secure-verification'
import { createServerError } from '@/lib/server-error-message'
import { getChannelKey } from '../api'
@@ -72,13 +74,13 @@ export function useChannelKeyDisclosure(
)
if (operation.current !== current) return
if (!res.success) {
throw new Error(res.message || t('Failed to fetch channel key'))
throw createServerError(res, t('Failed to fetch channel key'))
}
setDisclosedKey({ channelId, key: res.data?.key ?? '' })
toast.success(t('Channel key unlocked'))
} catch (error) {
if (operation.current === current && !current.signal.aborted) {
toast.error(t(AuthOperationError.from(error).message))
handleServerError(AuthOperationError.from(error))
}
} finally {
if (operation.current === current) {
@@ -0,0 +1,84 @@
/*
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 { useCallback, useEffect, useRef, useState } from 'react'
import { createServerError } from '@/lib/server-error-message'
import { fetchModels, fetchUpstreamModels } from '../api'
type DiscoveryState = {
status: 'idle' | 'loading' | 'success' | 'error' | 'stale'
models: string[]
error?: unknown
}
export type ChannelModelDiscoveryRequest =
| { kind: 'saved'; channelId: number }
| { kind: 'preview'; data: Parameters<typeof fetchModels>[0] }
type ChannelModelDiscoveryProps = {
enabled: boolean
request: ChannelModelDiscoveryRequest
}
export function useChannelModelDiscovery(props: ChannelModelDiscoveryProps) {
const [state, setState] = useState<DiscoveryState>({
status: 'idle',
models: [],
})
const sequence = useRef(0)
useEffect(() => {
sequence.current += 1
setState((previous) => {
if (previous.status === 'idle') return previous
if (!props.enabled) return { status: 'idle', models: [] }
return { status: 'stale', models: previous.models }
})
return () => {
sequence.current += 1
}
}, [props.enabled, props.request])
const fetch = useCallback(async () => {
if (!props.enabled) return
const requestSequence = ++sequence.current
setState((previous) => ({ status: 'loading', models: previous.models }))
try {
const response =
props.request.kind === 'saved'
? await fetchUpstreamModels(props.request.channelId)
: await fetchModels(props.request.data)
if (requestSequence !== sequence.current) return
if (!response.success) {
throw createServerError(response, 'Failed to fetch models')
}
setState({ status: 'success', models: response.data ?? [] })
} catch (error) {
if (requestSequence !== sequence.current) return
setState((previous) => ({
status: 'error',
models: previous.models,
error,
}))
}
}, [props.enabled, props.request])
return { ...state, fetch }
}
@@ -25,6 +25,8 @@ import {
ADMIN_PERMISSION_RESOURCES,
hasPermission,
} from '@/lib/admin-permissions'
import { handleServerError } from '@/lib/handle-server-error'
import { createServerError } from '@/lib/server-error-message'
import { useAuthStore } from '@/stores/auth-store'
import { createChannel, updateChannel } from '../api'
@@ -55,31 +57,6 @@ const SENSITIVE_UPDATE_FIELDS = [
'other',
] satisfies (keyof Channel)[]
function isRecord(value: unknown): value is Record<string, unknown> {
return typeof value === 'object' && value !== null
}
function getErrorMessage(error: unknown): string | undefined {
if (error instanceof Error && typeof error.message === 'string') {
return error.message
}
if (!isRecord(error)) return undefined
const response = error.response
if (isRecord(response)) {
const data = response.data
if (isRecord(data)) {
const message = data.message
if (typeof message === 'string') return message
}
}
const message = error.message
if (typeof message === 'string') return message
return undefined
}
export function useChannelMutateForm(props: UseChannelMutateFormParams) {
const { t } = useTranslation()
const currentUser = useAuthStore((s) => s.auth.user)
@@ -115,12 +92,14 @@ export function useChannelMutateForm(props: UseChannelMutateFormParams) {
}
: payload
const response = await updateChannel(
props.currentRow.id,
payloadWithKeyMode
)
const response = await updateChannel(props.currentRow.id, {
...payloadWithKeyMode,
...(canEditSensitive && props.isMultiKeyChannel
? { multi_key_mode: data.multi_key_type }
: {}),
})
if (!response.success) {
throw new Error(response.message || t(ERROR_MESSAGES.UPDATE_FAILED))
throw createServerError(response, t(ERROR_MESSAGES.UPDATE_FAILED))
}
return SUCCESS_MESSAGES.UPDATED
}
@@ -128,7 +107,7 @@ export function useChannelMutateForm(props: UseChannelMutateFormParams) {
const payload = transformFormDataToCreatePayload(data)
const response = await createChannel(payload)
if (!response.success) {
throw new Error(response.message || t(ERROR_MESSAGES.CREATE_FAILED))
throw createServerError(response, t(ERROR_MESSAGES.CREATE_FAILED))
}
return SUCCESS_MESSAGES.CREATED
},
@@ -137,7 +116,7 @@ export function useChannelMutateForm(props: UseChannelMutateFormParams) {
props.onSuccess()
},
onError: (error: unknown) => {
toast.error(getErrorMessage(error) || t(ERROR_MESSAGES.CREATE_FAILED))
handleServerError(error, t(ERROR_MESSAGES.CREATE_FAILED))
},
})
}
@@ -21,6 +21,7 @@ import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { api, type ApiRequestConfig } from '@/lib/api'
import { handleServerError } from '@/lib/handle-server-error'
import { normalizeModelList } from '../lib/upstream-update-utils'
@@ -31,9 +32,9 @@ const upstreamUpdateRequestConfig = {
function getManualIgnoredModelCount(settings: unknown): number {
let parsed: Record<string, unknown> | null = null
if (settings && typeof settings === 'object')
if (settings && typeof settings === 'object') {
parsed = settings as Record<string, unknown>
else if (typeof settings === 'string') {
} else if (typeof settings === 'string') {
try {
parsed = JSON.parse(settings)
} catch {
@@ -126,9 +127,9 @@ export function useChannelUpstreamUpdates(refresh: () => Promise<void>) {
},
upstreamUpdateRequestConfig
)
const { success, message, data } = res.data || {}
const { success, data } = res.data || {}
if (!success) {
toast.error(message || t('Operation failed'))
handleServerError(res.data, t('Operation failed'))
return
}
@@ -146,13 +147,7 @@ export function useChannelUpstreamUpdates(refresh: () => Promise<void>) {
closeModal()
await refresh()
} catch (e: unknown) {
const err = e as {
response?: { data?: { message?: string } }
message?: string
}
toast.error(
err?.response?.data?.message || err?.message || t('Operation failed')
)
handleServerError(e, t('Operation failed'))
} finally {
applyRef.current = false
setApplyLoading(false)
@@ -171,9 +166,9 @@ export function useChannelUpstreamUpdates(refresh: () => Promise<void>) {
{},
upstreamUpdateRequestConfig
)
const { success, message, data } = res.data || {}
const { success, data } = res.data || {}
if (!success) {
toast.error(message || t('Batch processing failed'))
handleServerError(res.data, t('Batch processing failed'))
return
}
@@ -190,15 +185,7 @@ export function useChannelUpstreamUpdates(refresh: () => Promise<void>) {
)
await refresh()
} catch (e: unknown) {
const err = e as {
response?: { data?: { message?: string } }
message?: string
}
toast.error(
err?.response?.data?.message ||
err?.message ||
t('Batch processing failed')
)
handleServerError(e, t('Batch processing failed'))
} finally {
applyAllRef.current = false
setApplyAllLoading(false)
@@ -215,9 +202,9 @@ export function useChannelUpstreamUpdates(refresh: () => Promise<void>) {
{ id: ch.id },
upstreamUpdateRequestConfig
)
const { success, message, data } = res.data || {}
const { success, data } = res.data || {}
if (!success) {
toast.error(message || t('Detection failed'))
handleServerError(res.data, t('Detection failed'))
return
}
@@ -229,13 +216,7 @@ export function useChannelUpstreamUpdates(refresh: () => Promise<void>) {
)
await refresh()
} catch (e: unknown) {
const err = e as {
response?: { data?: { message?: string } }
message?: string
}
toast.error(
err?.response?.data?.message || err?.message || t('Detection failed')
)
handleServerError(e, t('Detection failed'))
} finally {
detectRef.current = false
}
@@ -253,9 +234,9 @@ export function useChannelUpstreamUpdates(refresh: () => Promise<void>) {
{},
upstreamUpdateRequestConfig
)
const { success, message } = res.data || {}
const { success } = res.data || {}
if (!success) {
toast.error(message || t('Batch detection failed'))
handleServerError(res.data, t('Batch detection failed'))
return
}
@@ -266,15 +247,7 @@ export function useChannelUpstreamUpdates(refresh: () => Promise<void>) {
)
await refresh()
} catch (e: unknown) {
const err = e as {
response?: { data?: { message?: string } }
message?: string
}
toast.error(
err?.response?.data?.message ||
err?.message ||
t('Batch detection failed')
)
handleServerError(e, t('Batch detection failed'))
} finally {
detectAllRef.current = false
setDetectAllLoading(false)
@@ -0,0 +1,67 @@
/*
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 RefObject, useLayoutEffect, useState } from 'react'
export type RedirectPanelPlacement = {
x: number
y: number
width: number
}
/** Below this viewport width the floating panel is not offered at all. */
export const REDIRECT_PANEL_MIN_VIEWPORT = 1280
const GAP = 16
const WIDTH = 420
/**
* Picks where the redirect window first appears: beside the channel sheet on
* its free side, or overlapping the sheet's inner edge when the viewport has
* no room there. The window is draggable afterwards, so this is only a start
* position. `null` means the viewport is too narrow for a floating window.
*/
export function useRedirectPanelPlacement(
anchorRef: RefObject<HTMLElement | null>,
enabled: boolean,
sheetSide: 'left' | 'right'
): RedirectPanelPlacement | null {
const [placement, setPlacement] = useState<RedirectPanelPlacement | null>(
null
)
useLayoutEffect(() => {
if (!enabled || window.innerWidth < REDIRECT_PANEL_MIN_VIEWPORT) {
setPlacement(null)
return
}
const anchor =
anchorRef.current?.closest<HTMLElement>('[data-slot="sheet-content"]') ??
anchorRef.current
if (!anchor) {
setPlacement(null)
return
}
const rect = anchor.getBoundingClientRect()
const beside =
sheetSide === 'left' ? rect.right + GAP : rect.left - GAP - WIDTH
const x = Math.min(Math.max(GAP, beside), window.innerWidth - WIDTH - GAP)
setPlacement({ x, y: rect.top + GAP, width: WIDTH })
}, [anchorRef, enabled, sheetSide])
return placement
}