AIClient-2-API/static/app/file-upload.js
hex2077 80d4e16840 feat: 添加可视化Web UI管理控制台及相关功能模块
新增完整的Web UI管理控制台,包含以下主要功能:
1. 响应式设计的现代化界面
2. 实时监控系统状态和提供商统计
3. 配置管理功能,支持多种模型提供商
4. 文件上传和OAuth凭据管理
5. 路径路由调用示例和curl命令生成
6. 实时日志查看和事件流处理
7. 提供完整的UI文档说明

新增多个前端模块文件,包括导航、事件处理、文件上传等功能组件,并更新package.json添加multer依赖以支持文件上传功能。同时添加详细的UI_README.md文档说明所有功能特性和使用方法。
2025-11-11 18:53:39 +08:00

219 lines
No EOL
6.9 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';
/**
* 文件上传处理器类
*/
class FileUploadHandler {
constructor() {
this.currentProvider = 'gemini'; // 默认提供商
this.initEventListeners();
}
/**
* 初始化事件监听器
*/
initEventListeners() {
// 监听所有上传按钮的点击事件
document.addEventListener('click', (event) => {
if (event.target.closest('.upload-btn')) {
const button = event.target.closest('.upload-btn');
const targetInputId = button.getAttribute('data-target');
if (targetInputId) {
this.handleFileUpload(button, targetInputId);
}
}
});
// 监听提供商切换事件
const modelProvider = document.getElementById('modelProvider');
if (modelProvider) {
modelProvider.addEventListener('change', (event) => {
this.updateCurrentProvider(event.target.value);
});
}
}
/**
* 更新当前提供商
* @param {string} provider - 选择的提供商
*/
updateCurrentProvider(provider) {
this.currentProvider = this.getProviderKey(provider);
}
/**
* 获取提供商对应的键名
* @param {string} provider - 提供商名称
* @returns {string} - 提供商标识
*/
getProviderKey(provider) {
const providerMap = {
'gemini-cli-oauth': 'gemini',
'claude-kiro-oauth': 'kiro',
'openai-qwen-oauth': 'qwen'
};
return providerMap[provider] || 'gemini';
}
/**
* 处理文件上传
* @param {HTMLElement} button - 上传按钮元素
* @param {string} targetInputId - 目标输入框ID
*/
async handleFileUpload(button, targetInputId) {
// 创建隐藏的文件输入元素
const fileInput = this.createFileInput();
// 设置文件选择回调
fileInput.onchange = async (event) => {
const file = event.target.files[0];
if (file) {
// 只有文件被实际选择后才显示加载状态并上传
this.setButtonLoading(button, true);
await this.uploadFile(file, targetInputId, button);
}
// 清理临时文件输入元素
fileInput.remove();
};
// 触发文件选择
fileInput.click();
}
/**
* 创建文件输入元素
* @returns {HTMLInputElement} - 文件输入元素
*/
createFileInput() {
const fileInput = document.createElement('input');
fileInput.type = 'file';
fileInput.accept = '.json,.txt,.key,.pem,.p12,.pfx';
fileInput.style.display = 'none';
document.body.appendChild(fileInput);
return fileInput;
}
/**
* 上传文件到服务器
* @param {File} file - 要上传的文件
* @param {string} targetInputId - 目标输入框ID
* @param {HTMLElement} button - 上传按钮
*/
async uploadFile(file, targetInputId, button) {
try {
// 验证文件类型
if (!this.validateFileType(file)) {
showToast('不支持的文件类型,请选择 JSON、TXT、KEY、PEM、P12 或 PFX 文件', 'error');
this.setButtonLoading(button, false);
return;
}
// 验证文件大小 (5MB 限制)
if (file.size > 5 * 1024 * 1024) {
showToast('文件大小不能超过 5MB', 'error');
this.setButtonLoading(button, false);
return;
}
// 创建 FormData
const formData = new FormData();
formData.append('file', file);
formData.append('provider', this.currentProvider);
formData.append('targetInputId', targetInputId);
// 发送上传请求
const response = await fetch('/api/upload-oauth-credentials', {
method: 'POST',
body: formData
});
if (!response.ok) {
const errorData = await response.json();
throw new Error(errorData.error?.message || '上传失败');
}
const result = await response.json();
// 成功上传,设置文件路径到输入框
this.setFilePathToInput(targetInputId, result.filePath);
showToast('文件上传成功', 'success');
} catch (error) {
console.error('文件上传错误:', error);
showToast('文件上传失败: ' + error.message, 'error');
} finally {
this.setButtonLoading(button, false);
}
}
/**
* 验证文件类型
* @param {File} file - 要验证的文件
* @returns {boolean} - 是否为有效文件类型
*/
validateFileType(file) {
const allowedExtensions = ['.json', '.txt', '.key', '.pem', '.p12', '.pfx'];
const fileName = file.name.toLowerCase();
return allowedExtensions.some(ext => fileName.endsWith(ext));
}
/**
* 设置按钮加载状态
* @param {HTMLElement} button - 按钮元素
* @param {boolean} isLoading - 是否加载中
*/
setButtonLoading(button, isLoading) {
const icon = button.querySelector('i');
if (isLoading) {
button.disabled = true;
icon.className = 'fas fa-spinner fa-spin';
} else {
button.disabled = false;
icon.className = 'fas fa-upload';
}
}
/**
* 设置文件路径到输入框
* @param {string} inputId - 输入框ID
* @param {string} filePath - 文件路径
*/
setFilePathToInput(inputId, filePath) {
// console.log('设置文件路径到输入框:', inputId, filePath);
let input = document.getElementById(inputId);
if (input) {
// console.log('输入框元素存在,设置文件路径:', filePath);
input.value = filePath;
// 同时更新data-config-value属性用于编辑模式
if (input.hasAttribute('data-config-value')) {
input.setAttribute('data-config-value', filePath);
console.log('更新data-config-value属性:', filePath);
}
// 触发输入事件,通知其他监听器
input.dispatchEvent(new Event('input', { bubbles: true }));
} else {
console.error('无法找到输入框:', inputId);
}
}
}
/**
* 初始化文件上传功能
*/
function initFileUpload() {
// 文件上传功能是自初始化的单例
console.log('文件上传功能已初始化');
}
// 导出单例实例
const fileUploadHandler = new FileUploadHandler();
export {
fileUploadHandler,
FileUploadHandler,
initFileUpload
};