forked from boos/BoosAPI
feat: 支持从站点页快捷添加 API Key (#505)
Co-authored-by: juzi <956428403@qq.com>
This commit is contained in:
+78
-19
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* @Author: 橘子
|
||||
* @Project_description: Metapi 站点管理页
|
||||
* @Description: 代码是我抄的,不会也是真的
|
||||
*/
|
||||
import { useEffect, useMemo, useRef, useState } from 'react';
|
||||
import { useLocation, useNavigate } from 'react-router-dom';
|
||||
import { api } from '../api.js';
|
||||
@@ -105,6 +110,23 @@ function resolveSiteCreatedSessionLabel(platform?: string | null): string {
|
||||
return '添加账号(用户名密码登录)';
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到站点对应的连接补全流程。
|
||||
*/
|
||||
function buildSiteConnectionSearchParams(input: {
|
||||
siteId: number;
|
||||
initializationPresetId?: string | null;
|
||||
}) {
|
||||
const params = new URLSearchParams({
|
||||
create: '1',
|
||||
siteId: String(input.siteId),
|
||||
});
|
||||
if (input.initializationPresetId) {
|
||||
params.set('initPreset', input.initializationPresetId);
|
||||
}
|
||||
return params;
|
||||
}
|
||||
|
||||
function formatSubscriptionDate(value?: string | null): string {
|
||||
if (!value) return '';
|
||||
const parsed = Date.parse(value);
|
||||
@@ -875,32 +897,45 @@ export default function Sites() {
|
||||
});
|
||||
};
|
||||
|
||||
const handleSiteCreatedChoice = (choice: 'session' | 'apikey' | 'later') => {
|
||||
if (!createdSiteForChoice) return;
|
||||
|
||||
const siteId = createdSiteForChoice.id;
|
||||
const platform = createdSiteForChoice.platform?.toLowerCase().trim();
|
||||
const params = new URLSearchParams({
|
||||
create: '1',
|
||||
siteId: String(siteId),
|
||||
/**
|
||||
* 从站点页进入账号/API Key 连接创建流程。
|
||||
*/
|
||||
const openSiteConnectionFlow = (input: {
|
||||
siteId: number;
|
||||
platform?: string | null;
|
||||
initializationPresetId?: string | null;
|
||||
choice: 'session' | 'apikey';
|
||||
}) => {
|
||||
const platform = input.platform?.toLowerCase().trim();
|
||||
const params = buildSiteConnectionSearchParams({
|
||||
siteId: input.siteId,
|
||||
initializationPresetId: input.initializationPresetId,
|
||||
});
|
||||
if (createdSiteForChoice.initializationPresetId) {
|
||||
params.set('initPreset', createdSiteForChoice.initializationPresetId);
|
||||
}
|
||||
|
||||
if (choice === 'session') {
|
||||
// codex平台使用OAuth流程
|
||||
if (input.choice === 'session') {
|
||||
if (platform === 'codex') {
|
||||
params.set('provider', 'codex');
|
||||
navigate(`/oauth?${params.toString()}`);
|
||||
} else {
|
||||
// 其他平台跳转到账号创建页面(session模式)
|
||||
navigate(`/accounts?${params.toString()}`);
|
||||
return;
|
||||
}
|
||||
} else if (choice === 'apikey') {
|
||||
// 跳转到账号创建页面(apikey模式)
|
||||
params.set('segment', 'apikey');
|
||||
navigate(`/accounts?${params.toString()}`);
|
||||
return;
|
||||
}
|
||||
|
||||
params.set('segment', 'apikey');
|
||||
navigate(`/accounts?${params.toString()}`);
|
||||
};
|
||||
|
||||
const handleSiteCreatedChoice = (choice: 'session' | 'apikey' | 'later') => {
|
||||
if (!createdSiteForChoice) return;
|
||||
|
||||
if (choice === 'session' || choice === 'apikey') {
|
||||
openSiteConnectionFlow({
|
||||
siteId: createdSiteForChoice.id,
|
||||
platform: createdSiteForChoice.platform,
|
||||
initializationPresetId: createdSiteForChoice.initializationPresetId,
|
||||
choice,
|
||||
});
|
||||
}
|
||||
// choice === 'later': 不跳转,留在当前页面
|
||||
|
||||
@@ -985,6 +1020,18 @@ export default function Sites() {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 从站点列表直接进入 API Key 批量添加入口。
|
||||
*/
|
||||
const handleOpenSiteApiKey = (site: SiteRow) => {
|
||||
openSiteConnectionFlow({
|
||||
siteId: site.id,
|
||||
platform: site.platform,
|
||||
initializationPresetId: detectSiteInitializationPreset(site.url, site.platform)?.id || null,
|
||||
choice: 'apikey',
|
||||
});
|
||||
};
|
||||
|
||||
const handleTogglePin = async (site: SiteRow) => {
|
||||
const nextPinned = !site.isPinned;
|
||||
setPinningSiteId(site.id);
|
||||
@@ -1938,6 +1985,12 @@ export default function Sites() {
|
||||
>
|
||||
{isExpanded ? '收起' : '详情'}
|
||||
</button>
|
||||
<button
|
||||
onClick={() => handleOpenSiteApiKey(site)}
|
||||
className="btn btn-link btn-link-primary"
|
||||
>
|
||||
添加 Key
|
||||
</button>
|
||||
<button
|
||||
onClick={() => openEdit(site)}
|
||||
className="btn btn-link btn-link-primary"
|
||||
@@ -2256,6 +2309,12 @@ export default function Sites() {
|
||||
</button>
|
||||
</>
|
||||
)}
|
||||
<button
|
||||
onClick={() => handleOpenSiteApiKey(site)}
|
||||
className="btn btn-link btn-link-primary"
|
||||
>
|
||||
添加 Key
|
||||
</button>
|
||||
<button
|
||||
onClick={() => openEdit(site)}
|
||||
className="btn btn-link btn-link-primary"
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
/**
|
||||
* @Author: 橘子
|
||||
* @Project_description: Metapi 站点创建跳转测试
|
||||
* @Description: 代码是我抄的,不会也是真的
|
||||
*/
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import { act, create, type ReactTestRenderer } from 'react-test-renderer';
|
||||
import { MemoryRouter, Route, Routes, useLocation } from 'react-router-dom';
|
||||
@@ -188,6 +193,44 @@ describe('Sites create redirect', () => {
|
||||
expect(rendered).toContain('siteId=24');
|
||||
});
|
||||
|
||||
it('opens API key flow from site list action', async () => {
|
||||
apiMock.getSites.mockResolvedValue([
|
||||
{
|
||||
id: 51,
|
||||
name: 'List Site',
|
||||
url: 'https://list.example.com',
|
||||
platform: 'openai',
|
||||
},
|
||||
]);
|
||||
|
||||
let root!: ReactTestRenderer;
|
||||
try {
|
||||
await act(async () => {
|
||||
root = create(
|
||||
<ToastProvider>
|
||||
<MemoryRouter initialEntries={['/sites']}>
|
||||
<Routes>
|
||||
<Route path="/sites" element={<Sites />} />
|
||||
<Route path="/accounts" element={<LocationProbe />} />
|
||||
</Routes>
|
||||
</MemoryRouter>
|
||||
</ToastProvider>,
|
||||
);
|
||||
});
|
||||
await flushMicrotasks();
|
||||
|
||||
const addKeyButton = findClickableButtonByText(root, '添加 Key');
|
||||
await act(async () => {
|
||||
addKeyButton.props.onClick();
|
||||
});
|
||||
await flushMicrotasks();
|
||||
|
||||
expect(JSON.stringify(root.toJSON())).toContain('/accounts?create=1&siteId=51&segment=apikey');
|
||||
} finally {
|
||||
root?.unmount();
|
||||
}
|
||||
});
|
||||
|
||||
it('lists vendor-specific site types directly in the platform selector', async () => {
|
||||
apiMock.getSites.mockResolvedValue([]);
|
||||
apiMock.addSite.mockResolvedValue({ id: 30, name: 'Demo Site', platform: 'openai' });
|
||||
|
||||
Reference in New Issue
Block a user