AIClient-2-API/static/app/routing-examples.js
hex2077 68719879c5 feat(architecture): 重构适配器注册机制并引入并发控制系统
建立可扩展的提供商适配器注册表,实现动态服务发现与插槽管理:

架构改进:
- 采用 Map 注册表替代 switch-case 硬编码,支持热插拔适配器
- 实现 acquireSlot/releaseSlot 机制,精确追踪活跃请求与等待队列
- 新增节点评分算法,综合考量并发数、队列长度、健康状态

核心能力:
- 支持并发限制与队列等待,避免单节点过载 (concurrencyLimit/queueLimit)
- 实现 Fallback 链式调用,429 错误自动切换备用凭证
- 添加请求级 IP 追踪,日志格式优化为 `clientIp:requestId`

配套更新:
- 管理界面新增并发/队列配置字段与 Grok 逆向提供商选项
- 用量查询服务扩展 Grok 支持,同步剩余查询次数 (固定总量 80)
- 新增并发测试脚本 (tests/concurrent-test.js),支持自定义并发数与 RPM 限制

配置项:
- GROK_COOKIE_TOKEN, GROK_CF_CLEARANCE, GROK_USER_AGENT, GROK_BASE_URL
2026-02-26 18:19:38 +08:00

370 lines
No EOL
12 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 路径路由示例功能模块
import { showToast } from './utils.js';
import { t } from './i18n.js';
/**
* 初始化路径路由示例功能
*/
function initRoutingExamples() {
// 延迟初始化确保所有DOM都加载完成
setTimeout(() => {
initProtocolTabs();
initCopyButtons();
initCardInteractions();
}, 100);
}
/**
* 初始化协议标签切换功能
*/
function initProtocolTabs() {
// 使用事件委托方式绑定点击事件
document.addEventListener('click', function(e) {
// 检查点击的是不是协议标签或者其子元素
const tab = e.target.classList.contains('protocol-tab') ? e.target : e.target.closest('.protocol-tab');
if (tab) {
e.preventDefault();
e.stopPropagation();
const targetProtocol = tab.dataset.protocol;
const card = tab.closest('.routing-example-card');
if (!card) {
return;
}
// 移除当前卡片中所有标签和内容的活动状态
const cardTabs = card.querySelectorAll('.protocol-tab');
const cardContents = card.querySelectorAll('.protocol-content');
cardTabs.forEach(t => t.classList.remove('active'));
cardContents.forEach(c => c.classList.remove('active'));
// 为当前标签和对应内容添加活动状态
tab.classList.add('active');
// 使用更精确的选择器来查找对应的内容
const targetContent = card.querySelector(`.protocol-content[data-protocol="${targetProtocol}"]`);
if (targetContent) {
targetContent.classList.add('active');
}
}
});
}
/**
* 初始化复制按钮功能
*/
function initCopyButtons() {
document.addEventListener('click', async function(e) {
if (e.target.closest('.copy-btn')) {
e.stopPropagation();
const button = e.target.closest('.copy-btn');
const path = button.dataset.path;
if (!path) return;
try {
await navigator.clipboard.writeText(path);
showToast(t('common.success'), `${t('common.success')}: ${path}`, 'success');
// 临时更改按钮图标
const icon = button.querySelector('i');
if (icon) {
const originalClass = icon.className;
icon.className = 'fas fa-check';
button.style.color = 'var(--success-color)';
setTimeout(() => {
icon.className = originalClass;
button.style.color = '';
}, 2000);
}
} catch (error) {
console.error('Failed to copy to clipboard:', error);
showToast(t('common.error'), t('common.error'), 'error');
}
}
});
}
/**
* 初始化卡片交互功能
*/
function initCardInteractions() {
const routingCards = document.querySelectorAll('.routing-example-card');
routingCards.forEach(card => {
// 添加悬停效果
card.addEventListener('mouseenter', () => {
card.style.transform = 'translateY(-4px)';
card.style.boxShadow = 'var(--shadow-lg)';
});
card.addEventListener('mouseleave', () => {
card.style.transform = '';
card.style.boxShadow = '';
});
});
}
/**
* 获取所有可用的路由端点
* @returns {Array} 路由端点数组
*/
function getAvailableRoutes() {
return [
{
provider: 'claude-custom',
name: 'Claude Custom',
paths: {
openai: '/claude-custom/v1/chat/completions',
claude: '/claude-custom/v1/messages'
},
description: t('dashboard.routing.official'),
badge: t('dashboard.routing.official'),
badgeClass: 'official'
},
{
provider: 'claude-kiro-oauth',
name: 'Claude Kiro OAuth',
paths: {
openai: '/claude-kiro-oauth/v1/chat/completions',
claude: '/claude-kiro-oauth/v1/messages'
},
description: t('dashboard.routing.free'),
badge: t('dashboard.routing.free'),
badgeClass: 'oauth'
},
{
provider: 'openai-custom',
name: 'OpenAI Custom',
paths: {
openai: '/openai-custom/v1/chat/completions',
claude: '/openai-custom/v1/messages'
},
description: t('dashboard.routing.official'),
badge: t('dashboard.routing.official'),
badgeClass: 'official'
},
{
provider: 'gemini-cli-oauth',
name: 'Gemini CLI OAuth',
paths: {
openai: '/gemini-cli-oauth/v1/chat/completions',
claude: '/gemini-cli-oauth/v1/messages'
},
description: t('dashboard.routing.oauth'),
badge: t('dashboard.routing.oauth'),
badgeClass: 'oauth'
},
{
provider: 'openai-qwen-oauth',
name: 'Qwen OAuth',
paths: {
openai: '/openai-qwen-oauth/v1/chat/completions',
claude: '/openai-qwen-oauth/v1/messages'
},
description: 'Qwen Code Plus',
badge: t('dashboard.routing.oauth'),
badgeClass: 'oauth'
},
{
provider: 'openaiResponses-custom',
name: 'OpenAI Responses',
paths: {
openai: '/openaiResponses-custom/v1/responses',
claude: '/openaiResponses-custom/v1/messages'
},
description: '结构化对话API',
badge: 'Responses',
badgeClass: 'responses'
},
{
provider: 'grok-custom',
name: 'Grok Reverse',
paths: {
openai: '/grok-custom/v1/chat/completions',
claude: '/grok-custom/v1/messages'
},
description: t('dashboard.routing.free'),
badge: t('dashboard.routing.free'),
badgeClass: 'oauth'
}
];
}
/**
* 高亮显示特定提供商路由
* @param {string} provider - 提供商标识
*/
function highlightProviderRoute(provider) {
const card = document.querySelector(`[data-provider="${provider}"]`);
if (card) {
card.scrollIntoView({ behavior: 'smooth', block: 'center' });
card.style.borderColor = 'var(--success-color)';
card.style.boxShadow = '0 0 0 3px rgba(16, 185, 129, 0.1)';
setTimeout(() => {
card.style.borderColor = '';
card.style.boxShadow = '';
}, 3000);
showToast(t('common.success'), t('common.success') + `: ${provider}`, 'success');
}
}
/**
* 复制curl命令示例
* @param {string} provider - 提供商标识
* @param {Object} options - 选项参数
*/
async function copyCurlExample(provider, options = {}) {
const routes = getAvailableRoutes();
const route = routes.find(r => r.provider === provider);
if (!route) {
showToast(t('common.error'), t('common.error'), 'error');
return;
}
const { protocol = 'openai', model = 'default-model', message = 'Hello!' } = options;
const path = route.paths[protocol];
if (!path) {
showToast(t('common.error'), t('common.error'), 'error');
return;
}
let curlCommand = '';
// 根据不同提供商和协议生成对应的curl命令
switch (provider) {
case 'claude-custom':
case 'claude-kiro-oauth':
if (protocol === 'openai') {
curlCommand = `curl http://localhost:3000${path} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-d '{
"model": "${model}",
"messages": [{"role": "user", "content": "${message}"}],
"max_tokens": 1000
}'`;
} else {
curlCommand = `curl http://localhost:3000${path} \\
-H "Content-Type: application/json" \\
-d '{
"model": "${model}",
"max_tokens": 1000,
"messages": [{"role": "user", "content": "${message}"}]
}'`;
}
break;
case 'openai-custom':
case 'openai-qwen-oauth':
if (protocol === 'openai') {
curlCommand = `curl http://localhost:3000${path} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-d '{
"model": "${model}",
"messages": [{"role": "user", "content": "${message}"}],
"max_tokens": 1000
}'`;
} else {
curlCommand = `curl http://localhost:3000${path} \\
-H "Content-Type: application/json" \\
-H "X-API-Key: YOUR_API_KEY" \\
-d '{
"model": "${model}",
"max_tokens": 1000,
"messages": [{"role": "user", "content": "${message}"}]
}'`;
}
break;
case 'gemini-cli-oauth':
if (protocol === 'openai') {
curlCommand = `curl http://localhost:3000${path} \\
-H "Content-Type: application/json" \\
-d '{
"model": "gemini-2.0-flash-exp",
"messages": [{"role": "user", "content": "${message}"}],
"max_tokens": 1000
}'`;
} else {
curlCommand = `curl http://localhost:3000${path} \\
-H "Content-Type: application/json" \\
-d '{
"model": "gemini-2.0-flash-exp",
"max_tokens": 1000,
"messages": [{"role": "user", "content": "${message}"}]
}'`;
}
break;
case 'openaiResponses-custom':
if (protocol === 'openai') {
curlCommand = `curl http://localhost:3000${path} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-d '{
"model": "${model}",
"input": "${message}",
"max_output_tokens": 1000
}'`;
} else {
curlCommand = `curl http://localhost:3000${path} \\
-H "Content-Type: application/json" \\
-H "X-API-Key: YOUR_API_KEY" \\
-d '{
"model": "${model}",
"max_tokens": 1000,
"messages": [{"role": "user", "content": "${message}"}]
}'`;
}
break;
case 'grok-custom':
if (protocol === 'openai') {
curlCommand = `curl http://localhost:3000${path} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-d '{
"model": "grok-3",
"messages": [{"role": "user", "content": "${message}"}],
"stream": true
}'`;
} else {
curlCommand = `curl http://localhost:3000${path} \\
-H "Content-Type: application/json" \\
-H "X-API-Key: YOUR_API_KEY" \\
-d '{
"model": "grok-3",
"max_tokens": 1000,
"messages": [{"role": "user", "content": "${message}"}]
}'`;
}
break;
}
try {
await navigator.clipboard.writeText(curlCommand);
showToast(t('common.success'), t('oauth.success.msg'), 'success');
} catch (error) {
console.error('Failed to copy curl command:', error);
showToast(t('common.error'), t('common.error'), 'error');
}
}
export {
initRoutingExamples,
getAvailableRoutes,
highlightProviderRoute,
copyCurlExample
};