🛠️ fix: v1 interface feedback regressions

Resolve verified V1 frontend feedback by improving channel workflows, auth behavior, API key interactions, user filtering, layout persistence, subscription quota handling, i18n text, pricing metadata, and stale frontend cache recovery.

- Add a global frontend cache version cleanup to prevent old frontend localStorage from causing page errors after upgrades.
- Fix channel copy refresh, model mapping input focus loss, create-channel fetch-model title state, upstream model update confirmation, and batch test toast behavior.
- Respect password login settings and improve Turnstile, forgot-password, registration, and invite-link flows.
- Make user role/status filtering server-side and preserve table page size in URLs.
- Improve API key edit validation feedback and prefetch real keys for reliable copy actions.
- Fix rankings access fail-open behavior, double scrollbars, subscription received amount conversion/display, token i18n wording, model deletion confirmation grammar, and Claude pricing context inference.
- Add clearer Playground model/group loading errors.

Validation:
- bun run typecheck
- bun run i18n:sync
- gofmt on modified Go files
- go test ./controller ./model -run '^$'
This commit is contained in:
t0ng7u
2026-05-25 02:42:22 +08:00
parent 88437a1869
commit b302be30e3
53 changed files with 599 additions and 198 deletions
@@ -94,13 +94,33 @@ export function DataTableRowActions<TData>({
triggerRefresh,
setResolvedKey,
resolveRealKey,
resolvedKeys,
loadingKeys,
} = useApiKeys()
const isEnabled = apiKey.status === API_KEY_STATUS.ENABLED
const { chatPresets, serverAddress } = useChatPresets()
const [isTogglingStatus, setIsTogglingStatus] = useState(false)
const resolvedRealKey = resolvedKeys[apiKey.id]
const isRealKeyLoading = Boolean(loadingKeys[apiKey.id])
const hasChatPresets = chatPresets.length > 0
const handleMenuOpenChange = useCallback(
(open: boolean) => {
if (open && !resolvedRealKey && !isRealKeyLoading) {
void resolveRealKey(apiKey.id)
}
},
[apiKey.id, isRealKeyLoading, resolvedRealKey, resolveRealKey]
)
const getCachedRealKey = useCallback(() => {
if (resolvedRealKey) return resolvedRealKey
void resolveRealKey(apiKey.id)
toast.info(t('API key is loading, please try again in a moment'))
return null
}, [apiKey.id, resolvedRealKey, resolveRealKey, t])
const handleOpenChatPreset = useCallback(
async (preset: ChatPreset) => {
const realKey = await resolveRealKey(apiKey.id)
@@ -201,7 +221,7 @@ export function DataTableRowActions<TData>({
</TooltipContent>
</Tooltip>
<DropdownMenu modal={false}>
<DropdownMenu modal={false} onOpenChange={handleMenuOpenChange}>
<DropdownMenuTrigger
render={
<Button
@@ -216,7 +236,7 @@ export function DataTableRowActions<TData>({
<DropdownMenuContent align='end' className='w-[200px]'>
<DropdownMenuItem
onClick={async () => {
const realKey = await resolveRealKey(apiKey.id)
const realKey = getCachedRealKey()
if (!realKey) return
const ok = await copyToClipboard(realKey)
if (ok) toast.success(t('Copied'))
@@ -229,7 +249,7 @@ export function DataTableRowActions<TData>({
</DropdownMenuItem>
<DropdownMenuItem
onClick={async () => {
const realKey = await resolveRealKey(apiKey.id)
const realKey = getCachedRealKey()
if (!realKey) return
const connStr = encodeConnectionString(
realKey,