style(frontend): format code with prettier
格式化前端业务代码,符合代码规范 - 统一代码风格 - 修复 ESLint 警告
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
* Handles AI platform account management for administrators
|
* Handles AI platform account management for administrators
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { apiClient } from '../client';
|
import { apiClient } from '../client'
|
||||||
import type {
|
import type {
|
||||||
Account,
|
Account,
|
||||||
CreateAccountRequest,
|
CreateAccountRequest,
|
||||||
@@ -13,7 +13,7 @@ import type {
|
|||||||
WindowStats,
|
WindowStats,
|
||||||
ClaudeModel,
|
ClaudeModel,
|
||||||
AccountUsageStatsResponse,
|
AccountUsageStatsResponse,
|
||||||
} from '@/types';
|
} from '@/types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* List all accounts with pagination
|
* List all accounts with pagination
|
||||||
@@ -26,10 +26,10 @@ export async function list(
|
|||||||
page: number = 1,
|
page: number = 1,
|
||||||
pageSize: number = 20,
|
pageSize: number = 20,
|
||||||
filters?: {
|
filters?: {
|
||||||
platform?: string;
|
platform?: string
|
||||||
type?: string;
|
type?: string
|
||||||
status?: string;
|
status?: string
|
||||||
search?: string;
|
search?: string
|
||||||
}
|
}
|
||||||
): Promise<PaginatedResponse<Account>> {
|
): Promise<PaginatedResponse<Account>> {
|
||||||
const { data } = await apiClient.get<PaginatedResponse<Account>>('/admin/accounts', {
|
const { data } = await apiClient.get<PaginatedResponse<Account>>('/admin/accounts', {
|
||||||
@@ -38,8 +38,8 @@ export async function list(
|
|||||||
page_size: pageSize,
|
page_size: pageSize,
|
||||||
...filters,
|
...filters,
|
||||||
},
|
},
|
||||||
});
|
})
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -48,8 +48,8 @@ export async function list(
|
|||||||
* @returns Account details
|
* @returns Account details
|
||||||
*/
|
*/
|
||||||
export async function getById(id: number): Promise<Account> {
|
export async function getById(id: number): Promise<Account> {
|
||||||
const { data } = await apiClient.get<Account>(`/admin/accounts/${id}`);
|
const { data } = await apiClient.get<Account>(`/admin/accounts/${id}`)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -58,8 +58,8 @@ export async function getById(id: number): Promise<Account> {
|
|||||||
* @returns Created account
|
* @returns Created account
|
||||||
*/
|
*/
|
||||||
export async function create(accountData: CreateAccountRequest): Promise<Account> {
|
export async function create(accountData: CreateAccountRequest): Promise<Account> {
|
||||||
const { data } = await apiClient.post<Account>('/admin/accounts', accountData);
|
const { data } = await apiClient.post<Account>('/admin/accounts', accountData)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -69,8 +69,8 @@ export async function create(accountData: CreateAccountRequest): Promise<Account
|
|||||||
* @returns Updated account
|
* @returns Updated account
|
||||||
*/
|
*/
|
||||||
export async function update(id: number, updates: UpdateAccountRequest): Promise<Account> {
|
export async function update(id: number, updates: UpdateAccountRequest): Promise<Account> {
|
||||||
const { data } = await apiClient.put<Account>(`/admin/accounts/${id}`, updates);
|
const { data } = await apiClient.put<Account>(`/admin/accounts/${id}`, updates)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -79,8 +79,8 @@ export async function update(id: number, updates: UpdateAccountRequest): Promise
|
|||||||
* @returns Success confirmation
|
* @returns Success confirmation
|
||||||
*/
|
*/
|
||||||
export async function deleteAccount(id: number): Promise<{ message: string }> {
|
export async function deleteAccount(id: number): Promise<{ message: string }> {
|
||||||
const { data } = await apiClient.delete<{ message: string }>(`/admin/accounts/${id}`);
|
const { data } = await apiClient.delete<{ message: string }>(`/admin/accounts/${id}`)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -89,11 +89,8 @@ export async function deleteAccount(id: number): Promise<{ message: string }> {
|
|||||||
* @param status - New status
|
* @param status - New status
|
||||||
* @returns Updated account
|
* @returns Updated account
|
||||||
*/
|
*/
|
||||||
export async function toggleStatus(
|
export async function toggleStatus(id: number, status: 'active' | 'inactive'): Promise<Account> {
|
||||||
id: number,
|
return update(id, { status })
|
||||||
status: 'active' | 'inactive'
|
|
||||||
): Promise<Account> {
|
|
||||||
return update(id, { status });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,16 +99,16 @@ export async function toggleStatus(
|
|||||||
* @returns Test result
|
* @returns Test result
|
||||||
*/
|
*/
|
||||||
export async function testAccount(id: number): Promise<{
|
export async function testAccount(id: number): Promise<{
|
||||||
success: boolean;
|
success: boolean
|
||||||
message: string;
|
message: string
|
||||||
latency_ms?: number;
|
latency_ms?: number
|
||||||
}> {
|
}> {
|
||||||
const { data } = await apiClient.post<{
|
const { data } = await apiClient.post<{
|
||||||
success: boolean;
|
success: boolean
|
||||||
message: string;
|
message: string
|
||||||
latency_ms?: number;
|
latency_ms?: number
|
||||||
}>(`/admin/accounts/${id}/test`);
|
}>(`/admin/accounts/${id}/test`)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -120,8 +117,8 @@ export async function testAccount(id: number): Promise<{
|
|||||||
* @returns Updated account
|
* @returns Updated account
|
||||||
*/
|
*/
|
||||||
export async function refreshCredentials(id: number): Promise<Account> {
|
export async function refreshCredentials(id: number): Promise<Account> {
|
||||||
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/refresh`);
|
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/refresh`)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -133,8 +130,8 @@ export async function refreshCredentials(id: number): Promise<Account> {
|
|||||||
export async function getStats(id: number, days: number = 30): Promise<AccountUsageStatsResponse> {
|
export async function getStats(id: number, days: number = 30): Promise<AccountUsageStatsResponse> {
|
||||||
const { data } = await apiClient.get<AccountUsageStatsResponse>(`/admin/accounts/${id}/stats`, {
|
const { data } = await apiClient.get<AccountUsageStatsResponse>(`/admin/accounts/${id}/stats`, {
|
||||||
params: { days },
|
params: { days },
|
||||||
});
|
})
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -143,8 +140,8 @@ export async function getStats(id: number, days: number = 30): Promise<AccountUs
|
|||||||
* @returns Updated account
|
* @returns Updated account
|
||||||
*/
|
*/
|
||||||
export async function clearError(id: number): Promise<Account> {
|
export async function clearError(id: number): Promise<Account> {
|
||||||
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/clear-error`);
|
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/clear-error`)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -153,8 +150,8 @@ export async function clearError(id: number): Promise<Account> {
|
|||||||
* @returns Account usage info
|
* @returns Account usage info
|
||||||
*/
|
*/
|
||||||
export async function getUsage(id: number): Promise<AccountUsageInfo> {
|
export async function getUsage(id: number): Promise<AccountUsageInfo> {
|
||||||
const { data } = await apiClient.get<AccountUsageInfo>(`/admin/accounts/${id}/usage`);
|
const { data } = await apiClient.get<AccountUsageInfo>(`/admin/accounts/${id}/usage`)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -163,8 +160,10 @@ export async function getUsage(id: number): Promise<AccountUsageInfo> {
|
|||||||
* @returns Success confirmation
|
* @returns Success confirmation
|
||||||
*/
|
*/
|
||||||
export async function clearRateLimit(id: number): Promise<{ message: string }> {
|
export async function clearRateLimit(id: number): Promise<{ message: string }> {
|
||||||
const { data } = await apiClient.post<{ message: string }>(`/admin/accounts/${id}/clear-rate-limit`);
|
const { data } = await apiClient.post<{ message: string }>(
|
||||||
return data;
|
`/admin/accounts/${id}/clear-rate-limit`
|
||||||
|
)
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -177,8 +176,8 @@ export async function generateAuthUrl(
|
|||||||
endpoint: string,
|
endpoint: string,
|
||||||
config: { proxy_id?: number }
|
config: { proxy_id?: number }
|
||||||
): Promise<{ auth_url: string; session_id: string }> {
|
): Promise<{ auth_url: string; session_id: string }> {
|
||||||
const { data } = await apiClient.post<{ auth_url: string; session_id: string }>(endpoint, config);
|
const { data } = await apiClient.post<{ auth_url: string; session_id: string }>(endpoint, config)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -191,8 +190,8 @@ export async function exchangeCode(
|
|||||||
endpoint: string,
|
endpoint: string,
|
||||||
exchangeData: { session_id: string; code: string; proxy_id?: number }
|
exchangeData: { session_id: string; code: string; proxy_id?: number }
|
||||||
): Promise<Record<string, unknown>> {
|
): Promise<Record<string, unknown>> {
|
||||||
const { data } = await apiClient.post<Record<string, unknown>>(endpoint, exchangeData);
|
const { data } = await apiClient.post<Record<string, unknown>>(endpoint, exchangeData)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -201,16 +200,16 @@ export async function exchangeCode(
|
|||||||
* @returns Results of batch creation
|
* @returns Results of batch creation
|
||||||
*/
|
*/
|
||||||
export async function batchCreate(accounts: CreateAccountRequest[]): Promise<{
|
export async function batchCreate(accounts: CreateAccountRequest[]): Promise<{
|
||||||
success: number;
|
success: number
|
||||||
failed: number;
|
failed: number
|
||||||
results: Array<{ success: boolean; account?: Account; error?: string }>;
|
results: Array<{ success: boolean; account?: Account; error?: string }>
|
||||||
}> {
|
}> {
|
||||||
const { data } = await apiClient.post<{
|
const { data } = await apiClient.post<{
|
||||||
success: number;
|
success: number
|
||||||
failed: number;
|
failed: number
|
||||||
results: Array<{ success: boolean; account?: Account; error?: string }>;
|
results: Array<{ success: boolean; account?: Account; error?: string }>
|
||||||
}>('/admin/accounts/batch', { accounts });
|
}>('/admin/accounts/batch', { accounts })
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -219,20 +218,20 @@ export async function batchCreate(accounts: CreateAccountRequest[]): Promise<{
|
|||||||
* @returns Results of batch update
|
* @returns Results of batch update
|
||||||
*/
|
*/
|
||||||
export async function batchUpdateCredentials(request: {
|
export async function batchUpdateCredentials(request: {
|
||||||
account_ids: number[];
|
account_ids: number[]
|
||||||
field: string;
|
field: string
|
||||||
value: any;
|
value: any
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
success: number;
|
success: number
|
||||||
failed: number;
|
failed: number
|
||||||
results: Array<{ account_id: number; success: boolean; error?: string }>;
|
results: Array<{ account_id: number; success: boolean; error?: string }>
|
||||||
}> {
|
}> {
|
||||||
const { data} = await apiClient.post<{
|
const { data } = await apiClient.post<{
|
||||||
success: number;
|
success: number
|
||||||
failed: number;
|
failed: number
|
||||||
results: Array<{ account_id: number; success: boolean; error?: string }>;
|
results: Array<{ account_id: number; success: boolean; error?: string }>
|
||||||
}>('/admin/accounts/batch-update-credentials', request);
|
}>('/admin/accounts/batch-update-credentials', request)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -245,19 +244,19 @@ export async function bulkUpdate(
|
|||||||
accountIds: number[],
|
accountIds: number[],
|
||||||
updates: Record<string, unknown>
|
updates: Record<string, unknown>
|
||||||
): Promise<{
|
): Promise<{
|
||||||
success: number;
|
success: number
|
||||||
failed: number;
|
failed: number
|
||||||
results: Array<{ account_id: number; success: boolean; error?: string }>;
|
results: Array<{ account_id: number; success: boolean; error?: string }>
|
||||||
}> {
|
}> {
|
||||||
const { data } = await apiClient.post<{
|
const { data } = await apiClient.post<{
|
||||||
success: number;
|
success: number
|
||||||
failed: number;
|
failed: number
|
||||||
results: Array<{ account_id: number; success: boolean; error?: string }>;
|
results: Array<{ account_id: number; success: boolean; error?: string }>
|
||||||
}>('/admin/accounts/bulk-update', {
|
}>('/admin/accounts/bulk-update', {
|
||||||
account_ids: accountIds,
|
account_ids: accountIds,
|
||||||
...updates
|
...updates,
|
||||||
});
|
})
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -266,8 +265,8 @@ export async function bulkUpdate(
|
|||||||
* @returns Today's stats (requests, tokens, cost)
|
* @returns Today's stats (requests, tokens, cost)
|
||||||
*/
|
*/
|
||||||
export async function getTodayStats(id: number): Promise<WindowStats> {
|
export async function getTodayStats(id: number): Promise<WindowStats> {
|
||||||
const { data } = await apiClient.get<WindowStats>(`/admin/accounts/${id}/today-stats`);
|
const { data } = await apiClient.get<WindowStats>(`/admin/accounts/${id}/today-stats`)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -277,8 +276,10 @@ export async function getTodayStats(id: number): Promise<WindowStats> {
|
|||||||
* @returns Updated account
|
* @returns Updated account
|
||||||
*/
|
*/
|
||||||
export async function setSchedulable(id: number, schedulable: boolean): Promise<Account> {
|
export async function setSchedulable(id: number, schedulable: boolean): Promise<Account> {
|
||||||
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/schedulable`, { schedulable });
|
const { data } = await apiClient.post<Account>(`/admin/accounts/${id}/schedulable`, {
|
||||||
return data;
|
schedulable,
|
||||||
|
})
|
||||||
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -287,30 +288,30 @@ export async function setSchedulable(id: number, schedulable: boolean): Promise<
|
|||||||
* @returns List of available models for this account
|
* @returns List of available models for this account
|
||||||
*/
|
*/
|
||||||
export async function getAvailableModels(id: number): Promise<ClaudeModel[]> {
|
export async function getAvailableModels(id: number): Promise<ClaudeModel[]> {
|
||||||
const { data } = await apiClient.get<ClaudeModel[]>(`/admin/accounts/${id}/models`);
|
const { data } = await apiClient.get<ClaudeModel[]>(`/admin/accounts/${id}/models`)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function syncFromCrs(params: {
|
export async function syncFromCrs(params: {
|
||||||
base_url: string;
|
base_url: string
|
||||||
username: string;
|
username: string
|
||||||
password: string;
|
password: string
|
||||||
sync_proxies?: boolean;
|
sync_proxies?: boolean
|
||||||
}): Promise<{
|
}): Promise<{
|
||||||
created: number;
|
created: number
|
||||||
updated: number;
|
updated: number
|
||||||
skipped: number;
|
skipped: number
|
||||||
failed: number;
|
failed: number
|
||||||
items: Array<{
|
items: Array<{
|
||||||
crs_account_id: string;
|
crs_account_id: string
|
||||||
kind: string;
|
kind: string
|
||||||
name: string;
|
name: string
|
||||||
action: string;
|
action: string
|
||||||
error?: string;
|
error?: string
|
||||||
}>;
|
}>
|
||||||
}> {
|
}> {
|
||||||
const { data } = await apiClient.post('/admin/accounts/sync/crs', params);
|
const { data } = await apiClient.post('/admin/accounts/sync/crs', params)
|
||||||
return data;
|
return data
|
||||||
}
|
}
|
||||||
|
|
||||||
export const accountsAPI = {
|
export const accountsAPI = {
|
||||||
@@ -335,6 +336,6 @@ export const accountsAPI = {
|
|||||||
batchUpdateCredentials,
|
batchUpdateCredentials,
|
||||||
bulkUpdate,
|
bulkUpdate,
|
||||||
syncFromCrs,
|
syncFromCrs,
|
||||||
};
|
}
|
||||||
|
|
||||||
export default accountsAPI;
|
export default accountsAPI
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
<template>
|
<template>
|
||||||
<Modal
|
<Modal :show="show" :title="t('admin.accounts.bulkEdit.title')" size="lg" @close="handleClose">
|
||||||
:show="show"
|
<form class="space-y-5" @submit.prevent="handleSubmit">
|
||||||
:title="t('admin.accounts.bulkEdit.title')"
|
|
||||||
size="lg"
|
|
||||||
@close="handleClose"
|
|
||||||
>
|
|
||||||
<form @submit.prevent="handleSubmit" class="space-y-5">
|
|
||||||
<!-- Info -->
|
<!-- Info -->
|
||||||
<div class="rounded-lg bg-blue-50 dark:bg-blue-900/20 p-4">
|
<div class="rounded-lg bg-blue-50 dark:bg-blue-900/20 p-4">
|
||||||
<p class="text-sm text-blue-700 dark:text-blue-400">
|
<p class="text-sm text-blue-700 dark:text-blue-400">
|
||||||
<svg class="w-5 h-5 inline mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="w-5 h-5 inline mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{{ t('admin.accounts.bulkEdit.selectionInfo', { count: accountIds.length }) }}
|
{{ t('admin.accounts.bulkEdit.selectionInfo', { count: accountIds.length }) }}
|
||||||
</p>
|
</p>
|
||||||
@@ -21,8 +21,8 @@
|
|||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<label class="input-label mb-0">{{ t('admin.accounts.baseUrl') }}</label>
|
<label class="input-label mb-0">{{ t('admin.accounts.baseUrl') }}</label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="enableBaseUrl"
|
v-model="enableBaseUrl"
|
||||||
|
type="checkbox"
|
||||||
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -34,7 +34,9 @@
|
|||||||
:class="!enableBaseUrl && 'opacity-50 cursor-not-allowed'"
|
:class="!enableBaseUrl && 'opacity-50 cursor-not-allowed'"
|
||||||
:placeholder="t('admin.accounts.bulkEdit.baseUrlPlaceholder')"
|
:placeholder="t('admin.accounts.bulkEdit.baseUrlPlaceholder')"
|
||||||
/>
|
/>
|
||||||
<p class="input-hint">{{ t('admin.accounts.bulkEdit.baseUrlNotice') }}</p>
|
<p class="input-hint">
|
||||||
|
{{ t('admin.accounts.bulkEdit.baseUrlNotice') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Model restriction -->
|
<!-- Model restriction -->
|
||||||
@@ -42,8 +44,8 @@
|
|||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<label class="input-label mb-0">{{ t('admin.accounts.modelRestriction') }}</label>
|
<label class="input-label mb-0">{{ t('admin.accounts.modelRestriction') }}</label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="enableModelRestriction"
|
v-model="enableModelRestriction"
|
||||||
|
type="checkbox"
|
||||||
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -53,31 +55,51 @@
|
|||||||
<div class="flex gap-2 mb-4">
|
<div class="flex gap-2 mb-4">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="modelRestrictionMode = 'whitelist'"
|
|
||||||
:class="[
|
:class="[
|
||||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
||||||
modelRestrictionMode === 'whitelist'
|
modelRestrictionMode === 'whitelist'
|
||||||
? 'bg-primary-100 text-primary-700 dark:bg-primary-900/30 dark:text-primary-400'
|
? 'bg-primary-100 text-primary-700 dark:bg-primary-900/30 dark:text-primary-400'
|
||||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500',
|
||||||
]"
|
]"
|
||||||
|
@click="modelRestrictionMode = 'whitelist'"
|
||||||
>
|
>
|
||||||
<svg class="w-4 h-4 inline mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
|
class="w-4 h-4 inline mr-1.5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{{ t('admin.accounts.modelWhitelist') }}
|
{{ t('admin.accounts.modelWhitelist') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="modelRestrictionMode = 'mapping'"
|
|
||||||
:class="[
|
:class="[
|
||||||
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
'flex-1 rounded-lg px-4 py-2 text-sm font-medium transition-all',
|
||||||
modelRestrictionMode === 'mapping'
|
modelRestrictionMode === 'mapping'
|
||||||
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
|
? 'bg-purple-100 text-purple-700 dark:bg-purple-900/30 dark:text-purple-400'
|
||||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500',
|
||||||
]"
|
]"
|
||||||
|
@click="modelRestrictionMode = 'mapping'"
|
||||||
>
|
>
|
||||||
<svg class="w-4 h-4 inline mr-1.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4" />
|
class="w-4 h-4 inline mr-1.5"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M8 7h12m0 0l-4-4m4 4l-4 4m0 6H4m0 0l4 4m-4-4l4-4"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{{ t('admin.accounts.modelMapping') }}
|
{{ t('admin.accounts.modelMapping') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -87,8 +109,18 @@
|
|||||||
<div v-if="modelRestrictionMode === 'whitelist'">
|
<div v-if="modelRestrictionMode === 'whitelist'">
|
||||||
<div class="mb-3 rounded-lg bg-blue-50 dark:bg-blue-900/20 p-3">
|
<div class="mb-3 rounded-lg bg-blue-50 dark:bg-blue-900/20 p-3">
|
||||||
<p class="text-xs text-blue-700 dark:text-blue-400">
|
<p class="text-xs text-blue-700 dark:text-blue-400">
|
||||||
<svg class="w-4 h-4 inline mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
class="w-4 h-4 inline mr-1"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{{ t('admin.accounts.selectAllowedModels') }}
|
{{ t('admin.accounts.selectAllowedModels') }}
|
||||||
</p>
|
</p>
|
||||||
@@ -100,12 +132,16 @@
|
|||||||
v-for="model in allModels"
|
v-for="model in allModels"
|
||||||
:key="model.value"
|
:key="model.value"
|
||||||
class="flex cursor-pointer items-center rounded-lg border p-3 transition-all hover:bg-gray-50 dark:border-dark-600 dark:hover:bg-dark-700"
|
class="flex cursor-pointer items-center rounded-lg border p-3 transition-all hover:bg-gray-50 dark:border-dark-600 dark:hover:bg-dark-700"
|
||||||
:class="allowedModels.includes(model.value) ? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20' : 'border-gray-200'"
|
:class="
|
||||||
|
allowedModels.includes(model.value)
|
||||||
|
? 'border-primary-500 bg-primary-50 dark:bg-primary-900/20'
|
||||||
|
: 'border-gray-200'
|
||||||
|
"
|
||||||
>
|
>
|
||||||
<input
|
<input
|
||||||
|
v-model="allowedModels"
|
||||||
type="checkbox"
|
type="checkbox"
|
||||||
:value="model.value"
|
:value="model.value"
|
||||||
v-model="allowedModels"
|
|
||||||
class="mr-2 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
class="mr-2 rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||||
/>
|
/>
|
||||||
<span class="text-sm text-gray-700 dark:text-gray-300">{{ model.label }}</span>
|
<span class="text-sm text-gray-700 dark:text-gray-300">{{ model.label }}</span>
|
||||||
@@ -114,7 +150,9 @@
|
|||||||
|
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400">
|
<p class="text-xs text-gray-500 dark:text-gray-400">
|
||||||
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
{{ t('admin.accounts.selectedModels', { count: allowedModels.length }) }}
|
||||||
<span v-if="allowedModels.length === 0">{{ t('admin.accounts.supportsAllModels') }}</span>
|
<span v-if="allowedModels.length === 0">{{
|
||||||
|
t('admin.accounts.supportsAllModels')
|
||||||
|
}}</span>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -122,8 +160,18 @@
|
|||||||
<div v-else>
|
<div v-else>
|
||||||
<div class="mb-3 rounded-lg bg-purple-50 dark:bg-purple-900/20 p-3">
|
<div class="mb-3 rounded-lg bg-purple-50 dark:bg-purple-900/20 p-3">
|
||||||
<p class="text-xs text-purple-700 dark:text-purple-400">
|
<p class="text-xs text-purple-700 dark:text-purple-400">
|
||||||
<svg class="w-4 h-4 inline mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" />
|
class="w-4 h-4 inline mr-1"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{{ t('admin.accounts.mapRequestModels') }}
|
{{ t('admin.accounts.mapRequestModels') }}
|
||||||
</p>
|
</p>
|
||||||
@@ -142,8 +190,18 @@
|
|||||||
class="input flex-1"
|
class="input flex-1"
|
||||||
:placeholder="t('admin.accounts.requestModel')"
|
:placeholder="t('admin.accounts.requestModel')"
|
||||||
/>
|
/>
|
||||||
<svg class="w-4 h-4 text-gray-400 flex-shrink-0" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3" />
|
class="w-4 h-4 text-gray-400 flex-shrink-0"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M14 5l7 7m0 0l-7 7m7-7H3"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
<input
|
<input
|
||||||
v-model="mapping.to"
|
v-model="mapping.to"
|
||||||
@@ -153,11 +211,16 @@
|
|||||||
/>
|
/>
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="removeModelMapping(index)"
|
|
||||||
class="p-2 text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg transition-colors"
|
class="p-2 text-red-500 hover:text-red-600 hover:bg-red-50 dark:hover:bg-red-900/20 rounded-lg transition-colors"
|
||||||
|
@click="removeModelMapping(index)"
|
||||||
>
|
>
|
||||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16" />
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M19 7l-.867 12.142A2 2 0 0116.138 21H7.862a2 2 0 01-1.995-1.858L5 7m5 4v6m4-6v6m1-10V4a1 1 0 00-1-1h-4a1 1 0 00-1 1v3M4 7h16"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -165,11 +228,21 @@
|
|||||||
|
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="addModelMapping"
|
|
||||||
class="w-full rounded-lg border-2 border-dashed border-gray-300 dark:border-dark-500 px-4 py-2 text-gray-600 dark:text-gray-400 transition-colors hover:border-gray-400 hover:text-gray-700 dark:hover:border-dark-400 dark:hover:text-gray-300 mb-3"
|
class="w-full rounded-lg border-2 border-dashed border-gray-300 dark:border-dark-500 px-4 py-2 text-gray-600 dark:text-gray-400 transition-colors hover:border-gray-400 hover:text-gray-700 dark:hover:border-dark-400 dark:hover:text-gray-300 mb-3"
|
||||||
|
@click="addModelMapping"
|
||||||
>
|
>
|
||||||
<svg class="w-4 h-4 inline mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
class="w-4 h-4 inline mr-1"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 4v16m8-8H4"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{{ t('admin.accounts.addMapping') }}
|
{{ t('admin.accounts.addMapping') }}
|
||||||
</button>
|
</button>
|
||||||
@@ -180,11 +253,8 @@
|
|||||||
v-for="preset in presetMappings"
|
v-for="preset in presetMappings"
|
||||||
:key="preset.label"
|
:key="preset.label"
|
||||||
type="button"
|
type="button"
|
||||||
|
:class="['rounded-lg px-3 py-1 text-xs transition-colors', preset.color]"
|
||||||
@click="addPresetMapping(preset.from, preset.to)"
|
@click="addPresetMapping(preset.from, preset.to)"
|
||||||
:class="[
|
|
||||||
'rounded-lg px-3 py-1 text-xs transition-colors',
|
|
||||||
preset.color
|
|
||||||
]"
|
|
||||||
>
|
>
|
||||||
+ {{ preset.label }}
|
+ {{ preset.label }}
|
||||||
</button>
|
</button>
|
||||||
@@ -198,11 +268,13 @@
|
|||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<div>
|
<div>
|
||||||
<label class="input-label mb-0">{{ t('admin.accounts.customErrorCodes') }}</label>
|
<label class="input-label mb-0">{{ t('admin.accounts.customErrorCodes') }}</label>
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ t('admin.accounts.customErrorCodesHint') }}</p>
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||||
|
{{ t('admin.accounts.customErrorCodesHint') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="enableCustomErrorCodes"
|
v-model="enableCustomErrorCodes"
|
||||||
|
type="checkbox"
|
||||||
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -210,8 +282,18 @@
|
|||||||
<div v-if="enableCustomErrorCodes" class="space-y-3">
|
<div v-if="enableCustomErrorCodes" class="space-y-3">
|
||||||
<div class="rounded-lg bg-amber-50 dark:bg-amber-900/20 p-3">
|
<div class="rounded-lg bg-amber-50 dark:bg-amber-900/20 p-3">
|
||||||
<p class="text-xs text-amber-700 dark:text-amber-400">
|
<p class="text-xs text-amber-700 dark:text-amber-400">
|
||||||
<svg class="w-4 h-4 inline mr-1" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z" />
|
class="w-4 h-4 inline mr-1"
|
||||||
|
fill="none"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
stroke="currentColor"
|
||||||
|
>
|
||||||
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{{ t('admin.accounts.customErrorCodesWarning') }}
|
{{ t('admin.accounts.customErrorCodesWarning') }}
|
||||||
</p>
|
</p>
|
||||||
@@ -223,13 +305,13 @@
|
|||||||
v-for="code in commonErrorCodes"
|
v-for="code in commonErrorCodes"
|
||||||
:key="code.value"
|
:key="code.value"
|
||||||
type="button"
|
type="button"
|
||||||
@click="toggleErrorCode(code.value)"
|
|
||||||
:class="[
|
:class="[
|
||||||
'rounded-lg px-3 py-1.5 text-sm font-medium transition-colors',
|
'rounded-lg px-3 py-1.5 text-sm font-medium transition-colors',
|
||||||
selectedErrorCodes.includes(code.value)
|
selectedErrorCodes.includes(code.value)
|
||||||
? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400 ring-1 ring-red-500'
|
? 'bg-red-100 text-red-700 dark:bg-red-900/30 dark:text-red-400 ring-1 ring-red-500'
|
||||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500'
|
: 'bg-gray-100 text-gray-600 hover:bg-gray-200 dark:bg-dark-600 dark:text-gray-400 dark:hover:bg-dark-500',
|
||||||
]"
|
]"
|
||||||
|
@click="toggleErrorCode(code.value)"
|
||||||
>
|
>
|
||||||
{{ code.value }} {{ code.label }}
|
{{ code.value }} {{ code.label }}
|
||||||
</button>
|
</button>
|
||||||
@@ -246,13 +328,14 @@
|
|||||||
:placeholder="t('admin.accounts.enterErrorCode')"
|
:placeholder="t('admin.accounts.enterErrorCode')"
|
||||||
@keyup.enter="addCustomErrorCode"
|
@keyup.enter="addCustomErrorCode"
|
||||||
/>
|
/>
|
||||||
<button
|
<button type="button" class="btn btn-secondary px-3" @click="addCustomErrorCode">
|
||||||
type="button"
|
|
||||||
@click="addCustomErrorCode"
|
|
||||||
class="btn btn-secondary px-3"
|
|
||||||
>
|
|
||||||
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="w-4 h-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4" />
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M12 4v16m8-8H4"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@@ -267,11 +350,16 @@
|
|||||||
{{ code }}
|
{{ code }}
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="removeErrorCode(code)"
|
|
||||||
class="hover:text-red-900 dark:hover:text-red-300"
|
class="hover:text-red-900 dark:hover:text-red-300"
|
||||||
|
@click="removeErrorCode(code)"
|
||||||
>
|
>
|
||||||
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
<svg class="w-3.5 h-3.5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
<path
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-width="2"
|
||||||
|
d="M6 18L18 6M6 6l12 12"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
</button>
|
</button>
|
||||||
</span>
|
</span>
|
||||||
@@ -286,28 +374,32 @@
|
|||||||
<div class="border-t border-gray-200 dark:border-dark-600 pt-4">
|
<div class="border-t border-gray-200 dark:border-dark-600 pt-4">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex-1 pr-4">
|
<div class="flex-1 pr-4">
|
||||||
<label class="input-label mb-0">{{ t('admin.accounts.interceptWarmupRequests') }}</label>
|
<label class="input-label mb-0">{{
|
||||||
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">{{ t('admin.accounts.interceptWarmupRequestsDesc') }}</p>
|
t('admin.accounts.interceptWarmupRequests')
|
||||||
|
}}</label>
|
||||||
|
<p class="text-xs text-gray-500 dark:text-gray-400 mt-1">
|
||||||
|
{{ t('admin.accounts.interceptWarmupRequestsDesc') }}
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="enableInterceptWarmup"
|
v-model="enableInterceptWarmup"
|
||||||
|
type="checkbox"
|
||||||
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="enableInterceptWarmup" class="mt-3">
|
<div v-if="enableInterceptWarmup" class="mt-3">
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
@click="interceptWarmupRequests = !interceptWarmupRequests"
|
|
||||||
:class="[
|
:class="[
|
||||||
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
|
'relative inline-flex h-6 w-11 flex-shrink-0 cursor-pointer rounded-full border-2 border-transparent transition-colors duration-200 ease-in-out focus:outline-none focus:ring-2 focus:ring-primary-500 focus:ring-offset-2',
|
||||||
interceptWarmupRequests ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600'
|
interceptWarmupRequests ? 'bg-primary-600' : 'bg-gray-200 dark:bg-dark-600',
|
||||||
]"
|
]"
|
||||||
|
@click="interceptWarmupRequests = !interceptWarmupRequests"
|
||||||
>
|
>
|
||||||
<span
|
<span
|
||||||
:class="[
|
:class="[
|
||||||
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
'pointer-events-none inline-block h-5 w-5 transform rounded-full bg-white shadow ring-0 transition duration-200 ease-in-out',
|
||||||
interceptWarmupRequests ? 'translate-x-5' : 'translate-x-0'
|
interceptWarmupRequests ? 'translate-x-5' : 'translate-x-0',
|
||||||
]"
|
]"
|
||||||
/>
|
/>
|
||||||
</button>
|
</button>
|
||||||
@@ -319,16 +411,13 @@
|
|||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<label class="input-label mb-0">{{ t('admin.accounts.proxy') }}</label>
|
<label class="input-label mb-0">{{ t('admin.accounts.proxy') }}</label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="enableProxy"
|
v-model="enableProxy"
|
||||||
|
type="checkbox"
|
||||||
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="!enableProxy && 'opacity-50 pointer-events-none'">
|
<div :class="!enableProxy && 'opacity-50 pointer-events-none'">
|
||||||
<ProxySelector
|
<ProxySelector v-model="proxyId" :proxies="proxies" />
|
||||||
v-model="proxyId"
|
|
||||||
:proxies="proxies"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -338,8 +427,8 @@
|
|||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<label class="input-label mb-0">{{ t('admin.accounts.concurrency') }}</label>
|
<label class="input-label mb-0">{{ t('admin.accounts.concurrency') }}</label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="enableConcurrency"
|
v-model="enableConcurrency"
|
||||||
|
type="checkbox"
|
||||||
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -356,8 +445,8 @@
|
|||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<label class="input-label mb-0">{{ t('admin.accounts.priority') }}</label>
|
<label class="input-label mb-0">{{ t('admin.accounts.priority') }}</label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="enablePriority"
|
v-model="enablePriority"
|
||||||
|
type="checkbox"
|
||||||
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -377,16 +466,13 @@
|
|||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<label class="input-label mb-0">{{ t('common.status') }}</label>
|
<label class="input-label mb-0">{{ t('common.status') }}</label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="enableStatus"
|
v-model="enableStatus"
|
||||||
|
type="checkbox"
|
||||||
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="!enableStatus && 'opacity-50 pointer-events-none'">
|
<div :class="!enableStatus && 'opacity-50 pointer-events-none'">
|
||||||
<Select
|
<Select v-model="status" :options="statusOptions" />
|
||||||
v-model="status"
|
|
||||||
:options="statusOptions"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -395,43 +481,45 @@
|
|||||||
<div class="flex items-center justify-between mb-3">
|
<div class="flex items-center justify-between mb-3">
|
||||||
<label class="input-label mb-0">{{ t('nav.groups') }}</label>
|
<label class="input-label mb-0">{{ t('nav.groups') }}</label>
|
||||||
<input
|
<input
|
||||||
type="checkbox"
|
|
||||||
v-model="enableGroups"
|
v-model="enableGroups"
|
||||||
|
type="checkbox"
|
||||||
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
class="rounded border-gray-300 text-primary-600 focus:ring-primary-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div :class="!enableGroups && 'opacity-50 pointer-events-none'">
|
<div :class="!enableGroups && 'opacity-50 pointer-events-none'">
|
||||||
<GroupSelector
|
<GroupSelector v-model="groupIds" :groups="groups" />
|
||||||
v-model="groupIds"
|
|
||||||
:groups="groups"
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Action buttons -->
|
<!-- Action buttons -->
|
||||||
<div class="flex justify-end gap-3 pt-4">
|
<div class="flex justify-end gap-3 pt-4">
|
||||||
<button
|
<button type="button" class="btn btn-secondary" @click="handleClose">
|
||||||
@click="handleClose"
|
|
||||||
type="button"
|
|
||||||
class="btn btn-secondary"
|
|
||||||
>
|
|
||||||
{{ t('common.cancel') }}
|
{{ t('common.cancel') }}
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button type="submit" :disabled="submitting" class="btn btn-primary">
|
||||||
type="submit"
|
|
||||||
:disabled="submitting"
|
|
||||||
class="btn btn-primary"
|
|
||||||
>
|
|
||||||
<svg
|
<svg
|
||||||
v-if="submitting"
|
v-if="submitting"
|
||||||
class="animate-spin -ml-1 mr-2 h-4 w-4"
|
class="animate-spin -ml-1 mr-2 h-4 w-4"
|
||||||
fill="none"
|
fill="none"
|
||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
>
|
>
|
||||||
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
<circle
|
||||||
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
|
class="opacity-25"
|
||||||
|
cx="12"
|
||||||
|
cy="12"
|
||||||
|
r="10"
|
||||||
|
stroke="currentColor"
|
||||||
|
stroke-width="4"
|
||||||
|
/>
|
||||||
|
<path
|
||||||
|
class="opacity-75"
|
||||||
|
fill="currentColor"
|
||||||
|
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||||
|
/>
|
||||||
</svg>
|
</svg>
|
||||||
{{ submitting ? t('admin.accounts.bulkEdit.updating') : t('admin.accounts.bulkEdit.submit') }}
|
{{
|
||||||
|
submitting ? t('admin.accounts.bulkEdit.updating') : t('admin.accounts.bulkEdit.submit')
|
||||||
|
}}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@@ -513,18 +601,57 @@ const allModels = [
|
|||||||
{ value: 'gpt-5.1-codex', label: 'GPT-5.1 Codex' },
|
{ value: 'gpt-5.1-codex', label: 'GPT-5.1 Codex' },
|
||||||
{ value: 'gpt-5.1-2025-11-13', label: 'GPT-5.1' },
|
{ value: 'gpt-5.1-2025-11-13', label: 'GPT-5.1' },
|
||||||
{ value: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini' },
|
{ value: 'gpt-5.1-codex-mini', label: 'GPT-5.1 Codex Mini' },
|
||||||
{ value: 'gpt-5-2025-08-07', label: 'GPT-5' }
|
{ value: 'gpt-5-2025-08-07', label: 'GPT-5' },
|
||||||
]
|
]
|
||||||
|
|
||||||
// Preset mappings (combined Anthropic + OpenAI)
|
// Preset mappings (combined Anthropic + OpenAI)
|
||||||
const presetMappings = [
|
const presetMappings = [
|
||||||
{ label: 'Sonnet 4', from: 'claude-sonnet-4-20250514', to: 'claude-sonnet-4-20250514', color: 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400' },
|
{
|
||||||
{ label: 'Sonnet 4.5', from: 'claude-sonnet-4-5-20250929', to: 'claude-sonnet-4-5-20250929', color: 'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400' },
|
label: 'Sonnet 4',
|
||||||
{ label: 'Opus 4.5', from: 'claude-opus-4-5-20251101', to: 'claude-opus-4-5-20251101', color: 'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400' },
|
from: 'claude-sonnet-4-20250514',
|
||||||
{ label: 'Opus->Sonnet', from: 'claude-opus-4-5-20251101', to: 'claude-sonnet-4-5-20250929', color: 'bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400' },
|
to: 'claude-sonnet-4-20250514',
|
||||||
{ label: 'GPT-5.2', from: 'gpt-5.2-2025-12-11', to: 'gpt-5.2-2025-12-11', color: 'bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400' },
|
color: 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400',
|
||||||
{ label: 'GPT-5.2 Codex', from: 'gpt-5.2-codex', to: 'gpt-5.2-codex', color: 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400' },
|
},
|
||||||
{ label: 'Max->Codex', from: 'gpt-5.1-codex-max', to: 'gpt-5.1-codex', color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400' }
|
{
|
||||||
|
label: 'Sonnet 4.5',
|
||||||
|
from: 'claude-sonnet-4-5-20250929',
|
||||||
|
to: 'claude-sonnet-4-5-20250929',
|
||||||
|
color:
|
||||||
|
'bg-indigo-100 text-indigo-700 hover:bg-indigo-200 dark:bg-indigo-900/30 dark:text-indigo-400',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Opus 4.5',
|
||||||
|
from: 'claude-opus-4-5-20251101',
|
||||||
|
to: 'claude-opus-4-5-20251101',
|
||||||
|
color:
|
||||||
|
'bg-purple-100 text-purple-700 hover:bg-purple-200 dark:bg-purple-900/30 dark:text-purple-400',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Opus->Sonnet',
|
||||||
|
from: 'claude-opus-4-5-20251101',
|
||||||
|
to: 'claude-sonnet-4-5-20250929',
|
||||||
|
color:
|
||||||
|
'bg-amber-100 text-amber-700 hover:bg-amber-200 dark:bg-amber-900/30 dark:text-amber-400',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'GPT-5.2',
|
||||||
|
from: 'gpt-5.2-2025-12-11',
|
||||||
|
to: 'gpt-5.2-2025-12-11',
|
||||||
|
color:
|
||||||
|
'bg-green-100 text-green-700 hover:bg-green-200 dark:bg-green-900/30 dark:text-green-400',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'GPT-5.2 Codex',
|
||||||
|
from: 'gpt-5.2-codex',
|
||||||
|
to: 'gpt-5.2-codex',
|
||||||
|
color: 'bg-blue-100 text-blue-700 hover:bg-blue-200 dark:bg-blue-900/30 dark:text-blue-400',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Max->Codex',
|
||||||
|
from: 'gpt-5.1-codex-max',
|
||||||
|
to: 'gpt-5.1-codex',
|
||||||
|
color: 'bg-pink-100 text-pink-700 hover:bg-pink-200 dark:bg-pink-900/30 dark:text-pink-400',
|
||||||
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
// Common HTTP error codes
|
// Common HTTP error codes
|
||||||
@@ -535,12 +662,12 @@ const commonErrorCodes = [
|
|||||||
{ value: 500, label: 'Server Error' },
|
{ value: 500, label: 'Server Error' },
|
||||||
{ value: 502, label: 'Bad Gateway' },
|
{ value: 502, label: 'Bad Gateway' },
|
||||||
{ value: 503, label: 'Unavailable' },
|
{ value: 503, label: 'Unavailable' },
|
||||||
{ value: 529, label: 'Overloaded' }
|
{ value: 529, label: 'Overloaded' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const statusOptions = computed(() => [
|
const statusOptions = computed(() => [
|
||||||
{ value: 'active', label: t('common.active') },
|
{ value: 'active', label: t('common.active') },
|
||||||
{ value: 'inactive', label: t('common.inactive') }
|
{ value: 'inactive', label: t('common.inactive') },
|
||||||
])
|
])
|
||||||
|
|
||||||
// Model mapping helpers
|
// Model mapping helpers
|
||||||
@@ -553,7 +680,7 @@ const removeModelMapping = (index: number) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const addPresetMapping = (from: string, to: string) => {
|
const addPresetMapping = (from: string, to: string) => {
|
||||||
const exists = modelMappings.value.some(m => m.from === from)
|
const exists = modelMappings.value.some((m) => m.from === from)
|
||||||
if (exists) {
|
if (exists) {
|
||||||
appStore.showInfo(t('admin.accounts.mappingExists', { model: from }))
|
appStore.showInfo(t('admin.accounts.mappingExists', { model: from }))
|
||||||
return
|
return
|
||||||
@@ -681,15 +808,16 @@ const handleSubmit = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasAnyFieldEnabled = enableBaseUrl.value
|
const hasAnyFieldEnabled =
|
||||||
|| enableModelRestriction.value
|
enableBaseUrl.value ||
|
||||||
|| enableCustomErrorCodes.value
|
enableModelRestriction.value ||
|
||||||
|| enableInterceptWarmup.value
|
enableCustomErrorCodes.value ||
|
||||||
|| enableProxy.value
|
enableInterceptWarmup.value ||
|
||||||
|| enableConcurrency.value
|
enableProxy.value ||
|
||||||
|| enablePriority.value
|
enableConcurrency.value ||
|
||||||
|| enableStatus.value
|
enablePriority.value ||
|
||||||
|| enableGroups.value
|
enableStatus.value ||
|
||||||
|
enableGroups.value
|
||||||
|
|
||||||
if (!hasAnyFieldEnabled) {
|
if (!hasAnyFieldEnabled) {
|
||||||
appStore.showError(t('admin.accounts.bulkEdit.noFieldsSelected'))
|
appStore.showError(t('admin.accounts.bulkEdit.noFieldsSelected'))
|
||||||
@@ -730,32 +858,35 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Reset form when modal closes
|
// Reset form when modal closes
|
||||||
watch(() => props.show, (newShow) => {
|
watch(
|
||||||
if (!newShow) {
|
() => props.show,
|
||||||
// Reset all enable flags
|
(newShow) => {
|
||||||
enableBaseUrl.value = false
|
if (!newShow) {
|
||||||
enableModelRestriction.value = false
|
// Reset all enable flags
|
||||||
enableCustomErrorCodes.value = false
|
enableBaseUrl.value = false
|
||||||
enableInterceptWarmup.value = false
|
enableModelRestriction.value = false
|
||||||
enableProxy.value = false
|
enableCustomErrorCodes.value = false
|
||||||
enableConcurrency.value = false
|
enableInterceptWarmup.value = false
|
||||||
enablePriority.value = false
|
enableProxy.value = false
|
||||||
enableStatus.value = false
|
enableConcurrency.value = false
|
||||||
enableGroups.value = false
|
enablePriority.value = false
|
||||||
|
enableStatus.value = false
|
||||||
|
enableGroups.value = false
|
||||||
|
|
||||||
// Reset all values
|
// Reset all values
|
||||||
baseUrl.value = ''
|
baseUrl.value = ''
|
||||||
modelRestrictionMode.value = 'whitelist'
|
modelRestrictionMode.value = 'whitelist'
|
||||||
allowedModels.value = []
|
allowedModels.value = []
|
||||||
modelMappings.value = []
|
modelMappings.value = []
|
||||||
selectedErrorCodes.value = []
|
selectedErrorCodes.value = []
|
||||||
customErrorCodeInput.value = null
|
customErrorCodeInput.value = null
|
||||||
interceptWarmupRequests.value = false
|
interceptWarmupRequests.value = false
|
||||||
proxyId.value = null
|
proxyId.value = null
|
||||||
concurrency.value = 1
|
concurrency.value = 1
|
||||||
priority.value = 1
|
priority.value = 1
|
||||||
status.value = 'active'
|
status.value = 'active'
|
||||||
groupIds.value = []
|
groupIds.value = []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
)
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -17,11 +17,14 @@ export default {
|
|||||||
},
|
},
|
||||||
features: {
|
features: {
|
||||||
unifiedGateway: 'Unified API Gateway',
|
unifiedGateway: 'Unified API Gateway',
|
||||||
unifiedGatewayDesc: 'Convert Claude subscriptions to API endpoints. Access AI capabilities through standard /v1/messages interface.',
|
unifiedGatewayDesc:
|
||||||
|
'Convert Claude subscriptions to API endpoints. Access AI capabilities through standard /v1/messages interface.',
|
||||||
multiAccount: 'Multi-Account Pool',
|
multiAccount: 'Multi-Account Pool',
|
||||||
multiAccountDesc: 'Manage multiple upstream accounts with smart load balancing. Support OAuth and API Key authentication.',
|
multiAccountDesc:
|
||||||
|
'Manage multiple upstream accounts with smart load balancing. Support OAuth and API Key authentication.',
|
||||||
balanceQuota: 'Balance & Quota',
|
balanceQuota: 'Balance & Quota',
|
||||||
balanceQuotaDesc: 'Token-based billing with precise usage tracking. Manage quotas and recharge with redeem codes.',
|
balanceQuotaDesc:
|
||||||
|
'Token-based billing with precise usage tracking. Manage quotas and recharge with redeem codes.',
|
||||||
},
|
},
|
||||||
providers: {
|
providers: {
|
||||||
title: 'Supported Providers',
|
title: 'Supported Providers',
|
||||||
@@ -235,7 +238,8 @@ export default {
|
|||||||
useKey: 'Use Key',
|
useKey: 'Use Key',
|
||||||
useKeyModal: {
|
useKeyModal: {
|
||||||
title: 'Use API Key',
|
title: 'Use API Key',
|
||||||
description: 'Add the following environment variables to your terminal profile or run directly in terminal to configure API access.',
|
description:
|
||||||
|
'Add the following environment variables to your terminal profile or run directly in terminal to configure API access.',
|
||||||
copy: 'Copy',
|
copy: 'Copy',
|
||||||
copied: 'Copied',
|
copied: 'Copied',
|
||||||
note: 'These environment variables will be active in the current terminal session. For permanent configuration, add them to ~/.bashrc, ~/.zshrc, or the appropriate configuration file.',
|
note: 'These environment variables will be active in the current terminal session. For permanent configuration, add them to ~/.bashrc, ~/.zshrc, or the appropriate configuration file.',
|
||||||
@@ -517,7 +521,8 @@ export default {
|
|||||||
failedToLoadApiKeys: 'Failed to load user API keys',
|
failedToLoadApiKeys: 'Failed to load user API keys',
|
||||||
deleteConfirm: "Are you sure you want to delete '{email}'? This action cannot be undone.",
|
deleteConfirm: "Are you sure you want to delete '{email}'? This action cannot be undone.",
|
||||||
setAllowedGroups: 'Set Allowed Groups',
|
setAllowedGroups: 'Set Allowed Groups',
|
||||||
allowedGroupsHint: 'Select which standard groups this user can use. Subscription groups are managed separately.',
|
allowedGroupsHint:
|
||||||
|
'Select which standard groups this user can use. Subscription groups are managed separately.',
|
||||||
noStandardGroups: 'No standard groups available',
|
noStandardGroups: 'No standard groups available',
|
||||||
allowAllGroups: 'Allow All Groups',
|
allowAllGroups: 'Allow All Groups',
|
||||||
allowAllGroupsHint: 'User can use any non-exclusive group',
|
allowAllGroupsHint: 'User can use any non-exclusive group',
|
||||||
@@ -529,8 +534,10 @@ export default {
|
|||||||
depositAmount: 'Deposit Amount',
|
depositAmount: 'Deposit Amount',
|
||||||
withdrawAmount: 'Withdraw Amount',
|
withdrawAmount: 'Withdraw Amount',
|
||||||
currentBalance: 'Current Balance',
|
currentBalance: 'Current Balance',
|
||||||
depositNotesPlaceholder: 'e.g., New user registration bonus, promotional credit, compensation, etc.',
|
depositNotesPlaceholder:
|
||||||
withdrawNotesPlaceholder: 'e.g., Service issue refund, incorrect charge reversal, account closure refund, etc.',
|
'e.g., New user registration bonus, promotional credit, compensation, etc.',
|
||||||
|
withdrawNotesPlaceholder:
|
||||||
|
'e.g., Service issue refund, incorrect charge reversal, account closure refund, etc.',
|
||||||
notesOptional: 'Notes are optional but helpful for record keeping',
|
notesOptional: 'Notes are optional but helpful for record keeping',
|
||||||
amountHint: 'Please enter a positive amount',
|
amountHint: 'Please enter a positive amount',
|
||||||
newBalance: 'New Balance',
|
newBalance: 'New Balance',
|
||||||
@@ -597,12 +604,15 @@ export default {
|
|||||||
failedToCreate: 'Failed to create group',
|
failedToCreate: 'Failed to create group',
|
||||||
failedToUpdate: 'Failed to update group',
|
failedToUpdate: 'Failed to update group',
|
||||||
failedToDelete: 'Failed to delete group',
|
failedToDelete: 'Failed to delete group',
|
||||||
deleteConfirm: "Are you sure you want to delete '{name}'? All associated API keys will no longer belong to any group.",
|
deleteConfirm:
|
||||||
deleteConfirmSubscription: "Are you sure you want to delete subscription group '{name}'? This will invalidate all API keys bound to this subscription and delete all related subscription records. This action cannot be undone.",
|
"Are you sure you want to delete '{name}'? All associated API keys will no longer belong to any group.",
|
||||||
|
deleteConfirmSubscription:
|
||||||
|
"Are you sure you want to delete subscription group '{name}'? This will invalidate all API keys bound to this subscription and delete all related subscription records. This action cannot be undone.",
|
||||||
subscription: {
|
subscription: {
|
||||||
title: 'Subscription Settings',
|
title: 'Subscription Settings',
|
||||||
type: 'Billing Type',
|
type: 'Billing Type',
|
||||||
typeHint: 'Standard billing deducts from user balance. Subscription mode uses quota limits instead.',
|
typeHint:
|
||||||
|
'Standard billing deducts from user balance. Subscription mode uses quota limits instead.',
|
||||||
typeNotEditable: 'Billing type cannot be changed after group creation.',
|
typeNotEditable: 'Billing type cannot be changed after group creation.',
|
||||||
standard: 'Standard (Balance)',
|
standard: 'Standard (Balance)',
|
||||||
subscription: 'Subscription (Quota)',
|
subscription: 'Subscription (Quota)',
|
||||||
@@ -674,7 +684,8 @@ export default {
|
|||||||
failedToAssign: 'Failed to assign subscription',
|
failedToAssign: 'Failed to assign subscription',
|
||||||
failedToExtend: 'Failed to extend subscription',
|
failedToExtend: 'Failed to extend subscription',
|
||||||
failedToRevoke: 'Failed to revoke subscription',
|
failedToRevoke: 'Failed to revoke subscription',
|
||||||
revokeConfirm: "Are you sure you want to revoke the subscription for '{user}'? This action cannot be undone.",
|
revokeConfirm:
|
||||||
|
"Are you sure you want to revoke the subscription for '{user}'? This action cannot be undone.",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Accounts
|
// Accounts
|
||||||
@@ -754,7 +765,8 @@ export default {
|
|||||||
},
|
},
|
||||||
bulkEdit: {
|
bulkEdit: {
|
||||||
title: 'Bulk Edit Accounts',
|
title: 'Bulk Edit Accounts',
|
||||||
selectionInfo: '{count} account(s) selected. Only checked or filled fields will be updated; others stay unchanged.',
|
selectionInfo:
|
||||||
|
'{count} account(s) selected. Only checked or filled fields will be updated; others stay unchanged.',
|
||||||
baseUrlPlaceholder: 'https://api.anthropic.com or https://api.openai.com',
|
baseUrlPlaceholder: 'https://api.anthropic.com or https://api.openai.com',
|
||||||
baseUrlNotice: 'Applies to API Key accounts only; leave empty to keep existing value',
|
baseUrlNotice: 'Applies to API Key accounts only; leave empty to keep existing value',
|
||||||
submit: 'Update Accounts',
|
submit: 'Update Accounts',
|
||||||
@@ -797,7 +809,8 @@ export default {
|
|||||||
modelWhitelist: 'Model Whitelist',
|
modelWhitelist: 'Model Whitelist',
|
||||||
modelMapping: 'Model Mapping',
|
modelMapping: 'Model Mapping',
|
||||||
selectAllowedModels: 'Select allowed models. Leave empty to support all models.',
|
selectAllowedModels: 'Select allowed models. Leave empty to support all models.',
|
||||||
mapRequestModels: 'Map request models to actual models. Left is the requested model, right is the actual model sent to API.',
|
mapRequestModels:
|
||||||
|
'Map request models to actual models. Left is the requested model, right is the actual model sent to API.',
|
||||||
selectedModels: 'Selected {count} model(s)',
|
selectedModels: 'Selected {count} model(s)',
|
||||||
supportsAllModels: '(supports all models)',
|
supportsAllModels: '(supports all models)',
|
||||||
requestModel: 'Request model',
|
requestModel: 'Request model',
|
||||||
@@ -806,14 +819,16 @@ export default {
|
|||||||
mappingExists: 'Mapping for {model} already exists',
|
mappingExists: 'Mapping for {model} already exists',
|
||||||
customErrorCodes: 'Custom Error Codes',
|
customErrorCodes: 'Custom Error Codes',
|
||||||
customErrorCodesHint: 'Only stop scheduling for selected error codes',
|
customErrorCodesHint: 'Only stop scheduling for selected error codes',
|
||||||
customErrorCodesWarning: 'Only selected error codes will stop scheduling. Other errors will return 500.',
|
customErrorCodesWarning:
|
||||||
|
'Only selected error codes will stop scheduling. Other errors will return 500.',
|
||||||
selectedErrorCodes: 'Selected',
|
selectedErrorCodes: 'Selected',
|
||||||
noneSelectedUsesDefault: 'None selected (uses default policy)',
|
noneSelectedUsesDefault: 'None selected (uses default policy)',
|
||||||
enterErrorCode: 'Enter error code (100-599)',
|
enterErrorCode: 'Enter error code (100-599)',
|
||||||
invalidErrorCode: 'Please enter a valid HTTP error code (100-599)',
|
invalidErrorCode: 'Please enter a valid HTTP error code (100-599)',
|
||||||
errorCodeExists: 'This error code is already selected',
|
errorCodeExists: 'This error code is already selected',
|
||||||
interceptWarmupRequests: 'Intercept Warmup Requests',
|
interceptWarmupRequests: 'Intercept Warmup Requests',
|
||||||
interceptWarmupRequestsDesc: 'When enabled, warmup requests like title generation will return mock responses without consuming upstream tokens',
|
interceptWarmupRequestsDesc:
|
||||||
|
'When enabled, warmup requests like title generation will return mock responses without consuming upstream tokens',
|
||||||
proxy: 'Proxy',
|
proxy: 'Proxy',
|
||||||
noProxy: 'No Proxy',
|
noProxy: 'No Proxy',
|
||||||
concurrency: 'Concurrency',
|
concurrency: 'Concurrency',
|
||||||
@@ -836,11 +851,13 @@ export default {
|
|||||||
authMethod: 'Authorization Method',
|
authMethod: 'Authorization Method',
|
||||||
manualAuth: 'Manual Authorization',
|
manualAuth: 'Manual Authorization',
|
||||||
cookieAutoAuth: 'Cookie Auto-Auth',
|
cookieAutoAuth: 'Cookie Auto-Auth',
|
||||||
cookieAutoAuthDesc: 'Use claude.ai sessionKey to automatically complete OAuth authorization without manually opening browser.',
|
cookieAutoAuthDesc:
|
||||||
|
'Use claude.ai sessionKey to automatically complete OAuth authorization without manually opening browser.',
|
||||||
sessionKey: 'sessionKey',
|
sessionKey: 'sessionKey',
|
||||||
keysCount: '{count} keys',
|
keysCount: '{count} keys',
|
||||||
batchCreateAccounts: 'Will batch create {count} accounts',
|
batchCreateAccounts: 'Will batch create {count} accounts',
|
||||||
sessionKeyPlaceholder: 'One sessionKey per line, e.g.:\nsk-ant-sid01-xxxxx...\nsk-ant-sid01-yyyyy...',
|
sessionKeyPlaceholder:
|
||||||
|
'One sessionKey per line, e.g.:\nsk-ant-sid01-xxxxx...\nsk-ant-sid01-yyyyy...',
|
||||||
sessionKeyPlaceholderSingle: 'sk-ant-sid01-xxxxx...',
|
sessionKeyPlaceholderSingle: 'sk-ant-sid01-xxxxx...',
|
||||||
howToGetSessionKey: 'How to get sessionKey',
|
howToGetSessionKey: 'How to get sessionKey',
|
||||||
step1: 'Login to <strong>claude.ai</strong> in your browser',
|
step1: 'Login to <strong>claude.ai</strong> in your browser',
|
||||||
@@ -858,10 +875,13 @@ export default {
|
|||||||
generating: 'Generating...',
|
generating: 'Generating...',
|
||||||
regenerate: 'Regenerate',
|
regenerate: 'Regenerate',
|
||||||
step2OpenUrl: 'Open the URL in your browser and complete authorization',
|
step2OpenUrl: 'Open the URL in your browser and complete authorization',
|
||||||
openUrlDesc: 'Open the authorization URL in a new tab, log in to your Claude account and authorize.',
|
openUrlDesc:
|
||||||
proxyWarning: '<strong>Note:</strong> If you configured a proxy, make sure your browser uses the same proxy to access the authorization page.',
|
'Open the authorization URL in a new tab, log in to your Claude account and authorize.',
|
||||||
|
proxyWarning:
|
||||||
|
'<strong>Note:</strong> If you configured a proxy, make sure your browser uses the same proxy to access the authorization page.',
|
||||||
step3EnterCode: 'Enter the Authorization Code',
|
step3EnterCode: 'Enter the Authorization Code',
|
||||||
authCodeDesc: 'After authorization is complete, the page will display an <strong>Authorization Code</strong>. Copy and paste it below:',
|
authCodeDesc:
|
||||||
|
'After authorization is complete, the page will display an <strong>Authorization Code</strong>. Copy and paste it below:',
|
||||||
authCode: 'Authorization Code',
|
authCode: 'Authorization Code',
|
||||||
authCodePlaceholder: 'Paste the Authorization Code from Claude page...',
|
authCodePlaceholder: 'Paste the Authorization Code from Claude page...',
|
||||||
authCodeHint: 'Paste the Authorization Code copied from the Claude page',
|
authCodeHint: 'Paste the Authorization Code copied from the Claude page',
|
||||||
@@ -879,13 +899,18 @@ export default {
|
|||||||
step1GenerateUrl: 'Click the button below to generate the authorization URL',
|
step1GenerateUrl: 'Click the button below to generate the authorization URL',
|
||||||
generateAuthUrl: 'Generate Auth URL',
|
generateAuthUrl: 'Generate Auth URL',
|
||||||
step2OpenUrl: 'Open the URL in your browser and complete authorization',
|
step2OpenUrl: 'Open the URL in your browser and complete authorization',
|
||||||
openUrlDesc: 'Open the authorization URL in a new tab, log in to your OpenAI account and authorize.',
|
openUrlDesc:
|
||||||
importantNotice: '<strong>Important:</strong> The page may take a while to load after authorization. Please wait patiently. When the browser address bar changes to <code>http://localhost...</code>, the authorization is complete.',
|
'Open the authorization URL in a new tab, log in to your OpenAI account and authorize.',
|
||||||
|
importantNotice:
|
||||||
|
'<strong>Important:</strong> The page may take a while to load after authorization. Please wait patiently. When the browser address bar changes to <code>http://localhost...</code>, the authorization is complete.',
|
||||||
step3EnterCode: 'Enter Authorization URL or Code',
|
step3EnterCode: 'Enter Authorization URL or Code',
|
||||||
authCodeDesc: 'After authorization is complete, when the page URL becomes <code>http://localhost:xxx/auth/callback?code=...</code>:',
|
authCodeDesc:
|
||||||
|
'After authorization is complete, when the page URL becomes <code>http://localhost:xxx/auth/callback?code=...</code>:',
|
||||||
authCode: 'Authorization URL or Code',
|
authCode: 'Authorization URL or Code',
|
||||||
authCodePlaceholder: 'Option 1: Copy the complete URL\n(http://localhost:xxx/auth/callback?code=...)\nOption 2: Copy only the code parameter value',
|
authCodePlaceholder:
|
||||||
authCodeHint: 'You can copy the entire URL or just the code parameter value, the system will auto-detect',
|
'Option 1: Copy the complete URL\n(http://localhost:xxx/auth/callback?code=...)\nOption 2: Copy only the code parameter value',
|
||||||
|
authCodeHint:
|
||||||
|
'You can copy the entire URL or just the code parameter value, the system will auto-detect',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
// Re-Auth Modal
|
// Re-Auth Modal
|
||||||
@@ -985,8 +1010,10 @@ export default {
|
|||||||
standardAdd: 'Standard Add',
|
standardAdd: 'Standard Add',
|
||||||
batchAdd: 'Quick Add',
|
batchAdd: 'Quick Add',
|
||||||
batchInput: 'Proxy List',
|
batchInput: 'Proxy List',
|
||||||
batchInputPlaceholder: "Enter one proxy per line in the following formats:\nsocks5://user:pass{'@'}192.168.1.1:1080\nhttp://192.168.1.1:8080\nhttps://user:pass{'@'}proxy.example.com:443",
|
batchInputPlaceholder:
|
||||||
batchInputHint: "Supports http, https, socks5 protocols. Format: protocol://[user:pass{'@'}]host:port",
|
"Enter one proxy per line in the following formats:\nsocks5://user:pass{'@'}192.168.1.1:1080\nhttp://192.168.1.1:8080\nhttps://user:pass{'@'}proxy.example.com:443",
|
||||||
|
batchInputHint:
|
||||||
|
"Supports http, https, socks5 protocols. Format: protocol://[user:pass{'@'}]host:port",
|
||||||
parsedCount: '{count} valid',
|
parsedCount: '{count} valid',
|
||||||
invalidCount: '{count} invalid',
|
invalidCount: '{count} invalid',
|
||||||
duplicateCount: '{count} duplicate',
|
duplicateCount: '{count} duplicate',
|
||||||
@@ -1009,7 +1036,8 @@ export default {
|
|||||||
failedToUpdate: 'Failed to update proxy',
|
failedToUpdate: 'Failed to update proxy',
|
||||||
failedToDelete: 'Failed to delete proxy',
|
failedToDelete: 'Failed to delete proxy',
|
||||||
failedToTest: 'Failed to test proxy',
|
failedToTest: 'Failed to test proxy',
|
||||||
deleteConfirm: "Are you sure you want to delete '{name}'? Accounts using this proxy will have their proxy removed.",
|
deleteConfirm:
|
||||||
|
"Are you sure you want to delete '{name}'? Accounts using this proxy will have their proxy removed.",
|
||||||
},
|
},
|
||||||
|
|
||||||
// Redeem Codes
|
// Redeem Codes
|
||||||
@@ -1038,8 +1066,10 @@ export default {
|
|||||||
exportCsv: 'Export CSV',
|
exportCsv: 'Export CSV',
|
||||||
deleteAllUnused: 'Delete All Unused Codes',
|
deleteAllUnused: 'Delete All Unused Codes',
|
||||||
deleteCode: 'Delete Redeem Code',
|
deleteCode: 'Delete Redeem Code',
|
||||||
deleteCodeConfirm: 'Are you sure you want to delete this redeem code? This action cannot be undone.',
|
deleteCodeConfirm:
|
||||||
deleteAllUnusedConfirm: 'Are you sure you want to delete all unused (active) redeem codes? This action cannot be undone.',
|
'Are you sure you want to delete this redeem code? This action cannot be undone.',
|
||||||
|
deleteAllUnusedConfirm:
|
||||||
|
'Are you sure you want to delete all unused (active) redeem codes? This action cannot be undone.',
|
||||||
deleteAll: 'Delete All',
|
deleteAll: 'Delete All',
|
||||||
generateCodesTitle: 'Generate Redeem Codes',
|
generateCodesTitle: 'Generate Redeem Codes',
|
||||||
generatedSuccessfully: 'Generated Successfully',
|
generatedSuccessfully: 'Generated Successfully',
|
||||||
@@ -1119,7 +1149,8 @@ export default {
|
|||||||
siteSubtitle: 'Site Subtitle',
|
siteSubtitle: 'Site Subtitle',
|
||||||
siteSubtitleHint: 'Displayed on login and register pages',
|
siteSubtitleHint: 'Displayed on login and register pages',
|
||||||
apiBaseUrl: 'API Base URL',
|
apiBaseUrl: 'API Base URL',
|
||||||
apiBaseUrlHint: 'Used for "Use Key" and "Import to CC Switch" features. Leave empty to use current site URL.',
|
apiBaseUrlHint:
|
||||||
|
'Used for "Use Key" and "Import to CC Switch" features. Leave empty to use current site URL.',
|
||||||
contactInfo: 'Contact Info',
|
contactInfo: 'Contact Info',
|
||||||
contactInfoPlaceholder: 'e.g., QQ: 123456789',
|
contactInfoPlaceholder: 'e.g., QQ: 123456789',
|
||||||
contactInfoHint: 'Customer support contact info, displayed on redeem page, profile, etc.',
|
contactInfoHint: 'Customer support contact info, displayed on redeem page, profile, etc.',
|
||||||
@@ -1169,7 +1200,8 @@ export default {
|
|||||||
create: 'Create Key',
|
create: 'Create Key',
|
||||||
creating: 'Creating...',
|
creating: 'Creating...',
|
||||||
regenerateConfirm: 'Are you sure? The current key will be immediately invalidated.',
|
regenerateConfirm: 'Are you sure? The current key will be immediately invalidated.',
|
||||||
deleteConfirm: 'Are you sure you want to delete the admin API key? External integrations will stop working.',
|
deleteConfirm:
|
||||||
|
'Are you sure you want to delete the admin API key? External integrations will stop working.',
|
||||||
keyGenerated: 'New admin API key generated',
|
keyGenerated: 'New admin API key generated',
|
||||||
keyDeleted: 'Admin API key deleted',
|
keyDeleted: 'Admin API key deleted',
|
||||||
copyKey: 'Copy Key',
|
copyKey: 'Copy Key',
|
||||||
@@ -1235,7 +1267,8 @@ export default {
|
|||||||
title: 'My Subscriptions',
|
title: 'My Subscriptions',
|
||||||
description: 'View your subscription plans and usage',
|
description: 'View your subscription plans and usage',
|
||||||
noActiveSubscriptions: 'No Active Subscriptions',
|
noActiveSubscriptions: 'No Active Subscriptions',
|
||||||
noActiveSubscriptionsDesc: 'You don\'t have any active subscriptions. Contact administrator to get one.',
|
noActiveSubscriptionsDesc:
|
||||||
|
"You don't have any active subscriptions. Contact administrator to get one.",
|
||||||
status: {
|
status: {
|
||||||
active: 'Active',
|
active: 'Active',
|
||||||
expired: 'Expired',
|
expired: 'Expired',
|
||||||
|
|||||||
@@ -620,7 +620,8 @@ export default {
|
|||||||
editGroup: '编辑分组',
|
editGroup: '编辑分组',
|
||||||
deleteGroup: '删除分组',
|
deleteGroup: '删除分组',
|
||||||
deleteConfirm: "确定要删除分组 '{name}' 吗?所有关联的 API 密钥将不再属于任何分组。",
|
deleteConfirm: "确定要删除分组 '{name}' 吗?所有关联的 API 密钥将不再属于任何分组。",
|
||||||
deleteConfirmSubscription: "确定要删除订阅分组 '{name}' 吗?此操作会让所有绑定此订阅的用户的 API Key 失效,并删除所有相关的订阅记录。此操作无法撤销。",
|
deleteConfirmSubscription:
|
||||||
|
"确定要删除订阅分组 '{name}' 吗?此操作会让所有绑定此订阅的用户的 API Key 失效,并删除所有相关的订阅记录。此操作无法撤销。",
|
||||||
columns: {
|
columns: {
|
||||||
name: '名称',
|
name: '名称',
|
||||||
platform: '平台',
|
platform: '平台',
|
||||||
@@ -782,7 +783,8 @@ export default {
|
|||||||
createAccount: '添加账号',
|
createAccount: '添加账号',
|
||||||
syncFromCrs: '从 CRS 同步',
|
syncFromCrs: '从 CRS 同步',
|
||||||
syncFromCrsTitle: '从 CRS 同步账号',
|
syncFromCrsTitle: '从 CRS 同步账号',
|
||||||
syncFromCrsDesc: '将 claude-relay-service(CRS)中的账号同步到当前系统(不会在浏览器侧直接请求 CRS)。',
|
syncFromCrsDesc:
|
||||||
|
'将 claude-relay-service(CRS)中的账号同步到当前系统(不会在浏览器侧直接请求 CRS)。',
|
||||||
crsBaseUrl: 'CRS 服务地址',
|
crsBaseUrl: 'CRS 服务地址',
|
||||||
crsBaseUrlPlaceholder: '例如:http://127.0.0.1:3000',
|
crsBaseUrlPlaceholder: '例如:http://127.0.0.1:3000',
|
||||||
crsUsername: '用户名',
|
crsUsername: '用户名',
|
||||||
@@ -973,7 +975,8 @@ export default {
|
|||||||
sessionKey: 'sessionKey',
|
sessionKey: 'sessionKey',
|
||||||
keysCount: '{count} 个密钥',
|
keysCount: '{count} 个密钥',
|
||||||
batchCreateAccounts: '将批量创建 {count} 个账号',
|
batchCreateAccounts: '将批量创建 {count} 个账号',
|
||||||
sessionKeyPlaceholder: '每行一个 sessionKey,例如:\nsk-ant-sid01-xxxxx...\nsk-ant-sid01-yyyyy...',
|
sessionKeyPlaceholder:
|
||||||
|
'每行一个 sessionKey,例如:\nsk-ant-sid01-xxxxx...\nsk-ant-sid01-yyyyy...',
|
||||||
sessionKeyPlaceholderSingle: 'sk-ant-sid01-xxxxx...',
|
sessionKeyPlaceholderSingle: 'sk-ant-sid01-xxxxx...',
|
||||||
howToGetSessionKey: '如何获取 sessionKey',
|
howToGetSessionKey: '如何获取 sessionKey',
|
||||||
step1: '在浏览器中登录 <strong>claude.ai</strong>',
|
step1: '在浏览器中登录 <strong>claude.ai</strong>',
|
||||||
@@ -992,7 +995,8 @@ export default {
|
|||||||
regenerate: '重新生成',
|
regenerate: '重新生成',
|
||||||
step2OpenUrl: '在浏览器中打开 URL 并完成授权',
|
step2OpenUrl: '在浏览器中打开 URL 并完成授权',
|
||||||
openUrlDesc: '在新标签页中打开授权 URL,登录您的 Claude 账号并授权。',
|
openUrlDesc: '在新标签页中打开授权 URL,登录您的 Claude 账号并授权。',
|
||||||
proxyWarning: '<strong>注意:</strong>如果您配置了代理,请确保浏览器使用相同的代理访问授权页面。',
|
proxyWarning:
|
||||||
|
'<strong>注意:</strong>如果您配置了代理,请确保浏览器使用相同的代理访问授权页面。',
|
||||||
step3EnterCode: '输入授权码',
|
step3EnterCode: '输入授权码',
|
||||||
authCodeDesc: '授权完成后,页面会显示一个 <strong>授权码</strong>。复制并粘贴到下方:',
|
authCodeDesc: '授权完成后,页面会显示一个 <strong>授权码</strong>。复制并粘贴到下方:',
|
||||||
authCode: '授权码',
|
authCode: '授权码',
|
||||||
@@ -1013,11 +1017,14 @@ export default {
|
|||||||
generateAuthUrl: '生成授权链接',
|
generateAuthUrl: '生成授权链接',
|
||||||
step2OpenUrl: '在浏览器中打开链接并完成授权',
|
step2OpenUrl: '在浏览器中打开链接并完成授权',
|
||||||
openUrlDesc: '请在新标签页中打开授权链接,登录您的 OpenAI 账户并授权。',
|
openUrlDesc: '请在新标签页中打开授权链接,登录您的 OpenAI 账户并授权。',
|
||||||
importantNotice: '<strong>重要提示:</strong>授权后页面可能会加载较长时间,请耐心等待。当浏览器地址栏变为 <code>http://localhost...</code> 开头时,表示授权已完成。',
|
importantNotice:
|
||||||
|
'<strong>重要提示:</strong>授权后页面可能会加载较长时间,请耐心等待。当浏览器地址栏变为 <code>http://localhost...</code> 开头时,表示授权已完成。',
|
||||||
step3EnterCode: '输入授权链接或 Code',
|
step3EnterCode: '输入授权链接或 Code',
|
||||||
authCodeDesc: '授权完成后,当页面地址变为 <code>http://localhost:xxx/auth/callback?code=...</code> 时:',
|
authCodeDesc:
|
||||||
|
'授权完成后,当页面地址变为 <code>http://localhost:xxx/auth/callback?code=...</code> 时:',
|
||||||
authCode: '授权链接或 Code',
|
authCode: '授权链接或 Code',
|
||||||
authCodePlaceholder: '方式1:复制完整的链接\n(http://localhost:xxx/auth/callback?code=...)\n方式2:仅复制 code 参数的值',
|
authCodePlaceholder:
|
||||||
|
'方式1:复制完整的链接\n(http://localhost:xxx/auth/callback?code=...)\n方式2:仅复制 code 参数的值',
|
||||||
authCodeHint: '您可以直接复制整个链接或仅复制 code 参数值,系统会自动识别',
|
authCodeHint: '您可以直接复制整个链接或仅复制 code 参数值,系统会自动识别',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@@ -1153,7 +1160,8 @@ export default {
|
|||||||
standardAdd: '标准添加',
|
standardAdd: '标准添加',
|
||||||
batchAdd: '快捷添加',
|
batchAdd: '快捷添加',
|
||||||
batchInput: '代理列表',
|
batchInput: '代理列表',
|
||||||
batchInputPlaceholder: "每行输入一个代理,支持以下格式:\nsocks5://user:pass{'@'}192.168.1.1:1080\nhttp://192.168.1.1:8080\nhttps://user:pass{'@'}proxy.example.com:443",
|
batchInputPlaceholder:
|
||||||
|
"每行输入一个代理,支持以下格式:\nsocks5://user:pass{'@'}192.168.1.1:1080\nhttp://192.168.1.1:8080\nhttps://user:pass{'@'}proxy.example.com:443",
|
||||||
batchInputHint: "支持 http、https、socks5 协议,格式:协议://[用户名:密码{'@'}]主机:端口",
|
batchInputHint: "支持 http、https、socks5 协议,格式:协议://[用户名:密码{'@'}]主机:端口",
|
||||||
parsedCount: '有效 {count} 个',
|
parsedCount: '有效 {count} 个',
|
||||||
invalidCount: '无效 {count} 个',
|
invalidCount: '无效 {count} 个',
|
||||||
|
|||||||
Reference in New Issue
Block a user