mirror of
https://github.com/router-for-me/Cli-Proxy-API-Management-Center.git
synced 2026-02-03 03:10:50 +08:00
0.0.5
为Cli Proxy API主程序兼容做准备
This commit is contained in:
274
app.js
274
app.js
@@ -2,7 +2,8 @@
|
|||||||
class CLIProxyManager {
|
class CLIProxyManager {
|
||||||
constructor() {
|
constructor() {
|
||||||
// 仅保存基础地址(不含 /v0/management),请求时自动补齐
|
// 仅保存基础地址(不含 /v0/management),请求时自动补齐
|
||||||
this.apiBase = 'http://localhost:8317';
|
const detectedBase = this.detectApiBaseFromLocation();
|
||||||
|
this.apiBase = detectedBase;
|
||||||
this.apiUrl = this.computeApiUrl(this.apiBase);
|
this.apiUrl = this.computeApiUrl(this.apiBase);
|
||||||
this.managementKey = '';
|
this.managementKey = '';
|
||||||
this.isConnected = false;
|
this.isConnected = false;
|
||||||
@@ -107,6 +108,7 @@ class CLIProxyManager {
|
|||||||
this.setupLanguageSwitcher();
|
this.setupLanguageSwitcher();
|
||||||
this.setupThemeSwitcher();
|
this.setupThemeSwitcher();
|
||||||
// loadSettings 将在登录成功后调用
|
// loadSettings 将在登录成功后调用
|
||||||
|
this.updateLoginConnectionInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查登录状态
|
// 检查登录状态
|
||||||
@@ -185,6 +187,7 @@ class CLIProxyManager {
|
|||||||
document.getElementById('login-page').style.display = 'flex';
|
document.getElementById('login-page').style.display = 'flex';
|
||||||
document.getElementById('main-page').style.display = 'none';
|
document.getElementById('main-page').style.display = 'none';
|
||||||
this.isLoggedIn = false;
|
this.isLoggedIn = false;
|
||||||
|
this.updateLoginConnectionInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 显示主页面
|
// 显示主页面
|
||||||
@@ -236,55 +239,40 @@ class CLIProxyManager {
|
|||||||
|
|
||||||
// 处理登录表单提交
|
// 处理登录表单提交
|
||||||
async handleLogin() {
|
async handleLogin() {
|
||||||
// 获取当前活动的选项卡
|
const apiBaseInput = document.getElementById('login-api-base');
|
||||||
const activeTab = document.querySelector('.tab-button.active').getAttribute('data-tab');
|
const managementKeyInput = document.getElementById('login-management-key');
|
||||||
|
const managementKey = managementKeyInput ? managementKeyInput.value.trim() : '';
|
||||||
|
|
||||||
let apiUrl, managementKey;
|
if (!managementKey) {
|
||||||
|
this.showLoginError(i18n.t('login.error_required'));
|
||||||
if (activeTab === 'local') {
|
return;
|
||||||
// 本地连接:从端口号构建URL
|
|
||||||
const port = document.getElementById('local-port').value.trim();
|
|
||||||
managementKey = document.getElementById('local-management-key').value.trim();
|
|
||||||
|
|
||||||
if (!port || !managementKey) {
|
|
||||||
this.showLoginError(i18n.t('login.error_required'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
apiUrl = `http://localhost:${port}`;
|
|
||||||
} else {
|
|
||||||
// 远程连接:使用完整URL
|
|
||||||
apiUrl = document.getElementById('remote-api-url').value.trim();
|
|
||||||
managementKey = document.getElementById('remote-management-key').value.trim();
|
|
||||||
|
|
||||||
if (!apiUrl || !managementKey) {
|
|
||||||
this.showLoginError(i18n.t('login.error_required'));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const proxyUrl = document.getElementById('login-proxy-url').value.trim();
|
if (apiBaseInput && apiBaseInput.value.trim()) {
|
||||||
|
this.setApiBase(apiBaseInput.value.trim());
|
||||||
|
}
|
||||||
|
|
||||||
const submitBtn = document.getElementById('login-submit');
|
const submitBtn = document.getElementById('login-submit');
|
||||||
const originalText = submitBtn.innerHTML;
|
const originalText = submitBtn ? submitBtn.innerHTML : '';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
submitBtn.innerHTML = `<div class="loading"></div> ${i18n.t('login.submitting')}`;
|
if (submitBtn) {
|
||||||
submitBtn.disabled = true;
|
submitBtn.innerHTML = `<div class="loading"></div> ${i18n.t('login.submitting')}`;
|
||||||
|
submitBtn.disabled = true;
|
||||||
|
}
|
||||||
this.hideLoginError();
|
this.hideLoginError();
|
||||||
|
|
||||||
// 如果设置了代理,先保存代理设置
|
this.managementKey = managementKey;
|
||||||
if (proxyUrl) {
|
localStorage.setItem('managementKey', this.managementKey);
|
||||||
localStorage.setItem('proxyUrl', proxyUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
await this.login(apiUrl, managementKey);
|
|
||||||
|
|
||||||
|
await this.login(this.apiBase, this.managementKey);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.showLoginError(`${i18n.t('login.error_title')}: ${error.message}`);
|
this.showLoginError(`${i18n.t('login.error_title')}: ${error.message}`);
|
||||||
} finally {
|
} finally {
|
||||||
submitBtn.innerHTML = originalText;
|
if (submitBtn) {
|
||||||
submitBtn.disabled = false;
|
submitBtn.innerHTML = originalText;
|
||||||
|
submitBtn.disabled = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -355,67 +343,30 @@ class CLIProxyManager {
|
|||||||
loadLoginSettings() {
|
loadLoginSettings() {
|
||||||
const savedBase = localStorage.getItem('apiBase');
|
const savedBase = localStorage.getItem('apiBase');
|
||||||
const savedKey = localStorage.getItem('managementKey');
|
const savedKey = localStorage.getItem('managementKey');
|
||||||
const savedProxy = localStorage.getItem('proxyUrl');
|
const loginKeyInput = document.getElementById('login-management-key');
|
||||||
|
const apiBaseInput = document.getElementById('login-api-base');
|
||||||
|
|
||||||
// 检查元素是否存在(确保在登录页面)
|
|
||||||
const localPortInput = document.getElementById('local-port');
|
|
||||||
const remoteApiInput = document.getElementById('remote-api-url');
|
|
||||||
const localKeyInput = document.getElementById('local-management-key');
|
|
||||||
const remoteKeyInput = document.getElementById('remote-management-key');
|
|
||||||
const proxyInput = document.getElementById('login-proxy-url');
|
|
||||||
|
|
||||||
// 设置本地端口和远程API地址
|
|
||||||
if (savedBase) {
|
if (savedBase) {
|
||||||
if (savedBase.includes('localhost')) {
|
this.setApiBase(savedBase);
|
||||||
// 从本地URL中提取端口号
|
} else {
|
||||||
const match = savedBase.match(/localhost:(\d+)/);
|
this.setApiBase(this.detectApiBaseFromLocation());
|
||||||
if (match && localPortInput) {
|
|
||||||
localPortInput.value = match[1];
|
|
||||||
}
|
|
||||||
} else if (remoteApiInput) {
|
|
||||||
remoteApiInput.value = savedBase;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置密钥
|
if (apiBaseInput) {
|
||||||
if (localKeyInput && savedKey) {
|
apiBaseInput.value = this.apiBase || '';
|
||||||
localKeyInput.value = savedKey;
|
|
||||||
}
|
|
||||||
if (remoteKeyInput && savedKey) {
|
|
||||||
remoteKeyInput.value = savedKey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置代理
|
if (loginKeyInput && savedKey) {
|
||||||
if (proxyInput && savedProxy) {
|
loginKeyInput.value = savedKey;
|
||||||
proxyInput.value = savedProxy;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置实时保存监听器
|
|
||||||
this.setupLoginAutoSave();
|
this.setupLoginAutoSave();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置登录页面自动保存
|
|
||||||
setupLoginAutoSave() {
|
setupLoginAutoSave() {
|
||||||
const localPortInput = document.getElementById('local-port');
|
const loginKeyInput = document.getElementById('login-management-key');
|
||||||
const remoteApiInput = document.getElementById('remote-api-url');
|
const apiBaseInput = document.getElementById('login-api-base');
|
||||||
const localKeyInput = document.getElementById('local-management-key');
|
const resetButton = document.getElementById('login-reset-api-base');
|
||||||
const remoteKeyInput = document.getElementById('remote-management-key');
|
|
||||||
const proxyInput = document.getElementById('login-proxy-url');
|
|
||||||
|
|
||||||
const saveLocalBase = (port) => {
|
|
||||||
if (port.trim()) {
|
|
||||||
const apiUrl = `http://localhost:${port}`;
|
|
||||||
this.setApiBase(apiUrl);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const saveLocalBaseDebounced = this.debounce(saveLocalBase, 500);
|
|
||||||
|
|
||||||
const saveRemoteBase = (val) => {
|
|
||||||
if (val.trim()) {
|
|
||||||
this.setApiBase(val);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const saveRemoteBaseDebounced = this.debounce(saveRemoteBase, 500);
|
|
||||||
|
|
||||||
const saveKey = (val) => {
|
const saveKey = (val) => {
|
||||||
if (val.trim()) {
|
if (val.trim()) {
|
||||||
@@ -425,41 +376,32 @@ class CLIProxyManager {
|
|||||||
};
|
};
|
||||||
const saveKeyDebounced = this.debounce(saveKey, 500);
|
const saveKeyDebounced = this.debounce(saveKey, 500);
|
||||||
|
|
||||||
const saveProxy = (val) => {
|
if (loginKeyInput) {
|
||||||
if (val.trim()) {
|
loginKeyInput.addEventListener('change', (e) => saveKey(e.target.value));
|
||||||
localStorage.setItem('proxyUrl', val);
|
loginKeyInput.addEventListener('input', (e) => saveKeyDebounced(e.target.value));
|
||||||
}
|
|
||||||
};
|
|
||||||
const saveProxyDebounced = this.debounce(saveProxy, 500);
|
|
||||||
|
|
||||||
// 绑定本地端口输入框
|
|
||||||
if (localPortInput) {
|
|
||||||
localPortInput.addEventListener('change', (e) => saveLocalBase(e.target.value));
|
|
||||||
localPortInput.addEventListener('input', (e) => saveLocalBaseDebounced(e.target.value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绑定远程API输入框
|
if (apiBaseInput) {
|
||||||
if (remoteApiInput) {
|
const persistBase = (val) => {
|
||||||
remoteApiInput.addEventListener('change', (e) => saveRemoteBase(e.target.value));
|
const normalized = this.normalizeBase(val);
|
||||||
remoteApiInput.addEventListener('input', (e) => saveRemoteBaseDebounced(e.target.value));
|
if (normalized) {
|
||||||
|
this.setApiBase(normalized);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const persistBaseDebounced = this.debounce(persistBase, 500);
|
||||||
|
|
||||||
|
apiBaseInput.addEventListener('change', (e) => persistBase(e.target.value));
|
||||||
|
apiBaseInput.addEventListener('input', (e) => persistBaseDebounced(e.target.value));
|
||||||
}
|
}
|
||||||
|
|
||||||
// 绑定本地密钥输入框
|
if (resetButton) {
|
||||||
if (localKeyInput) {
|
resetButton.addEventListener('click', () => {
|
||||||
localKeyInput.addEventListener('change', (e) => saveKey(e.target.value));
|
const detected = this.detectApiBaseFromLocation();
|
||||||
localKeyInput.addEventListener('input', (e) => saveKeyDebounced(e.target.value));
|
this.setApiBase(detected);
|
||||||
}
|
if (apiBaseInput) {
|
||||||
|
apiBaseInput.value = detected;
|
||||||
// 绑定远程密钥输入框
|
}
|
||||||
if (remoteKeyInput) {
|
});
|
||||||
remoteKeyInput.addEventListener('change', (e) => saveKey(e.target.value));
|
|
||||||
remoteKeyInput.addEventListener('input', (e) => saveKeyDebounced(e.target.value));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 绑定代理输入框
|
|
||||||
if (proxyInput) {
|
|
||||||
proxyInput.addEventListener('change', (e) => saveProxy(e.target.value));
|
|
||||||
proxyInput.addEventListener('input', (e) => saveProxyDebounced(e.target.value));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -476,9 +418,6 @@ class CLIProxyManager {
|
|||||||
logoutBtn.addEventListener('click', () => this.logout());
|
logoutBtn.addEventListener('click', () => this.logout());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 选项卡切换事件
|
|
||||||
this.setupTabSwitching();
|
|
||||||
|
|
||||||
// 密钥可见性切换事件
|
// 密钥可见性切换事件
|
||||||
this.setupKeyVisibilityToggle();
|
this.setupKeyVisibilityToggle();
|
||||||
|
|
||||||
@@ -486,30 +425,6 @@ class CLIProxyManager {
|
|||||||
this.bindMainPageEvents();
|
this.bindMainPageEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置选项卡切换
|
|
||||||
setupTabSwitching() {
|
|
||||||
const tabButtons = document.querySelectorAll('.tab-button');
|
|
||||||
const connectionForms = document.querySelectorAll('.connection-form');
|
|
||||||
|
|
||||||
tabButtons.forEach(button => {
|
|
||||||
button.addEventListener('click', () => {
|
|
||||||
const targetTab = button.getAttribute('data-tab');
|
|
||||||
|
|
||||||
// 更新选项卡状态
|
|
||||||
tabButtons.forEach(btn => btn.classList.remove('active'));
|
|
||||||
button.classList.add('active');
|
|
||||||
|
|
||||||
// 切换表单
|
|
||||||
connectionForms.forEach(form => {
|
|
||||||
form.classList.remove('active');
|
|
||||||
if (form.id === `${targetTab}-form`) {
|
|
||||||
form.classList.add('active');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// 设置密钥可见性切换
|
// 设置密钥可见性切换
|
||||||
setupKeyVisibilityToggle() {
|
setupKeyVisibilityToggle() {
|
||||||
const toggleButtons = document.querySelectorAll('.toggle-key-visibility');
|
const toggleButtons = document.querySelectorAll('.toggle-key-visibility');
|
||||||
@@ -729,6 +644,7 @@ class CLIProxyManager {
|
|||||||
this.apiUrl = this.computeApiUrl(this.apiBase);
|
this.apiUrl = this.computeApiUrl(this.apiBase);
|
||||||
localStorage.setItem('apiBase', this.apiBase);
|
localStorage.setItem('apiBase', this.apiBase);
|
||||||
localStorage.setItem('apiUrl', this.apiUrl); // 兼容旧字段
|
localStorage.setItem('apiUrl', this.apiUrl); // 兼容旧字段
|
||||||
|
this.updateLoginConnectionInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
// 加载设置(简化版,仅加载内部状态)
|
// 加载设置(简化版,仅加载内部状态)
|
||||||
@@ -737,21 +653,20 @@ class CLIProxyManager {
|
|||||||
const savedUrl = localStorage.getItem('apiUrl');
|
const savedUrl = localStorage.getItem('apiUrl');
|
||||||
const savedKey = localStorage.getItem('managementKey');
|
const savedKey = localStorage.getItem('managementKey');
|
||||||
|
|
||||||
// 只设置内部状态,不操作DOM元素
|
|
||||||
if (savedBase) {
|
if (savedBase) {
|
||||||
this.setApiBase(savedBase);
|
this.setApiBase(savedBase);
|
||||||
} else if (savedUrl) {
|
} else if (savedUrl) {
|
||||||
const base = (savedUrl || '').replace(/\/?v0\/management\/?$/i, '');
|
const base = (savedUrl || '').replace(/\/?v0\/management\/?$/i, '');
|
||||||
this.setApiBase(base);
|
this.setApiBase(base);
|
||||||
} else {
|
} else {
|
||||||
this.setApiBase(this.apiBase);
|
this.setApiBase(this.detectApiBaseFromLocation());
|
||||||
}
|
}
|
||||||
|
|
||||||
if (savedKey) {
|
if (savedKey) {
|
||||||
this.managementKey = savedKey;
|
this.managementKey = savedKey;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.updateLoginConnectionInfo();
|
||||||
}
|
}
|
||||||
|
|
||||||
// API 请求方法
|
// API 请求方法
|
||||||
@@ -2191,6 +2106,7 @@ class CLIProxyManager {
|
|||||||
showGeminiWebTokenModal() {
|
showGeminiWebTokenModal() {
|
||||||
const inlineSecure1psid = document.getElementById('secure-1psid-input');
|
const inlineSecure1psid = document.getElementById('secure-1psid-input');
|
||||||
const inlineSecure1psidts = document.getElementById('secure-1psidts-input');
|
const inlineSecure1psidts = document.getElementById('secure-1psidts-input');
|
||||||
|
const inlineLabel = document.getElementById('gemini-web-label-input');
|
||||||
const modalBody = document.getElementById('modal-body');
|
const modalBody = document.getElementById('modal-body');
|
||||||
modalBody.innerHTML = `
|
modalBody.innerHTML = `
|
||||||
<h3>${i18n.t('auth_login.gemini_web_button')}</h3>
|
<h3>${i18n.t('auth_login.gemini_web_button')}</h3>
|
||||||
@@ -2205,6 +2121,11 @@ class CLIProxyManager {
|
|||||||
<input type="text" id="modal-secure-1psidts" placeholder="${i18n.t('auth_login.secure_1psidts_placeholder')}" required>
|
<input type="text" id="modal-secure-1psidts" placeholder="${i18n.t('auth_login.secure_1psidts_placeholder')}" required>
|
||||||
<div class="form-hint">从浏览器开发者工具 → Application → Cookies 中获取</div>
|
<div class="form-hint">从浏览器开发者工具 → Application → Cookies 中获取</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="modal-gemini-web-label">${i18n.t('auth_login.gemini_web_label_label')}</label>
|
||||||
|
<input type="text" id="modal-gemini-web-label" placeholder="${i18n.t('auth_login.gemini_web_label_placeholder')}">
|
||||||
|
<div class="form-hint">为此认证文件设置一个标签名称(可选)</div>
|
||||||
|
</div>
|
||||||
<div class="modal-actions">
|
<div class="modal-actions">
|
||||||
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
|
<button class="btn btn-secondary" onclick="manager.closeModal()">${i18n.t('common.cancel')}</button>
|
||||||
<button class="btn btn-primary" onclick="manager.saveGeminiWebToken()">${i18n.t('common.save')}</button>
|
<button class="btn btn-primary" onclick="manager.saveGeminiWebToken()">${i18n.t('common.save')}</button>
|
||||||
@@ -2215,6 +2136,7 @@ class CLIProxyManager {
|
|||||||
|
|
||||||
const modalSecure1psid = document.getElementById('modal-secure-1psid');
|
const modalSecure1psid = document.getElementById('modal-secure-1psid');
|
||||||
const modalSecure1psidts = document.getElementById('modal-secure-1psidts');
|
const modalSecure1psidts = document.getElementById('modal-secure-1psidts');
|
||||||
|
const modalLabel = document.getElementById('modal-gemini-web-label');
|
||||||
|
|
||||||
if (modalSecure1psid && inlineSecure1psid) {
|
if (modalSecure1psid && inlineSecure1psid) {
|
||||||
modalSecure1psid.value = inlineSecure1psid.value.trim();
|
modalSecure1psid.value = inlineSecure1psid.value.trim();
|
||||||
@@ -2222,6 +2144,9 @@ class CLIProxyManager {
|
|||||||
if (modalSecure1psidts && inlineSecure1psidts) {
|
if (modalSecure1psidts && inlineSecure1psidts) {
|
||||||
modalSecure1psidts.value = inlineSecure1psidts.value.trim();
|
modalSecure1psidts.value = inlineSecure1psidts.value.trim();
|
||||||
}
|
}
|
||||||
|
if (modalLabel && inlineLabel) {
|
||||||
|
modalLabel.value = inlineLabel.value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
if (modalSecure1psid) {
|
if (modalSecure1psid) {
|
||||||
modalSecure1psid.focus();
|
modalSecure1psid.focus();
|
||||||
@@ -2232,6 +2157,7 @@ class CLIProxyManager {
|
|||||||
async saveGeminiWebToken() {
|
async saveGeminiWebToken() {
|
||||||
const secure1psid = document.getElementById('modal-secure-1psid').value.trim();
|
const secure1psid = document.getElementById('modal-secure-1psid').value.trim();
|
||||||
const secure1psidts = document.getElementById('modal-secure-1psidts').value.trim();
|
const secure1psidts = document.getElementById('modal-secure-1psidts').value.trim();
|
||||||
|
const label = document.getElementById('modal-gemini-web-label').value.trim();
|
||||||
|
|
||||||
if (!secure1psid || !secure1psidts) {
|
if (!secure1psid || !secure1psidts) {
|
||||||
this.showNotification('请填写完整的 Cookie 信息', 'error');
|
this.showNotification('请填写完整的 Cookie 信息', 'error');
|
||||||
@@ -2239,27 +2165,38 @@ class CLIProxyManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const requestBody = {
|
||||||
|
secure_1psid: secure1psid,
|
||||||
|
secure_1psidts: secure1psidts
|
||||||
|
};
|
||||||
|
|
||||||
|
// 如果提供了 label,则添加到请求体中
|
||||||
|
if (label) {
|
||||||
|
requestBody.label = label;
|
||||||
|
}
|
||||||
|
|
||||||
const response = await this.makeRequest('/gemini-web-token', {
|
const response = await this.makeRequest('/gemini-web-token', {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json'
|
'Content-Type': 'application/json'
|
||||||
},
|
},
|
||||||
body: JSON.stringify({
|
body: JSON.stringify(requestBody)
|
||||||
secure_1psid: secure1psid,
|
|
||||||
secure_1psidts: secure1psidts
|
|
||||||
})
|
|
||||||
});
|
});
|
||||||
|
|
||||||
this.closeModal();
|
this.closeModal();
|
||||||
this.loadAuthFiles(); // 刷新认证文件列表
|
this.loadAuthFiles(); // 刷新认证文件列表
|
||||||
const inlineSecure1psid = document.getElementById('secure-1psid-input');
|
const inlineSecure1psid = document.getElementById('secure-1psid-input');
|
||||||
const inlineSecure1psidts = document.getElementById('secure-1psidts-input');
|
const inlineSecure1psidts = document.getElementById('secure-1psidts-input');
|
||||||
|
const inlineLabel = document.getElementById('gemini-web-label-input');
|
||||||
if (inlineSecure1psid) {
|
if (inlineSecure1psid) {
|
||||||
inlineSecure1psid.value = secure1psid;
|
inlineSecure1psid.value = secure1psid;
|
||||||
}
|
}
|
||||||
if (inlineSecure1psidts) {
|
if (inlineSecure1psidts) {
|
||||||
inlineSecure1psidts.value = secure1psidts;
|
inlineSecure1psidts.value = secure1psidts;
|
||||||
}
|
}
|
||||||
|
if (inlineLabel) {
|
||||||
|
inlineLabel.value = label;
|
||||||
|
}
|
||||||
this.showNotification(`${i18n.t('auth_login.gemini_web_saved')}: ${response.file}`, 'success');
|
this.showNotification(`${i18n.t('auth_login.gemini_web_saved')}: ${response.file}`, 'success');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.showNotification(`保存失败: ${error.message}`, 'error');
|
this.showNotification(`保存失败: ${error.message}`, 'error');
|
||||||
@@ -2614,6 +2551,28 @@ class CLIProxyManager {
|
|||||||
closeModal() {
|
closeModal() {
|
||||||
document.getElementById('modal').style.display = 'none';
|
document.getElementById('modal').style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
detectApiBaseFromLocation() {
|
||||||
|
try {
|
||||||
|
const { protocol, hostname, port } = window.location;
|
||||||
|
const normalizedPort = port ? `:${port}` : '';
|
||||||
|
return this.normalizeBase(`${protocol}//${hostname}${normalizedPort}`);
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('无法从当前地址检测 API 基础地址,使用默认设置', error);
|
||||||
|
return this.normalizeBase(this.apiBase || 'http://localhost:8317');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
updateLoginConnectionInfo() {
|
||||||
|
const connectionUrlElement = document.getElementById('login-connection-url');
|
||||||
|
const customInput = document.getElementById('login-api-base');
|
||||||
|
if (connectionUrlElement) {
|
||||||
|
connectionUrlElement.textContent = this.apiBase || '-';
|
||||||
|
}
|
||||||
|
if (customInput && customInput !== document.activeElement) {
|
||||||
|
customInput.value = this.apiBase || '';
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 全局管理器实例
|
// 全局管理器实例
|
||||||
@@ -2625,6 +2584,19 @@ function setupSiteLogo() {
|
|||||||
const loginImg = document.getElementById('login-logo');
|
const loginImg = document.getElementById('login-logo');
|
||||||
if (!img && !loginImg) return;
|
if (!img && !loginImg) return;
|
||||||
|
|
||||||
|
const inlineLogo = typeof window !== 'undefined' ? window.__INLINE_LOGO__ : null;
|
||||||
|
if (inlineLogo) {
|
||||||
|
if (img) {
|
||||||
|
img.src = inlineLogo;
|
||||||
|
img.style.display = 'inline-block';
|
||||||
|
}
|
||||||
|
if (loginImg) {
|
||||||
|
loginImg.src = inlineLogo;
|
||||||
|
loginImg.style.display = 'inline-block';
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const candidates = [
|
const candidates = [
|
||||||
'../logo.svg', '../logo.png', '../logo.jpg', '../logo.jpeg', '../logo.webp', '../logo.gif',
|
'../logo.svg', '../logo.png', '../logo.jpg', '../logo.jpeg', '../logo.webp', '../logo.gif',
|
||||||
'logo.svg', 'logo.png', 'logo.jpg', 'logo.jpeg', 'logo.webp', 'logo.gif',
|
'logo.svg', 'logo.png', 'logo.jpg', 'logo.jpeg', 'logo.webp', 'logo.gif',
|
||||||
|
|||||||
132
build.js
Normal file
132
build.js
Normal file
@@ -0,0 +1,132 @@
|
|||||||
|
'use strict';
|
||||||
|
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const projectRoot = __dirname;
|
||||||
|
const distDir = path.join(projectRoot, 'dist');
|
||||||
|
|
||||||
|
const sourceFiles = {
|
||||||
|
html: path.join(projectRoot, 'index.html'),
|
||||||
|
css: path.join(projectRoot, 'styles.css'),
|
||||||
|
i18n: path.join(projectRoot, 'i18n.js'),
|
||||||
|
app: path.join(projectRoot, 'app.js')
|
||||||
|
};
|
||||||
|
|
||||||
|
const logoCandidates = ['logo.png', 'logo.jpg', 'logo.jpeg', 'logo.svg', 'logo.webp', 'logo.gif'];
|
||||||
|
const logoMimeMap = {
|
||||||
|
'.png': 'image/png',
|
||||||
|
'.jpg': 'image/jpeg',
|
||||||
|
'.jpeg': 'image/jpeg',
|
||||||
|
'.svg': 'image/svg+xml',
|
||||||
|
'.webp': 'image/webp',
|
||||||
|
'.gif': 'image/gif'
|
||||||
|
};
|
||||||
|
|
||||||
|
function readFile(filePath) {
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(filePath, 'utf8');
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`读取文件失败: ${filePath}`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function readBinary(filePath) {
|
||||||
|
try {
|
||||||
|
return fs.readFileSync(filePath);
|
||||||
|
} catch (err) {
|
||||||
|
console.error(`读取文件失败: ${filePath}`);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeForScript(content) {
|
||||||
|
return content.replace(/<\/(script)/gi, '<\\/$1');
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeForStyle(content) {
|
||||||
|
return content.replace(/<\/(style)/gi, '<\\/$1');
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureDistDir() {
|
||||||
|
if (fs.existsSync(distDir)) {
|
||||||
|
fs.rmSync(distDir, { recursive: true, force: true });
|
||||||
|
}
|
||||||
|
fs.mkdirSync(distDir);
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadLogoDataUrl() {
|
||||||
|
for (const candidate of logoCandidates) {
|
||||||
|
const filePath = path.join(projectRoot, candidate);
|
||||||
|
if (!fs.existsSync(filePath)) continue;
|
||||||
|
|
||||||
|
const ext = path.extname(candidate).toLowerCase();
|
||||||
|
const mime = logoMimeMap[ext];
|
||||||
|
if (!mime) {
|
||||||
|
console.warn(`未知 Logo 文件类型,跳过内联: ${candidate}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const buffer = readBinary(filePath);
|
||||||
|
const base64 = buffer.toString('base64');
|
||||||
|
return `data:${mime};base64,${base64}`;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function build() {
|
||||||
|
ensureDistDir();
|
||||||
|
|
||||||
|
let html = readFile(sourceFiles.html);
|
||||||
|
const css = escapeForStyle(readFile(sourceFiles.css));
|
||||||
|
const i18n = escapeForScript(readFile(sourceFiles.i18n));
|
||||||
|
const app = escapeForScript(readFile(sourceFiles.app));
|
||||||
|
|
||||||
|
html = html.replace(
|
||||||
|
'<link rel="stylesheet" href="styles.css">',
|
||||||
|
`<style>
|
||||||
|
${css}
|
||||||
|
</style>`
|
||||||
|
);
|
||||||
|
|
||||||
|
html = html.replace(
|
||||||
|
'<script src="i18n.js"></script>',
|
||||||
|
`<script>
|
||||||
|
${i18n}
|
||||||
|
</script>`
|
||||||
|
);
|
||||||
|
|
||||||
|
html = html.replace(
|
||||||
|
'<script src="app.js"></script>',
|
||||||
|
`<script>
|
||||||
|
${app}
|
||||||
|
</script>`
|
||||||
|
);
|
||||||
|
|
||||||
|
const logoDataUrl = loadLogoDataUrl();
|
||||||
|
if (logoDataUrl) {
|
||||||
|
const logoScript = `<script>window.__INLINE_LOGO__ = "${logoDataUrl}";</script>`;
|
||||||
|
if (html.includes('</body>')) {
|
||||||
|
html = html.replace('</body>', `${logoScript}\n</body>`);
|
||||||
|
} else {
|
||||||
|
html += `\n${logoScript}`;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.warn('未找到可内联的 Logo 文件,将保持运行时加载。');
|
||||||
|
}
|
||||||
|
|
||||||
|
const outputPath = path.join(distDir, 'index.html');
|
||||||
|
fs.writeFileSync(outputPath, html, 'utf8');
|
||||||
|
|
||||||
|
console.log('构建完成: dist/index.html');
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
build();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('构建失败:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
46
i18n.js
46
i18n.js
@@ -47,19 +47,13 @@ const i18n = {
|
|||||||
|
|
||||||
// 登录页面
|
// 登录页面
|
||||||
'login.subtitle': '请输入连接信息以访问管理界面',
|
'login.subtitle': '请输入连接信息以访问管理界面',
|
||||||
'login.tab_local_title': 'Local',
|
'login.connection_title': '连接地址',
|
||||||
'login.tab_local_subtitle': '在本地运行 Cli Web 服务器',
|
'login.connection_current': '当前地址',
|
||||||
'login.tab_remote_title': 'Remote',
|
'login.connection_auto_hint': '系统将自动使用当前访问地址进行连接',
|
||||||
'login.tab_remote_subtitle': '远程连接到 Cli Web 服务器',
|
'login.custom_connection_label': '自定义连接地址:',
|
||||||
'login.proxy_label': '代理服务器 (可选):',
|
'login.custom_connection_placeholder': '例如: https://example.com:8317',
|
||||||
'login.proxy_placeholder': 'http://ip:port 或 https://ip:port 或 socks5://user:pass@ip:port',
|
'login.custom_connection_hint': '默认使用当前访问地址,若需要可手动输入其他地址。',
|
||||||
'login.local_port_label': '端口号:',
|
'login.use_current_address': '使用当前地址',
|
||||||
'login.local_port_placeholder': '8317',
|
|
||||||
'login.local_url_hint': '将连接到 http://localhost:端口/v0/management',
|
|
||||||
'login.api_url_label': 'API 基础地址:',
|
|
||||||
'login.api_url_placeholder': '例如: http://localhost:8317 或 127.0.0.1:8317',
|
|
||||||
'login.remote_api_url_placeholder': '例如: https://example.com:8317',
|
|
||||||
'login.api_url_hint': '将自动补全 /v0/management',
|
|
||||||
'login.management_key_label': '管理密钥:',
|
'login.management_key_label': '管理密钥:',
|
||||||
'login.management_key_placeholder': '请输入管理密钥',
|
'login.management_key_placeholder': '请输入管理密钥',
|
||||||
'login.connect_button': '连接',
|
'login.connect_button': '连接',
|
||||||
@@ -211,6 +205,8 @@ const i18n = {
|
|||||||
'auth_login.secure_1psid_placeholder': '输入 __Secure-1PSID cookie 值',
|
'auth_login.secure_1psid_placeholder': '输入 __Secure-1PSID cookie 值',
|
||||||
'auth_login.secure_1psidts_label': '__Secure-1PSIDTS Cookie:',
|
'auth_login.secure_1psidts_label': '__Secure-1PSIDTS Cookie:',
|
||||||
'auth_login.secure_1psidts_placeholder': '输入 __Secure-1PSIDTS cookie 值',
|
'auth_login.secure_1psidts_placeholder': '输入 __Secure-1PSIDTS cookie 值',
|
||||||
|
'auth_login.gemini_web_label_label': '标签 (可选):',
|
||||||
|
'auth_login.gemini_web_label_placeholder': '输入标签名称 (可选)',
|
||||||
'auth_login.gemini_web_saved': 'Gemini Web Token 保存成功',
|
'auth_login.gemini_web_saved': 'Gemini Web Token 保存成功',
|
||||||
|
|
||||||
// 使用统计
|
// 使用统计
|
||||||
@@ -342,21 +338,15 @@ const i18n = {
|
|||||||
|
|
||||||
// Login page
|
// Login page
|
||||||
'login.subtitle': 'Please enter connection information to access the management interface',
|
'login.subtitle': 'Please enter connection information to access the management interface',
|
||||||
'login.tab_local_title': 'Local',
|
'login.connection_title': 'Connection Address',
|
||||||
'login.tab_local_subtitle': 'Run Cli Web server on your local machine',
|
'login.connection_current': 'Current URL',
|
||||||
'login.tab_remote_title': 'Remote',
|
'login.connection_auto_hint': 'The system will automatically use the current URL for connection',
|
||||||
'login.tab_remote_subtitle': 'Remote connection for a remote Cli Web server',
|
'login.custom_connection_label': 'Custom Connection URL:',
|
||||||
'login.proxy_label': 'Proxy Server (Optional):',
|
'login.custom_connection_placeholder': 'Eg: https://example.com:8317',
|
||||||
'login.proxy_placeholder': 'http://ip:port or https://ip:port or socks5://user:pass@ip:port',
|
'login.custom_connection_hint': 'By default the current URL is used. Override it here if needed.',
|
||||||
'login.local_port_label': 'Port:',
|
'login.use_current_address': 'Use Current URL',
|
||||||
'login.local_port_placeholder': '8317',
|
|
||||||
'login.local_url_hint': 'Will connect to http://localhost:port/v0/management',
|
|
||||||
'login.api_url_label': 'API Base URL:',
|
|
||||||
'login.api_url_placeholder': 'e.g.: http://localhost:8317 or 127.0.0.1:8317',
|
|
||||||
'login.remote_api_url_placeholder': 'e.g.: https://example.com:8317',
|
|
||||||
'login.api_url_hint': 'Will automatically append /v0/management',
|
|
||||||
'login.management_key_label': 'Management Key:',
|
'login.management_key_label': 'Management Key:',
|
||||||
'login.management_key_placeholder': 'Please enter management key',
|
'login.management_key_placeholder': 'Enter the management key',
|
||||||
'login.connect_button': 'Connect',
|
'login.connect_button': 'Connect',
|
||||||
'login.submit_button': 'Login',
|
'login.submit_button': 'Login',
|
||||||
'login.submitting': 'Connecting...',
|
'login.submitting': 'Connecting...',
|
||||||
@@ -506,6 +496,8 @@ const i18n = {
|
|||||||
'auth_login.secure_1psid_placeholder': 'Enter __Secure-1PSID cookie value',
|
'auth_login.secure_1psid_placeholder': 'Enter __Secure-1PSID cookie value',
|
||||||
'auth_login.secure_1psidts_label': '__Secure-1PSIDTS Cookie:',
|
'auth_login.secure_1psidts_label': '__Secure-1PSIDTS Cookie:',
|
||||||
'auth_login.secure_1psidts_placeholder': 'Enter __Secure-1PSIDTS cookie value',
|
'auth_login.secure_1psidts_placeholder': 'Enter __Secure-1PSIDTS cookie value',
|
||||||
|
'auth_login.gemini_web_label_label': 'Label (Optional):',
|
||||||
|
'auth_login.gemini_web_label_placeholder': 'Enter label name (optional)',
|
||||||
'auth_login.gemini_web_saved': 'Gemini Web Token saved successfully',
|
'auth_login.gemini_web_saved': 'Gemini Web Token saved successfully',
|
||||||
|
|
||||||
// Usage Statistics
|
// Usage Statistics
|
||||||
|
|||||||
103
index.html
103
index.html
@@ -49,67 +49,39 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 选项卡导航 -->
|
<div class="login-body">
|
||||||
<div class="connection-tabs">
|
<div class="login-connection-info">
|
||||||
<button class="tab-button active" data-tab="local">
|
<div class="connection-summary">
|
||||||
<i class="fas fa-home"></i>
|
<i class="fas fa-link"></i>
|
||||||
<div class="tab-content">
|
<div>
|
||||||
<span class="tab-title" data-i18n="login.tab_local_title">Local</span>
|
<h3 data-i18n="login.connection_title">连接地址</h3>
|
||||||
<span class="tab-subtitle" data-i18n="login.tab_local_subtitle">Run CLI Web server on your local machine</span>
|
<p class="connection-url">
|
||||||
|
<span data-i18n="login.connection_current">当前地址</span>
|
||||||
|
<span class="connection-url-separator">:</span>
|
||||||
|
<span id="login-connection-url">-</span>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</button>
|
<p class="form-hint" data-i18n="login.connection_auto_hint">系统将自动使用当前访问地址进行连接</p>
|
||||||
<button class="tab-button" data-tab="remote">
|
</div>
|
||||||
<i class="fas fa-cloud"></i>
|
|
||||||
<div class="tab-content">
|
|
||||||
<span class="tab-title" data-i18n="login.tab_remote_title">Remote</span>
|
|
||||||
<span class="tab-subtitle" data-i18n="login.tab_remote_subtitle">Remote connection for a remote CLI Web server</span>
|
|
||||||
</div>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 代理服务器设置(可选) -->
|
|
||||||
<div class="proxy-settings">
|
|
||||||
<label data-i18n="login.proxy_label">Proxy Server (Optional):</label>
|
|
||||||
<input type="text" id="login-proxy-url" data-i18n="login.proxy_placeholder" placeholder="http://ip:port or https://ip:port or socks5://user:pass@ip:port">
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 本地连接表单 -->
|
|
||||||
<div id="local-form" class="connection-form active">
|
|
||||||
<form class="login-form">
|
<form class="login-form">
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="local-port" data-i18n="login.local_port_label">端口号:</label>
|
<label for="login-api-base" data-i18n="login.custom_connection_label">自定义连接地址:</label>
|
||||||
<div class="local-url-group">
|
|
||||||
<span class="url-prefix">http://localhost:</span>
|
|
||||||
<input type="number" id="local-port" value="8317" min="1" max="65535" data-i18n="login.local_port_placeholder" placeholder="8317" required>
|
|
||||||
</div>
|
|
||||||
<div class="form-hint" data-i18n="login.local_url_hint">将连接到 http://localhost:端口/v0/management</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="local-management-key" data-i18n="login.management_key_label">管理密钥:</label>
|
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="password" id="local-management-key" data-i18n="login.management_key_placeholder" placeholder="请输入管理密钥" required>
|
<input type="text" id="login-api-base" data-i18n="login.custom_connection_placeholder" placeholder="例如: https://example.com:8317">
|
||||||
<button type="button" class="btn btn-secondary toggle-key-visibility">
|
<button type="button" id="login-reset-api-base" class="btn btn-secondary connection-reset-btn">
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-location-arrow"></i>
|
||||||
|
<span data-i18n="login.use_current_address">使用当前地址</span>
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<p class="form-hint" data-i18n="login.custom_connection_hint">默认使用当前访问地址,若需要可手动输入其他地址。</p>
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- 远程连接表单 -->
|
|
||||||
<div id="remote-form" class="connection-form">
|
|
||||||
<form class="login-form">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="remote-api-url" data-i18n="login.api_url_label">API 基础地址:</label>
|
|
||||||
<input type="text" id="remote-api-url" data-i18n="login.remote_api_url_placeholder" placeholder="例如: https://example.com:8317" required>
|
|
||||||
<div class="form-hint" data-i18n="login.api_url_hint">将自动补全 /v0/management</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label for="remote-management-key" data-i18n="login.management_key_label">管理密钥:</label>
|
<label for="login-management-key" data-i18n="login.management_key_label">管理密钥:</label>
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input type="password" id="remote-management-key" data-i18n="login.management_key_placeholder" placeholder="请输入管理密钥" required>
|
<input type="password" id="login-management-key" data-i18n="login.management_key_placeholder" placeholder="请输入管理密钥" required>
|
||||||
<button type="button" class="btn btn-secondary toggle-key-visibility">
|
<button type="button" class="btn btn-secondary toggle-key-visibility">
|
||||||
<i class="fas fa-eye"></i>
|
<i class="fas fa-eye"></i>
|
||||||
</button>
|
</button>
|
||||||
@@ -383,7 +355,19 @@
|
|||||||
<div id="openai-providers-list" class="provider-list"></div>
|
<div id="openai-providers-list" class="provider-list"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<!-- 认证文件管理 -->
|
||||||
|
<section id="auth-files" class="content-section">
|
||||||
|
<h2 data-i18n="auth_files.title">认证文件管理</h2>
|
||||||
|
|
||||||
|
<div class="card" style="margin-bottom: 20px;">
|
||||||
|
<div class="card-content">
|
||||||
|
<p class="form-hint" data-i18n="auth_files.description">
|
||||||
|
这里管理 Qwen 和 Gemini 的认证配置文件。上传 JSON 格式的认证文件以启用相应的 AI 服务。
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Gemini Web Token -->
|
<!-- Gemini Web Token -->
|
||||||
<div class="card">
|
<div class="card">
|
||||||
@@ -405,19 +389,10 @@
|
|||||||
<label for="secure-1psidts-input" data-i18n="auth_login.secure_1psidts_label">__Secure-1PSIDTS Cookie:</label>
|
<label for="secure-1psidts-input" data-i18n="auth_login.secure_1psidts_label">__Secure-1PSIDTS Cookie:</label>
|
||||||
<input type="text" id="secure-1psidts-input" data-i18n="auth_login.secure_1psidts_placeholder" placeholder="输入 __Secure-1PSIDTS cookie 值">
|
<input type="text" id="secure-1psidts-input" data-i18n="auth_login.secure_1psidts_placeholder" placeholder="输入 __Secure-1PSIDTS cookie 值">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div class="form-group">
|
||||||
</div>
|
<label for="gemini-web-label-input" data-i18n="auth_login.gemini_web_label_label">Label (Optional):</label>
|
||||||
</section>
|
<input type="text" id="gemini-web-label-input" data-i18n="auth_login.gemini_web_label_placeholder" placeholder="输入标签名称 (可选)">
|
||||||
|
</div>
|
||||||
<!-- 认证文件管理 -->
|
|
||||||
<section id="auth-files" class="content-section">
|
|
||||||
<h2 data-i18n="auth_files.title">认证文件管理</h2>
|
|
||||||
|
|
||||||
<div class="card" style="margin-bottom: 20px;">
|
|
||||||
<div class="card-content">
|
|
||||||
<p class="form-hint" data-i18n="auth_files.description">
|
|
||||||
这里管理 Qwen 和 Gemini 的认证配置文件。上传 JSON 格式的认证文件以启用相应的 AI 服务。
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -579,7 +554,7 @@
|
|||||||
<!-- 版本信息 -->
|
<!-- 版本信息 -->
|
||||||
<footer class="version-footer">
|
<footer class="version-footer">
|
||||||
<div class="version-info">
|
<div class="version-info">
|
||||||
<span data-i18n="footer.version">版本</span>: v0.0.3
|
<span data-i18n="footer.version">版本</span>: v0.0.5
|
||||||
<span class="separator">•</span>
|
<span class="separator">•</span>
|
||||||
<span data-i18n="footer.author">作者</span>: Supra4E8C
|
<span data-i18n="footer.author">作者</span>: Supra4E8C
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
2074
package-lock.json
generated
2074
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
16
package.json
16
package.json
@@ -6,9 +6,7 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"start": "npx serve .",
|
"start": "npx serve .",
|
||||||
"dev": "npx serve . --port 3000",
|
"dev": "npx serve . --port 3000",
|
||||||
"prebuild": "node build-scripts/prepare-html.js",
|
"build": "node build.js",
|
||||||
"build": "webpack --config webpack.config.js",
|
|
||||||
"postbuild": "rm -f index.build.html",
|
|
||||||
"lint": "echo '使用浏览器开发者工具检查代码'"
|
"lint": "echo '使用浏览器开发者工具检查代码'"
|
||||||
},
|
},
|
||||||
"keywords": [
|
"keywords": [
|
||||||
@@ -20,14 +18,7 @@
|
|||||||
"author": "CLI Proxy API WebUI",
|
"author": "CLI Proxy API WebUI",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"css-loader": "^6.8.1",
|
"serve": "^14.2.1"
|
||||||
"html-inline-script-webpack-plugin": "^3.2.1",
|
|
||||||
"html-loader": "^4.2.0",
|
|
||||||
"html-webpack-plugin": "^5.5.4",
|
|
||||||
"serve": "^14.2.1",
|
|
||||||
"style-loader": "^3.3.3",
|
|
||||||
"webpack": "^5.102.0",
|
|
||||||
"webpack-cli": "^5.1.4"
|
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=14.0.0"
|
"node": ">=14.0.0"
|
||||||
@@ -35,5 +26,6 @@
|
|||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "local"
|
"url": "local"
|
||||||
}
|
},
|
||||||
|
"dependencies": {}
|
||||||
}
|
}
|
||||||
|
|||||||
267
styles.css
267
styles.css
@@ -296,155 +296,6 @@
|
|||||||
margin-bottom: 25px;
|
margin-bottom: 25px;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* 选项卡导航样式 */
|
|
||||||
.connection-tabs {
|
|
||||||
display: flex;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
margin-top: 10px;
|
|
||||||
border-radius: 12px;
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
padding: 4px;
|
|
||||||
border: 1px solid var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button {
|
|
||||||
flex: 1;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 16px;
|
|
||||||
border: none;
|
|
||||||
background: transparent;
|
|
||||||
border-radius: 8px;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
text-align: left;
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button:hover {
|
|
||||||
background: var(--accent-tertiary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button.active {
|
|
||||||
background: var(--accent-primary);
|
|
||||||
color: var(--text-inverse);
|
|
||||||
box-shadow: var(--shadow-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button i {
|
|
||||||
font-size: 20px;
|
|
||||||
min-width: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 2px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-title {
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-subtitle {
|
|
||||||
font-size: 12px;
|
|
||||||
opacity: 0.8;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 代理设置样式 */
|
|
||||||
.proxy-settings {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
padding: 16px;
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
border: 1px solid var(--border-primary);
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proxy-settings label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 8px;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-weight: 600;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proxy-settings input {
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border: 2px solid var(--border-primary);
|
|
||||||
border-radius: 8px;
|
|
||||||
font-size: 14px;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.proxy-settings input:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--border-focus);
|
|
||||||
box-shadow: 0 0 0 3px var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 连接表单样式 */
|
|
||||||
.connection-form {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.connection-form.active {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* 本地URL组合输入框样式 */
|
|
||||||
.local-url-group {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
border: 2px solid var(--border-primary);
|
|
||||||
border-radius: 8px;
|
|
||||||
background: var(--bg-secondary);
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.local-url-group:focus-within {
|
|
||||||
border-color: var(--border-focus);
|
|
||||||
box-shadow: 0 0 0 3px var(--border-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.url-prefix {
|
|
||||||
padding: 12px 16px;
|
|
||||||
background: var(--bg-tertiary);
|
|
||||||
color: var(--text-tertiary);
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 14px;
|
|
||||||
border-right: 1px solid var(--border-primary);
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.local-url-group input {
|
|
||||||
flex: 1;
|
|
||||||
padding: 12px 16px;
|
|
||||||
border: none;
|
|
||||||
font-size: 14px;
|
|
||||||
background: transparent;
|
|
||||||
color: var(--text-primary);
|
|
||||||
outline: none;
|
|
||||||
min-width: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.local-url-group input::-webkit-outer-spin-button,
|
|
||||||
.local-url-group input::-webkit-inner-spin-button {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.local-url-group input[type="number"] {
|
|
||||||
-moz-appearance: textfield;
|
|
||||||
appearance: textfield;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-title {
|
.login-title {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
@@ -475,8 +326,63 @@
|
|||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.login-body {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.login-connection-info {
|
||||||
|
background: var(--bg-tertiary);
|
||||||
|
border: 1px solid var(--border-primary);
|
||||||
|
border-radius: 16px;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .login-connection-info {
|
||||||
|
background: rgba(30, 41, 59, 0.7);
|
||||||
|
border-color: rgba(100, 116, 139, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-summary {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-summary i {
|
||||||
|
font-size: 24px;
|
||||||
|
color: var(--primary-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-url {
|
||||||
|
font-size: 16px;
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-url-separator {
|
||||||
|
margin: 0 8px;
|
||||||
|
color: var(--text-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
#login-connection-url {
|
||||||
|
font-family: "Fira Code", "Consolas", "Courier New", monospace;
|
||||||
|
color: var(--text-primary);
|
||||||
|
word-break: break-all;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] #login-connection-url {
|
||||||
|
color: var(--text-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
.login-form {
|
.login-form {
|
||||||
width: 100%;
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.login-form .form-group {
|
.login-form .form-group {
|
||||||
@@ -644,41 +550,6 @@
|
|||||||
height: 50px;
|
height: 50px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.connection-tabs {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-button {
|
|
||||||
flex-direction: row;
|
|
||||||
gap: 12px;
|
|
||||||
padding: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.tab-subtitle {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.proxy-settings {
|
|
||||||
padding: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.local-url-group {
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
|
|
||||||
.url-prefix {
|
|
||||||
border-right: none;
|
|
||||||
border-bottom: 1px solid #e2e8f0;
|
|
||||||
border-radius: 8px 8px 0 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.local-url-group input {
|
|
||||||
border-radius: 0 0 8px 8px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.login-form .input-group {
|
.login-form .input-group {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: stretch;
|
align-items: stretch;
|
||||||
@@ -1847,3 +1718,29 @@ input:checked + .slider:before {
|
|||||||
color: var(--text-secondary);
|
color: var(--text-secondary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.connection-reset-btn {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 6px;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-reset-btn i {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.connection-reset-btn span {
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .connection-reset-btn {
|
||||||
|
background: rgba(30, 41, 59, 0.9);
|
||||||
|
border-color: rgba(100, 116, 139, 0.4);
|
||||||
|
color: #cbd5e1;
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-theme="dark"] .connection-reset-btn:hover {
|
||||||
|
background: rgba(51, 65, 85, 0.95);
|
||||||
|
border-color: rgba(100, 116, 139, 0.6);
|
||||||
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user