添加 iFlow API 提供商支持,包括: 1. 新增 MODEL_PROVIDER.IFLOW_API 常量 2. 实现 IFlowApiService 和适配器 3. 添加 OAuth 认证流程及令牌刷新机制 4. 更新相关配置文件、路由和前端界面 5. 扩展多语言支持 6. 修改 Docker 端口映射范围以包含 iFlow 回调端口
220 lines
No EOL
7.3 KiB
JavaScript
220 lines
No EOL
7.3 KiB
JavaScript
// 文件上传功能模块
|
||
|
||
import { showToast } from './utils.js';
|
||
import { t } from './i18n.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) {
|
||
// 尝试从模态框获取 providerType
|
||
const modal = button.closest('.provider-modal');
|
||
const providerType = modal ? modal.getAttribute('data-provider-type') : null;
|
||
this.handleFileUpload(button, targetInputId, providerType);
|
||
}
|
||
}
|
||
});
|
||
|
||
// 监听提供商切换事件
|
||
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',
|
||
'gemini-antigravity': 'antigravity',
|
||
'claude-kiro-oauth': 'kiro',
|
||
'openai-qwen-oauth': 'qwen',
|
||
'openai-iflow': 'iflow'
|
||
};
|
||
return providerMap[provider] || 'gemini';
|
||
}
|
||
|
||
/**
|
||
* 处理文件上传
|
||
* @param {HTMLElement} button - 上传按钮元素
|
||
* @param {string} targetInputId - 目标输入框ID
|
||
* @param {string} providerType - 提供商类型
|
||
*/
|
||
async handleFileUpload(button, targetInputId, providerType) {
|
||
// 创建隐藏的文件输入元素
|
||
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, providerType);
|
||
}
|
||
|
||
// 清理临时文件输入元素
|
||
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 - 上传按钮
|
||
* @param {string} providerType - 提供商类型
|
||
*/
|
||
async uploadFile(file, targetInputId, button, providerType) {
|
||
try {
|
||
// 验证文件类型
|
||
if (!this.validateFileType(file)) {
|
||
showToast(t('common.error'), t('common.fileType'), 'error');
|
||
this.setButtonLoading(button, false);
|
||
return;
|
||
}
|
||
|
||
// 验证文件大小 (5MB 限制)
|
||
if (file.size > 5 * 1024 * 1024) {
|
||
showToast(t('common.error'), t('common.fileSize'), 'error');
|
||
this.setButtonLoading(button, false);
|
||
return;
|
||
}
|
||
|
||
// 使用传入的 providerType 或回退到 currentProvider
|
||
const provider = providerType ? this.getProviderKey(providerType) : this.currentProvider;
|
||
|
||
// 创建 FormData
|
||
const formData = new FormData();
|
||
formData.append('file', file);
|
||
formData.append('provider', provider);
|
||
formData.append('targetInputId', targetInputId);
|
||
|
||
// 使用封装接口发送上传请求
|
||
const result = await window.apiClient.upload('/upload-oauth-credentials', formData);
|
||
|
||
// 成功上传,设置文件路径到输入框
|
||
this.setFilePathToInput(targetInputId, result.filePath);
|
||
showToast(t('common.success'), t('common.uploadSuccess'), 'success');
|
||
|
||
} catch (error) {
|
||
console.error('文件上传错误:', error);
|
||
showToast(t('common.error'), t('common.uploadFailed') + ': ' + 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
|
||
}; |