Add site filters for manual connection selection (#328)

This commit is contained in:
Cita
2026-03-30 16:32:48 +08:00
committed by GitHub
parent 0d2c4501c2
commit 7d91823f6c
7 changed files with 239 additions and 36 deletions
+22 -21
View File
@@ -43,6 +43,8 @@ const ACCOUNT_SEGMENTS: Array<{
{ value: 'tokens', label: '账号令牌管理', tooltip: '从账号同步或手动维护,供路由实际调用', tooltipSide: 'bottom', tooltipAlign: 'end' },
];
const SITE_SELECT_SEARCH_PLACEHOLDER = '筛选站点(名称 / 平台 / URL)';
function createLoginForm() {
return { siteId: 0, username: '', password: '' };
}
@@ -172,6 +174,17 @@ export default function Accounts() {
() => sites.find((item) => item.id === tokenForm.siteId) || null,
[sites, tokenForm.siteId],
);
const siteSelectOptions = useMemo(
() => [
{ value: '0', label: '选择站点' },
...sites.map((site: any) => ({
value: String(site.id),
label: `${site.name} (${site.platform})`,
description: site.url || undefined,
})),
],
[sites],
);
const isSub2ApiSelected = (selectedTokenSite?.platform || '').toLowerCase() === 'sub2api';
const activeAddCredentialMode = activeSegment === 'apikey' ? 'apikey' : 'session';
@@ -1176,14 +1189,10 @@ export default function Accounts() {
setTokenForm((f) => ({ ...f, siteId: nextSiteId }));
setVerifyResult(null);
}}
options={[
{ value: '0', label: '选择站点' },
...sites.map((s: any) => ({
value: String(s.id),
label: `${s.name} (${s.platform})`,
})),
]}
options={siteSelectOptions}
placeholder="选择站点"
searchable
searchPlaceholder={SITE_SELECT_SEARCH_PLACEHOLDER}
/>
<input
placeholder="连接名称(可选)"
@@ -1303,14 +1312,10 @@ export default function Accounts() {
const nextSiteId = Number.parseInt(nextValue, 10) || 0;
setLoginForm((f) => ({ ...f, siteId: nextSiteId }));
}}
options={[
{ value: '0', label: '选择站点' },
...sites.map((s: any) => ({
value: String(s.id),
label: `${s.name} (${s.platform})`,
})),
]}
options={siteSelectOptions}
placeholder="选择站点"
searchable
searchPlaceholder={SITE_SELECT_SEARCH_PLACEHOLDER}
/>
<input placeholder="用户名" value={loginForm.username} onChange={(e) => setLoginForm((f) => ({ ...f, username: e.target.value }))} style={inputStyle} />
<input type="password" placeholder="密码" value={loginForm.password} onChange={(e) => setLoginForm((f) => ({ ...f, password: e.target.value }))} onKeyDown={(e) => e.key === 'Enter' && handleLoginAdd()} style={inputStyle} />
@@ -1332,14 +1337,10 @@ export default function Accounts() {
setTokenForm((f) => ({ ...f, siteId: nextSiteId, credentialMode: 'apikey' }));
setVerifyResult(null);
}}
options={[
{ value: '0', label: '选择站点' },
...sites.map((s: any) => ({
value: String(s.id),
label: `${s.name} (${s.platform})`,
})),
]}
options={siteSelectOptions}
placeholder="选择站点"
searchable
searchPlaceholder={SITE_SELECT_SEARCH_PLACEHOLDER}
/>
<input
placeholder="连接名称(可选)"
+42 -4
View File
@@ -24,11 +24,14 @@ async function flushMicrotasks() {
});
}
async function renderAccounts(initialEntry: string) {
apiMock.getAccounts.mockResolvedValue([]);
apiMock.getSites.mockResolvedValue([
async function renderAccounts(
initialEntry: string,
sites: Array<{ id: number; name: string; url?: string; platform: string; status: string }> = [
{ id: 10, name: 'Demo Site', platform: 'new-api', status: 'active' },
]);
],
) {
apiMock.getAccounts.mockResolvedValue([]);
apiMock.getSites.mockResolvedValue(sites);
apiMock.getAccountTokens.mockResolvedValue([]);
let root!: WebTestRenderer;
@@ -81,6 +84,41 @@ describe('Accounts create intent handling', () => {
}
});
it('uses searchable site selectors for manual connection creation', async () => {
const root = await renderAccounts('/accounts', [
{ id: 10, name: 'Demo Site', url: 'https://demo.example.com', platform: 'new-api', status: 'active' },
{ id: 11, name: 'Codex Workspace', url: 'https://workspace.example.com', platform: 'codex', status: 'active' },
]);
try {
const addButton = root.root.find((node) => (
node.type === 'button'
&& typeof node.props.onClick === 'function'
&& typeof node.props.className === 'string'
&& node.props.className.includes('btn btn-primary')
));
await act(async () => {
addButton.props.onClick();
});
await flushMicrotasks();
const selects = root.root.findAllByType(ModernSelect);
expect(selects[1]?.props.searchable).toBe(true);
expect(selects[1]?.props.searchPlaceholder).toBe('筛选站点(名称 / 平台 / URL)');
expect(selects[1]?.props.options).toEqual(
expect.arrayContaining([
expect.objectContaining({
value: '11',
label: 'Codex Workspace (codex)',
description: 'https://workspace.example.com',
}),
]),
);
} finally {
root?.unmount();
}
});
it('ignores create intent in the tokens segment', async () => {
const root = await renderAccounts('/accounts?segment=tokens&create=1&siteId=10');
try {