import { useMutation, useQueryClient } from '@tanstack/react-query';
import { Stack, router } from 'expo-router';
import { useMemo, useState } from 'react';
import { Pressable, ScrollView, Text, TextInput, View } from 'react-native';
import { SafeAreaView } from 'react-native-safe-area-context';
import { createAccount } from '@/src/services/admin';
import type { AccountType } from '@/src/types/admin';
const colors = {
page: '#f4efe4',
card: '#fbf8f2',
text: '#16181a',
subtext: '#6f665c',
border: '#e7dfcf',
primary: '#1d5f55',
dark: '#1b1d1f',
errorBg: '#f7e1d6',
errorText: '#a4512b',
muted: '#f7f1e6',
};
const PLATFORM_OPTIONS = ['anthropic', 'openai', 'gemini', 'sora', 'antigravity'];
function Section({ title, children }: { title: string; children: React.ReactNode }) {
return (
{title}
{children}
);
}
function getErrorMessage(error: unknown) {
if (error instanceof Error && error.message) {
switch (error.message) {
case 'BASE_URL_REQUIRED':
return '请先到服务器页填写服务地址。';
case 'ADMIN_API_KEY_REQUIRED':
return '请先到服务器页填写 Admin Token。';
case 'INVALID_SERVER_RESPONSE':
return '服务返回格式异常,请确认后端接口可用并检查网关日志。';
case 'REQUEST_FAILED':
return '请求失败,请检查服务地址、Token 和网络连通性。';
default:
return error.message;
}
}
return '创建账号失败,请稍后重试。';
}
function parseNumberValue(raw: string) {
if (!raw.trim()) return undefined;
const value = Number(raw);
return Number.isFinite(value) ? value : undefined;
}
function parseGroupIds(raw: string) {
const values = raw
.split(',')
.map((item) => Number(item.trim()))
.filter((value) => Number.isFinite(value) && value > 0);
return values.length > 0 ? values : undefined;
}
function parseJsonObject(raw: string) {
if (!raw.trim()) return {} as Record;
const parsed = JSON.parse(raw) as Record;
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
const entries = Object.entries(parsed as Record);
for (const [, value] of entries) {
const valueType = typeof value;
if (
value !== null
&& value !== undefined
&& valueType !== 'string'
&& valueType !== 'number'
&& valueType !== 'boolean'
) {
throw new Error('JSON 仅支持 string / number / boolean / null。');
}
}
return parsed;
}
throw new Error('JSON 需要是对象格式。');
}
export default function CreateAccountScreen() {
const queryClient = useQueryClient();
const [name, setName] = useState('');
const [notes, setNotes] = useState('');
const [platform, setPlatform] = useState('anthropic');
const [accountType, setAccountType] = useState('apikey');
const [baseUrl, setBaseUrl] = useState('');
const [apiKey, setApiKey] = useState('');
const [accessToken, setAccessToken] = useState('');
const [refreshToken, setRefreshToken] = useState('');
const [clientId, setClientId] = useState('');
const [concurrency, setConcurrency] = useState('');
const [priority, setPriority] = useState('');
const [rateMultiplier, setRateMultiplier] = useState('');
const [proxyId, setProxyId] = useState('');
const [groupIds, setGroupIds] = useState('');
const [extraCredentialsJson, setExtraCredentialsJson] = useState('');
const [formError, setFormError] = useState(null);
const canSubmit = useMemo(() => {
if (!name.trim()) return false;
if (accountType === 'apikey') return Boolean(baseUrl.trim() && apiKey.trim());
return Boolean(accessToken.trim());
}, [accessToken, accountType, apiKey, baseUrl, name]);
const createMutation = useMutation({
mutationFn: async () => {
const credentialsFromJson = parseJsonObject(extraCredentialsJson);
const credentials = accountType === 'apikey'
? {
...credentialsFromJson,
base_url: baseUrl.trim(),
api_key: apiKey.trim(),
}
: {
...credentialsFromJson,
access_token: accessToken.trim(),
refresh_token: refreshToken.trim() || undefined,
client_id: clientId.trim() || undefined,
};
return createAccount({
name: name.trim(),
platform,
type: accountType,
notes: notes.trim() || undefined,
concurrency: parseNumberValue(concurrency),
priority: parseNumberValue(priority),
rate_multiplier: parseNumberValue(rateMultiplier),
proxy_id: parseNumberValue(proxyId),
group_ids: parseGroupIds(groupIds),
credentials,
});
},
onSuccess: () => {
setFormError(null);
queryClient.invalidateQueries({ queryKey: ['accounts'] });
router.replace('/(tabs)/accounts');
},
onError: (error) => {
setFormError(getErrorMessage(error));
},
});
return (
<>
账号名称
平台
{PLATFORM_OPTIONS.map((item) => {
const active = platform === item;
return (
setPlatform(item)}
style={{
borderRadius: 999,
paddingHorizontal: 12,
paddingVertical: 8,
borderWidth: 1,
borderColor: active ? colors.primary : colors.border,
backgroundColor: active ? colors.primary : colors.muted,
}}
>
{item}
);
})}
账号类型
{(['apikey', 'oauth'] as const).map((item) => {
const active = accountType === item;
return (
setAccountType(item)}
style={{
flex: 1,
borderRadius: 12,
paddingVertical: 11,
alignItems: 'center',
borderWidth: 1,
borderColor: active ? colors.primary : colors.border,
backgroundColor: active ? colors.primary : colors.muted,
}}
>
{item.toUpperCase()}
);
})}
备注(可选)
{accountType === 'apikey' ? (
<>
Base URL
API Key
>
) : (
<>
Access Token
Refresh Token(可选)
Client ID(可选)
>
)}
额外凭证 JSON(可选)
并发 concurrency
优先级 priority
倍率 rate_multiplier
代理 ID proxy_id
分组 IDs(逗号分隔)
{formError ? (
{formError}
) : null}
{
setFormError(null);
createMutation.mutate();
}}
disabled={!canSubmit || createMutation.isPending}
style={{
backgroundColor: !canSubmit || createMutation.isPending ? '#8a8072' : colors.dark,
borderRadius: 12,
paddingVertical: 14,
alignItems: 'center',
}}
>
{createMutation.isPending ? '提交中...' : '创建账号'}
>
);
}