- 完全重构前端管理界面,采用模块化架构 - 新增独立的 CSS 和 JS 模块(utils, auth, channels, tokens, pricing, api-test, database) - 实现统一的 loading 遮罩效果,所有 API 请求自动显示加载状态 - 添加现代化多选下拉框组件用于频道选择 - 实现模型部署映射的可视化表格编辑 - 修复页面高度自适应问题,管理页面占满全屏 - 优化侧边栏布局,添加 GitHub 链接和版本号 - 令牌管理页面隐藏敏感 key,添加复制功能 - 管理页面切换时自动重置为列表视图并重新加载数据 - 新增 Claude Proxy 提供商支持 - 统一所有 API 调用的错误处理和 loading 状态管理 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
591 lines
35 KiB
HTML
591 lines
35 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-CN">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>AWSL One API - 管理控制台</title>
|
||
<link rel="icon" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 viewBox=%220 0 100 100%22><text y=%22.9em%22 font-size=%2290%22>🤖</text></svg>">
|
||
<link rel="stylesheet" href="/css/styles.css">
|
||
</head>
|
||
<body>
|
||
<div class="app-container">
|
||
<!-- Sidebar -->
|
||
<aside class="sidebar" id="sidebar">
|
||
<div class="sidebar-header">
|
||
<div class="sidebar-brand">
|
||
<span>🤖</span>
|
||
<span>AWSL One API</span>
|
||
</div>
|
||
</div>
|
||
|
||
<nav class="sidebar-nav">
|
||
<div class="nav-section">
|
||
<div class="nav-section-title">主菜单</div>
|
||
<button class="nav-item active" onclick="switchTab('dashboard')" data-tab="dashboard">
|
||
<span class="nav-item-icon">📊</span>
|
||
<span>仪表盘</span>
|
||
</button>
|
||
<button class="nav-item" onclick="switchTab('api-test')" data-tab="api-test">
|
||
<span class="nav-item-icon">🧪</span>
|
||
<span>API 测试</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="nav-section admin-only hidden">
|
||
<div class="nav-section-title">管理</div>
|
||
<button class="nav-item" onclick="switchTab('channels')" data-tab="channels">
|
||
<span class="nav-item-icon">🔗</span>
|
||
<span>频道管理</span>
|
||
</button>
|
||
<button class="nav-item" onclick="switchTab('tokens')" data-tab="tokens">
|
||
<span class="nav-item-icon">🔑</span>
|
||
<span>令牌管理</span>
|
||
</button>
|
||
<button class="nav-item" onclick="switchTab('pricing')" data-tab="pricing">
|
||
<span class="nav-item-icon">💰</span>
|
||
<span>定价管理</span>
|
||
</button>
|
||
</div>
|
||
|
||
<div class="nav-section admin-only hidden">
|
||
<div class="nav-section-title">系统</div>
|
||
<button class="nav-item" onclick="switchTab('database')" data-tab="database">
|
||
<span class="nav-item-icon">🗄️</span>
|
||
<span>数据库</span>
|
||
</button>
|
||
</div>
|
||
</nav>
|
||
|
||
<div class="sidebar-footer">
|
||
<div style="padding: 0.75rem 1rem; border-top: 1px solid hsl(var(--border)); display: flex; justify-content: space-between; align-items: center; font-size: 0.875rem; color: hsl(var(--muted-foreground)); margin-bottom: 0.75rem;">
|
||
<a href="https://github.com/dreamhunter2333/awsl-one-api" target="_blank" style="color: hsl(var(--primary)); text-decoration: none; display: flex; align-items: center; gap: 0.25rem; transition: opacity 0.2s;">
|
||
🔗 GitHub
|
||
</a>
|
||
<span>v1.0.0</span>
|
||
</div>
|
||
<button class="btn btn-outline w-full" onclick="toggleTheme()" style="justify-content: center; margin-bottom: 0.5rem;">
|
||
<span id="themeIcon">🌙</span>
|
||
<span style="margin-left: 0.5rem;">切换主题</span>
|
||
</button>
|
||
<button class="btn btn-outline w-full" onclick="toggleAuthModal()" id="authButton">
|
||
🔑 管理员登录
|
||
</button>
|
||
</div>
|
||
</aside>
|
||
|
||
<!-- Main Content -->
|
||
<main class="main-content">
|
||
<header class="main-header">
|
||
<div class="header-content">
|
||
<h1 class="header-title" id="pageTitle">仪表盘</h1>
|
||
</div>
|
||
</header>
|
||
|
||
<div class="content-wrapper">
|
||
<!-- Dashboard Tab -->
|
||
<div id="dashboard" class="tab-content active">
|
||
<div class="alert alert-info mb-6" id="loginPrompt">
|
||
<div class="flex items-center justify-between">
|
||
<div>
|
||
<strong>💡 提示</strong>
|
||
<p class="text-sm mt-1">要使用管理功能,请先进行身份验证</p>
|
||
</div>
|
||
<button class="btn btn-primary btn-sm" onclick="toggleAuthModal()">
|
||
立即登录
|
||
</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card mb-6">
|
||
<div class="card-header">
|
||
<h2 class="card-title">欢迎使用 AWSL One API</h2>
|
||
<p class="card-description">统一的 AI 模型接口管理服务</p>
|
||
</div>
|
||
<div class="card-content">
|
||
<p class="mb-6">
|
||
AWSL One API 是一个强大的 API 代理和管理平台,为您提供统一的 AI 模型接口管理服务。
|
||
</p>
|
||
|
||
<h3 class="font-semibold mb-4">核心特性</h3>
|
||
<div class="grid grid-cols-2 gap-4">
|
||
<div class="card">
|
||
<div class="card-content">
|
||
<h4 class="font-semibold mb-2">🌐 多提供商支持</h4>
|
||
<p class="text-sm text-muted">支持 OpenAI、Azure OpenAI 和 Claude API,轻松切换不同的 AI 服务。</p>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-content">
|
||
<h4 class="font-semibold mb-2">⚖️ 负载均衡</h4>
|
||
<p class="text-sm text-muted">自动在多个频道间分配请求,确保服务稳定性和高可用性。</p>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-content">
|
||
<h4 class="font-semibold mb-2">📊 用量追踪</h4>
|
||
<p class="text-sm text-muted">实时监控 token 使用情况,精确计算成本,防止超出配额。</p>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-content">
|
||
<h4 class="font-semibold mb-2">🔐 安全管理</h4>
|
||
<p class="text-sm text-muted">基于令牌的访问控制,为不同用户分配独立的 API 密钥。</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title">快速开始</h3>
|
||
<p class="card-description">按照以下步骤开始使用</p>
|
||
</div>
|
||
<div class="card-content">
|
||
<ol style="list-style: decimal; padding-left: 1.5rem; line-height: 2;">
|
||
<li><strong>管理员登录:</strong>使用管理员令牌登录系统</li>
|
||
<li><strong>配置频道:</strong>添加 OpenAI、Azure 或 Claude API 频道</li>
|
||
<li><strong>创建令牌:</strong>为您的应用创建 API 令牌</li>
|
||
<li><strong>开始使用:</strong>使用生成的令牌调用 API</li>
|
||
</ol>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- API Test Tab -->
|
||
<div id="api-test" class="tab-content">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h2 class="card-title">API 测试</h2>
|
||
<p class="card-description">测试您的 API 连接和配置</p>
|
||
</div>
|
||
<div class="card-content">
|
||
<form id="apiTestForm" onsubmit="testAPI(event)">
|
||
<div class="grid grid-cols-2">
|
||
<div class="form-group">
|
||
<label class="label">API 端点</label>
|
||
<select class="input" id="testEndpoint" onchange="updateRequestTemplate()">
|
||
<option value="/v1/chat/completions">/v1/chat/completions</option>
|
||
<option value="/v1/messages">/v1/messages</option>
|
||
<option value="/v1/completions">/v1/completions</option>
|
||
</select>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="label">API 令牌</label>
|
||
<input type="text" class="input" id="testApiToken" placeholder="sk-..." required>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="form-group">
|
||
<label class="label">请求体 (JSON)</label>
|
||
<textarea class="input" id="testRequestBody" rows="12" placeholder='{"model": "gpt-3.5-turbo", "messages": [...]}'></textarea>
|
||
<p class="text-sm text-muted mt-1">直接编辑 JSON 格式的请求体</p>
|
||
</div>
|
||
|
||
<button type="submit" class="btn btn-primary" id="testSubmitBtn">
|
||
🚀 发送请求
|
||
</button>
|
||
</form>
|
||
|
||
<div id="testResult" class="hidden" style="margin-top: 1.5rem;">
|
||
<div class="flex items-center justify-between mb-3">
|
||
<h3 class="font-semibold">响应结果</h3>
|
||
<div class="flex gap-2">
|
||
<span class="badge badge-secondary" id="testStatusBadge">等待中</span>
|
||
<span class="badge badge-outline" id="testTimeBadge">0ms</span>
|
||
</div>
|
||
</div>
|
||
<div class="card">
|
||
<div class="card-content">
|
||
<pre id="testResultContent" style="white-space: pre-wrap; word-wrap: break-word; font-size: 0.875rem; line-height: 1.6; max-height: 500px; overflow-y: auto;"></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Channels Tab -->
|
||
<div id="channels" class="tab-content">
|
||
<!-- Channel List View -->
|
||
<div id="channelListView">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<div class="flex gap-2">
|
||
<button class="btn btn-primary" onclick="showChannelForm()">➕ 添加频道</button>
|
||
<button class="btn btn-secondary" onclick="loadChannels()">🔄 刷新</button>
|
||
</div>
|
||
</div>
|
||
<div id="channelsList" class="item-list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Channel Form View -->
|
||
<div id="channelFormView" style="display: none;">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title" id="channelFormTitle">添加频道</h3>
|
||
<div class="flex gap-2">
|
||
<button class="btn btn-secondary" onclick="toggleChannelEditMode()" id="channelModeToggle">📝 切换到JSON模式</button>
|
||
<button class="btn btn-primary" onclick="saveChannel()">💾 保存频道</button>
|
||
<button class="btn btn-secondary" onclick="showChannelList()">← 返回列表</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card-content">
|
||
<!-- Common Key Field -->
|
||
<div class="form-group">
|
||
<label for="channelKey">频道标识</label>
|
||
<input type="text" id="channelKey" class="input" placeholder="例如: azure-openai-1">
|
||
</div>
|
||
|
||
<!-- Form Mode -->
|
||
<div id="channelFormMode">
|
||
<div class="form-group">
|
||
<label for="channelName">频道名称</label>
|
||
<input type="text" id="channelName" class="input" placeholder="例如: Azure OpenAI">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="channelType">频道类型</label>
|
||
<select id="channelType" class="input">
|
||
<option value="azure-openai">Azure OpenAI</option>
|
||
<option value="openai">OpenAI</option>
|
||
<option value="claude">Claude</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="channelEndpoint">API 端点</label>
|
||
<input type="text" id="channelEndpoint" class="input" placeholder="https://your-resource.openai.azure.com/">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="channelApiKey">API 密钥</label>
|
||
<input type="password" id="channelApiKey" class="input" placeholder="your-api-key">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="channelApiVersion">API 版本(可选)</label>
|
||
<input type="text" id="channelApiVersion" class="input" placeholder="例如: 2024-02-01">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="channelMapperTable">模型部署映射</label>
|
||
<div style="margin-bottom: 0.5rem;">
|
||
<button type="button" class="btn btn-secondary btn-sm" onclick="window.addMapperRow()">➕ 添加映射</button>
|
||
</div>
|
||
<div class="table-container">
|
||
<table class="table">
|
||
<thead>
|
||
<tr>
|
||
<th>请求模型名</th>
|
||
<th>部署模型名</th>
|
||
<th style="width: 100px; text-align: center;">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="channelMapperTable">
|
||
<tr>
|
||
<td colspan="3" class="text-center text-muted">暂无映射,点击上方按钮添加</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
<p class="text-sm text-muted mt-1">配置模型名称映射关系(主要用于Azure OpenAI)。留空表示不进行映射</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- JSON Mode -->
|
||
<div id="channelJsonMode" style="display: none;">
|
||
<div class="form-group">
|
||
<label for="channelValueJson">频道配置 (JSON)</label>
|
||
<textarea id="channelValueJson" class="input" rows="15" placeholder='{"name": "Azure OpenAI", "type": "azure-openai", "endpoint": "https://xxx.openai.azure.com/", "api_key": "your-api-key", "api_version": "2024-02-01", "deployment_mapper": {"gpt-35-turbo": "gpt-35-turbo", "gpt-4": "gpt-4"}}'></textarea>
|
||
<p class="text-sm text-muted mt-1">直接编辑完整的频道配置JSON</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Tokens Tab -->
|
||
<div id="tokens" class="tab-content">
|
||
<!-- Token List View -->
|
||
<div id="tokenListView">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<div class="flex gap-2">
|
||
<button class="btn btn-primary" onclick="showTokenForm()">➕ 添加令牌</button>
|
||
<button class="btn btn-secondary" onclick="loadTokens()">🔄 刷新</button>
|
||
</div>
|
||
</div>
|
||
<div id="tokensList" class="item-list"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Token Form View -->
|
||
<div id="tokenFormView" style="display: none;">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<h3 class="card-title" id="tokenFormTitle">添加令牌</h3>
|
||
<div class="flex gap-2">
|
||
<button class="btn btn-secondary" onclick="toggleTokenEditMode()" id="tokenModeToggle">📝 切换到JSON模式</button>
|
||
<button class="btn btn-primary" onclick="saveToken()">💾 保存令牌</button>
|
||
<button class="btn btn-secondary" onclick="showTokenList()">← 返回列表</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card-content">
|
||
<!-- Common Key Field -->
|
||
<div class="form-group">
|
||
<label for="tokenKey">令牌标识</label>
|
||
<div class="flex gap-2">
|
||
<input type="text" id="tokenKey" class="input" placeholder="输入令牌标识或点击生成">
|
||
<button type="button" id="generateTokenBtn" class="btn btn-secondary" onclick="generateTokenKey()">🎲 生成</button>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Form Mode -->
|
||
<div id="tokenFormMode">
|
||
<div class="form-group">
|
||
<label for="tokenName">令牌名称</label>
|
||
<input type="text" id="tokenName" class="input" placeholder="例如: 用户令牌1">
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="channelKeysSelect">允许访问的频道</label>
|
||
<div class="multi-select-wrapper">
|
||
<div class="multi-select-trigger" id="channelSelectTrigger" onclick="window.toggleChannelDropdown()">
|
||
<span class="multi-select-value" id="channelSelectValue">请选择频道...</span>
|
||
<span class="multi-select-arrow">▼</span>
|
||
</div>
|
||
<div class="multi-select-dropdown hidden" id="channelSelectDropdown">
|
||
<div class="multi-select-search">
|
||
<input type="text" class="input" placeholder="搜索频道..." id="channelSearchInput" oninput="window.filterChannels()">
|
||
</div>
|
||
<div class="multi-select-options" id="channelOptionsContainer">
|
||
<div class="multi-select-loading">加载中...</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
<p class="text-sm text-muted mt-1">不选表示允许访问所有频道</p>
|
||
</div>
|
||
<div class="form-group">
|
||
<label for="totalQuotaInput">总配额</label>
|
||
<input type="number" id="totalQuotaInput" class="input" placeholder="1000000" min="0" step="1">
|
||
<div style="margin-top: 10px;">
|
||
<small class="text-sm text-muted" style="display: block; margin-bottom: 8px;">快速选择:</small>
|
||
<div style="display: flex; gap: 5px; flex-wrap: wrap;">
|
||
<button type="button" class="btn btn-secondary btn-sm" onclick="setQuotaValue(1000000)">$1</button>
|
||
<button type="button" class="btn btn-secondary btn-sm" onclick="setQuotaValue(5000000)">$5</button>
|
||
<button type="button" class="btn btn-secondary btn-sm" onclick="setQuotaValue(10000000)">$10</button>
|
||
<button type="button" class="btn btn-secondary btn-sm" onclick="setQuotaValue(20000000)">$20</button>
|
||
<button type="button" class="btn btn-secondary btn-sm" onclick="setQuotaValue(50000000)">$50</button>
|
||
</div>
|
||
</div>
|
||
<p class="text-sm text-muted mt-2">设置令牌的总使用配额 (基础单位:1百万token = $1.00,例如:输入1000000表示1美元的配额)</p>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- JSON Mode -->
|
||
<div id="tokenJsonMode" style="display: none;">
|
||
<div class="form-group">
|
||
<label for="tokenValueJson">令牌配置 (JSON)</label>
|
||
<textarea id="tokenValueJson" class="input" rows="10" placeholder='{"name": "用户令牌1", "channel_keys": ["azure-openai-1"], "total_quota": 1000000}'></textarea>
|
||
<p class="text-sm text-muted mt-1">直接编辑完整的令牌配置JSON</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Pricing Tab -->
|
||
<div id="pricing" class="tab-content">
|
||
<div class="card">
|
||
<div class="card-header">
|
||
<div class="flex gap-2">
|
||
<button class="btn btn-secondary" onclick="togglePricingEditMode()" id="pricingModeToggle">📝 切换到JSON模式</button>
|
||
<button class="btn btn-secondary" onclick="addPricingModel()" id="addPricingModelBtn">➕ 添加模型</button>
|
||
<button class="btn btn-primary" onclick="savePricing(event)">💾 更新定价</button>
|
||
<button class="btn btn-secondary" onclick="loadPricing()">🔄 刷新</button>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="card-content">
|
||
<div style="background: hsl(var(--accent)); padding: 15px; border-radius: 6px; margin-bottom: 20px; border-left: 4px solid hsl(var(--primary));">
|
||
<h4 style="margin: 0 0 10px 0; color: hsl(var(--foreground)); font-size: 14px; font-weight: 600;">💡 定价说明</h4>
|
||
<p style="margin: 0; font-size: 13px; color: hsl(var(--foreground)); line-height: 1.5;">
|
||
<strong>基础配额单位:1百万token = $1.00</strong> | 定价配置为不同模型相对于基础单位的倍率 | 例如:gpt-4的input倍率为30,表示1百万token消耗30美元配额
|
||
</p>
|
||
</div>
|
||
|
||
<!-- Table Mode -->
|
||
<div id="pricingTableMode">
|
||
<div class="table-container">
|
||
<table class="table">
|
||
<thead>
|
||
<tr>
|
||
<th>模型名称</th>
|
||
<th>输入倍率</th>
|
||
<th>输出倍率</th>
|
||
<th style="width: 100px; text-align: center;">操作</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody id="pricingTableBody">
|
||
<tr>
|
||
<td colspan="4" class="text-center text-muted">加载中...</td>
|
||
</tr>
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- JSON Mode -->
|
||
<div id="pricingJsonMode" style="display: none;">
|
||
<div class="form-group">
|
||
<label for="pricingConfigJson">定价配置 (JSON)</label>
|
||
<textarea class="input" id="pricingConfigJson" rows="15" placeholder='{"gpt-3.5-turbo": {"input": 0.001, "output": 0.002}, "gpt-4": {"input": 0.03, "output": 0.06}}'></textarea>
|
||
<p class="text-sm text-muted mt-2">配置格式:模型名称 -> {input: 输入倍率, output: 输出倍率}。倍率基于1百万token=$1.00计算</p>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Database Tab -->
|
||
<div id="database" class="tab-content">
|
||
<div class="card">
|
||
<div class="card-content">
|
||
<div class="alert alert-warning mb-4">
|
||
<strong>⚠️ 警告:</strong>
|
||
<p class="text-sm mt-1">初始化数据库将创建必要的表结构。如果表已存在,操作将被忽略。</p>
|
||
</div>
|
||
|
||
<button class="btn btn-primary" onclick="initDatabase()">
|
||
🗄️ 初始化数据库
|
||
</button>
|
||
|
||
<div id="dbResult" class="hidden" style="margin-top: 1.5rem;">
|
||
<div class="card">
|
||
<div class="card-content">
|
||
<pre id="dbResultContent" style="white-space: pre-wrap; font-size: 0.875rem;"></pre>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</main>
|
||
</div>
|
||
|
||
<!-- Auth Modal -->
|
||
<div class="modal-overlay" id="authModal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2 class="modal-title">管理员身份验证</h2>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="authForm" onsubmit="authenticate(event)">
|
||
<div class="form-group">
|
||
<label class="label">管理员令牌</label>
|
||
<input type="password" class="input" id="adminToken" placeholder="请输入管理员令牌" required>
|
||
</div>
|
||
<div id="authError" class="alert alert-error hidden">
|
||
<strong>❌ 错误:</strong>
|
||
<p class="text-sm mt-1" id="authErrorMessage"></p>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-outline" onclick="toggleAuthModal()">取消</button>
|
||
<button class="btn btn-primary" onclick="authenticate(event)">登录</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add Channel Modal -->
|
||
<div class="modal-overlay" id="addChannelModal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2 class="modal-title">添加频道</h2>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="addChannelForm">
|
||
<div class="form-group">
|
||
<label class="label">频道 Key</label>
|
||
<input type="text" class="input" id="channelKey" placeholder="channel-1" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="label">频道名称</label>
|
||
<input type="text" class="input" id="channelName" placeholder="My Channel" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="label">类型</label>
|
||
<select class="input" id="channelType" required>
|
||
<option value="openai">OpenAI</option>
|
||
<option value="azure-openai">Azure OpenAI</option>
|
||
<option value="claude">Claude</option>
|
||
</select>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="label">API 端点</label>
|
||
<input type="url" class="input" id="channelEndpoint" placeholder="https://api.openai.com" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="label">API 密钥</label>
|
||
<input type="password" class="input" id="channelApiKey" placeholder="sk-..." required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="label">模型映射 (JSON)</label>
|
||
<textarea class="input" id="channelMapper" rows="4" placeholder='{"gpt-3.5-turbo": "gpt-3.5-turbo"}' required></textarea>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-outline" onclick="toggleModal('addChannelModal')">取消</button>
|
||
<button class="btn btn-primary" onclick="addChannel(event)">添加</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Add Token Modal -->
|
||
<div class="modal-overlay" id="addTokenModal">
|
||
<div class="modal-content">
|
||
<div class="modal-header">
|
||
<h2 class="modal-title">创建令牌</h2>
|
||
</div>
|
||
<div class="modal-body">
|
||
<form id="addTokenForm">
|
||
<div class="form-group">
|
||
<label class="label">令牌 Key</label>
|
||
<input type="text" class="input" id="tokenKey" placeholder="sk-..." required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="label">令牌名称</label>
|
||
<input type="text" class="input" id="tokenName" placeholder="My Token" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="label">总配额</label>
|
||
<input type="number" class="input" id="tokenQuota" placeholder="100" step="0.01" required>
|
||
</div>
|
||
<div class="form-group">
|
||
<label class="label">允许的频道 (留空允许所有)</label>
|
||
<input type="text" class="input" id="tokenChannels" placeholder="channel-1,channel-2">
|
||
<p class="text-sm text-muted mt-1">多个频道用逗号分隔</p>
|
||
</div>
|
||
</form>
|
||
</div>
|
||
<div class="modal-footer">
|
||
<button class="btn btn-outline" onclick="toggleModal('addTokenModal')">取消</button>
|
||
<button class="btn btn-primary" onclick="addToken(event)">创建</button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- Loading Overlay -->
|
||
<div id="loadingOverlay">
|
||
<div class="loading-spinner"></div>
|
||
<div class="loading-text">加载中...</div>
|
||
</div>
|
||
|
||
<script type="module" src="/js/main.js"></script>
|
||
</body>
|
||
</html>
|