AIClient-2-API/static/app/routing-examples.js
hex2077 05df61df74 feat: 添加 TLS Sidecar 支持并重构前端提供商配置系统
- 新增 TLS Sidecar 功能文档,支持绕过 Grok 等服务的 Cloudflare 403 封锁
- 重构前端提供商配置管理,实现动态配置缓存和统一数据源
- 升级配置文件管理界面 UI,优化信息展示和交互体验
- 改进 Claude Kiro 工具调用流式响应,实时推送 content_block 事件
- 修复 Codex 配额重置时间格式问题

主要变更:
- README 文档新增 TLS Sidecar 配置说明和使用指南
- 新增 tutorial-manager.js 模块,动态渲染 OAuth 授权路径
- routing-examples.js 支持动态生成路径路由示例卡片
- upload-config-manager.js 重构列表项布局,支持节点关联信息展示
- config-manager、models-manager、usage-manager 统一使用提供商配置缓存
- i18n 新增多语言翻译键,更新 Gemini 模型版本信息
2026-03-03 20:05:51 +08:00

544 lines
18 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: 'forward-api',
name: 'NewAPI',
paths: {
openai: '/forward-api/v1/chat/completions',
claude: '/forward-api/v1/messages'
},
description: t('dashboard.routing.official'),
badge: t('dashboard.routing.official'),
badgeClass: 'official'
},
{
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: 'gemini-antigravity',
name: 'Gemini Antigravity',
paths: {
openai: '/gemini-antigravity/v1/chat/completions',
claude: '/gemini-antigravity/v1/messages'
},
description: t('dashboard.routing.experimental') || '实验性',
badge: t('dashboard.routing.experimental') || '实验性',
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: 'openai-iflow',
name: 'iFlow OAuth',
paths: {
openai: '/openai-iflow/v1/chat/completions',
claude: '/openai-iflow/v1/messages'
},
description: t('dashboard.routing.oauth'),
badge: t('dashboard.routing.oauth'),
badgeClass: 'oauth'
},
{
provider: 'openai-codex-oauth',
name: 'OpenAI Codex OAuth',
paths: {
openai: '/openai-codex-oauth/v1/chat/completions',
claude: '/openai-codex-oauth/v1/messages'
},
description: t('dashboard.routing.oauth'),
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-3.1-pro-preview",
"messages": [{"role": "user", "content": "${message}"}],
"max_tokens": 1000
}'`;
} else {
curlCommand = `curl http://localhost:3000${path} \\
-H "Content-Type: application/json" \\
-d '{
"model": "gemini-3.1-pro-preview",
"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');
}
}
/**
* 动态渲染路径路由示例
* @param {Array} providerConfigs - 提供商配置列表
*/
function renderRoutingExamples(providerConfigs) {
const container = document.querySelector('.routing-examples-grid');
if (!container) return;
container.innerHTML = '';
// 获取路由端点基础信息
const routes = getAvailableRoutes();
// 图标映射
const iconMap = {
'forward-api': 'fa-share-square',
'gemini-cli-oauth': 'fa-gem',
'gemini-antigravity': 'fa-rocket',
'openai-custom': 'fa-comments',
'claude-custom': 'fa-brain',
'claude-kiro-oauth': 'fa-robot',
'openai-qwen-oauth': 'fa-code',
'openaiResponses-custom': 'fa-comment-alt',
'openai-iflow': 'fa-wind',
'openai-codex-oauth': 'fa-keyboard',
'grok-custom': 'fa-search'
};
// 默认模型映射 (用于 curl 示例)
const modelMap = {
'gemini-cli-oauth': 'gemini-3-flash-preview',
'gemini-antigravity': 'gemini-3-flash-preview',
'claude-custom': 'claude-sonnet-4-6',
'claude-kiro-oauth': 'claude-sonnet-4-6',
'openai-custom': 'gpt-4o',
'openai-qwen-oauth': 'qwen3-coder-plus',
'openai-iflow': 'qwen3-max',
'openai-codex-oauth': 'gpt-5',
'grok-custom': 'grok-3',
'openaiResponses-custom': 'gpt-4o'
};
providerConfigs.forEach(config => {
if (config.visible === false) return;
let routeInfo = routes.find(r => r.provider === config.id);
// 如果没找到,则创建一个默认的
if (!routeInfo) {
routeInfo = {
provider: config.id,
name: config.name,
paths: {
openai: `/${config.id}/v1/chat/completions`,
claude: `/${config.id}/v1/messages`
},
description: t('dashboard.routing.oauth'),
badge: t('dashboard.routing.oauth'),
badgeClass: 'oauth'
};
}
const icon = iconMap[config.id] || 'fa-route';
const defaultModel = modelMap[config.id] || 'default-model';
const hostname = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1' ?
`http://${window.location.host}` :
`${window.location.protocol}//${window.location.host}`;
const card = document.createElement('div');
card.className = 'routing-example-card';
card.dataset.provider = `${config.id}-card`;
card.innerHTML = `
<div class="routing-card-header">
<i class="fas ${icon}"></i>
<h4>${routeInfo.name}</h4>
<span class="provider-badge ${routeInfo.badgeClass}">${routeInfo.badge}</span>
</div>
<div class="routing-card-content">
<div class="protocol-tabs">
<button class="protocol-tab ${config.id === 'openai-codex-oauth' ? '' : 'active'}" data-protocol="openai" data-i18n="dashboard.routing.openai">${t('dashboard.routing.openai')}</button>
<button class="protocol-tab ${config.id === 'openai-codex-oauth' ? 'active' : ''}" data-protocol="claude" data-i18n="dashboard.routing.claude">${t('dashboard.routing.claude')}</button>
</div>
<div class="protocol-content ${config.id === 'openai-codex-oauth' ? '' : 'active'}" data-protocol="openai">
<div class="endpoint-info">
<label data-i18n="dashboard.routing.endpoint">${t('dashboard.routing.endpoint')}</label>
<code class="endpoint-path">${routeInfo.paths.openai}</code>
</div>
<div class="usage-example">
<label data-i18n="dashboard.routing.exampleOpenAI">${t('dashboard.routing.exampleOpenAI')}</label>
<pre><code>curl ${hostname}${routeInfo.paths.openai} \\
-H "Content-Type: application/json" \\
-H "Authorization: Bearer YOUR_API_KEY" \\
-d '{
"model": "${defaultModel}",
"messages": [{"role": "user", "content": "Hello!"}],
"max_tokens": 1000
}'</code></pre>
</div>
</div>
<div class="protocol-content ${config.id === 'openai-codex-oauth' ? 'active' : ''}" data-protocol="claude">
<div class="endpoint-info">
<label data-i18n="dashboard.routing.endpoint">${t('dashboard.routing.endpoint')}</label>
<code class="endpoint-path">${routeInfo.paths.claude}</code>
</div>
<div class="usage-example">
<label data-i18n="dashboard.routing.exampleClaude">${t('dashboard.routing.exampleClaude')}</label>
<pre><code>curl ${hostname}${routeInfo.paths.claude} \\
-H "Content-Type: application/json" \\
-H "X-API-Key: YOUR_API_KEY" \\
-d '{
"model": "${defaultModel}",
"max_tokens": 1000,
"messages": [{"role": "user", "content": "Hello!"}]
}'</code></pre>
</div>
</div>
</div>
`;
container.appendChild(card);
});
// 重新初始化卡片交互
initCardInteractions();
}
export {
initRoutingExamples,
getAvailableRoutes,
highlightProviderRoute,
copyCurlExample,
renderRoutingExamples
};