Local branch had diverged from origin/master (sibling commits on the same base). Rewrote local history linearly on top of origin/master, folding in all local content: exa/hot-platform discovery hunters, leaks ledger, nightly sweep orchestrator, updated .gitignore and skill docs, plus the latest hunt output and vault state. Remote-only files (vault keys, channel scripts) were restored rather than dropped, so the resulting tree is a full union of both sides.
1 line
15 KiB
JSON
1 line
15 KiB
JSON
{"repo": "bingyaoxing/priceelasticity", "files": {"src/services/deepseekService.js": {"0bc09fc21c4e8ed8d02b64f620bf58a675aff152": {"t": 1786068267.677919, "n": 12539, "h": "2b532e10e844b4ed", "c": "/**\n * DeepSeek V3 API服务\n * 用于处理与DeepSeek V3大模型的通信\n * 参考文档: https://api-docs.deepseek.com/\n *\n * 该服务提供了与DeepSeek API通信的功能,包括:\n * - 文件上传\n * - 发送聊天消息\n * - 格式化消息\n */\n\n// 默认API配置\nconst DEFAULT_API_URL = 'https://api.deepseek.com';\nconst DEFAULT_FILE_UPLOAD_URL = 'https://api.deepseek.com/v1/files';\nconst DEFAULT_MODEL = 'deepseek-chat';\nconst DEFAULT_TEMPERATURE = 0.7;\nconst DEFAULT_MAX_TOKENS = 1000;\nconst MAX_RETRY_ATTEMPTS = 5;\nconst RETRY_DELAY_MS = 2000;\nconst MAX_BACKOFF_DELAY = 30000; // 最大重试延迟时间(30秒)\n\n/**\n * 错误类型枚举\n * 用于分类API请求中可能出现的错误类型\n */\nconst ErrorTypes = {\n NETWORK_ERROR: 'network_error',\n AUTHENTICATION_ERROR: 'authentication_error',\n RATE_LIMIT_ERROR: 'rate_limit_error',\n SERVER_ERROR: 'server_error',\n VALIDATION_ERROR: 'validation_error',\n UNKNOWN_ERROR: 'unknown_error'\n};\n\n/**\n * 根据HTTP状态码和错误信息判断错误类型\n * @param {number} statusCode - HTTP状态码\n * @param {Object} errorData - 错误响应数据\n * @returns {string} - 错误类型\n */\nconst determineErrorType = (statusCode, errorData) => {\n // 网络错误\n if (!statusCode) return ErrorTypes.NETWORK_ERROR;\n \n // 认证错误\n if (statusCode === 401) return ErrorTypes.AUTHENTICATION_ERROR;\n \n // 请求频率限制\n if (statusCode === 429) return ErrorTypes.RATE_LIMIT_ERROR;\n \n // 服务器错误\n if (statusCode >= 500) return ErrorTypes.SERVER_ERROR;\n \n // 验证错误\n if (statusCode === 400 || statusCode === 422) return ErrorTypes.VALIDATION_ERROR;\n \n // 其他未知错误\n return ErrorTypes.UNKNOWN_ERROR;\n};\n\n/**\n * 根据错误类型生成用户友好的错误消息\n * @param {string} errorType - 错误类型\n * @param {string} originalMessage - 原始错误消息\n * @param {number} statusCode - HTTP状态码\n * @returns {string} - 用户友好的错误消息\n */\nconst generateUserFriendlyErrorMessage = (errorType, originalMessage, statusCode) => {\n switch (errorType) {\n case ErrorTypes.NETWORK_ERROR:\n return `网络连接错误,请检查您的网络连接并重试。详情: ${originalMessage}`;\n \n case ErrorTypes.AUTHENTICATION_ERROR:\n return `认证失败,请检查您的API密钥是否正确。详情: ${originalMessage}`;\n \n case ErrorTypes.RATE_LIMIT_ERROR:\n return `请求频率超出限制,请稍后再试。详情: ${originalMessage}`;\n \n case ErrorTypes.SERVER_ERROR:\n return `DeepSeek服务器错误(${statusCode}),请稍后再试。详情: ${originalMessage}`;\n \n case ErrorTypes.VALIDATION_ERROR:\n return `请求参数无效,请检查您的输入。详情: ${originalMessage}`;\n \n case ErrorTypes.UNKNOWN_ERROR:\n default:\n return `发生未知错误(${statusCode})。详情: ${originalMessage}`;\n }\n};\n\n/**\n * 处理API响应错误\n * @param {Response} response - Fetch API响应对象\n * @returns {Promise<Object>} - 包含错误类型和消息的对象\n */\nconst handleApiResponseError = async (response) => {\n let errorData = {};\n let errorMessage = response.statusText || '未知错误';\n \n try {\n errorData = await response.json();\n errorMessage = errorData.error?.message || errorMessage;\n } catch (e) {\n // 如果响应不是JSON格式,使用状态文本作为错误消息\n console.warn('无法解析错误响应为JSON:', e);\n }\n \n const errorType = determineErrorType(response.status, errorData);\n const userFriendlyMessage = generateUserFriendlyErrorMessage(errorType, errorMessage, response.status);\n \n return {\n type: errorType,\n message: userFriendlyMessage,\n originalError: errorData,\n statusCode: response.status\n };\n};\n\n/**\n * 带重试机制的API请求\n * @param {Function} requestFn - 执行API请求的函数\n * @param {number} maxRetries - 最大重试次数\n * @param {number} delayMs - 重试延迟(毫秒)\n * @returns {Promise<any>} - API响应\n */\nconst withRetry = async (requestFn, maxRetries = MAX_RETRY_ATTEMPTS, delayMs = RETRY_DELAY_MS) => {\n let lastError;\n \n for (let attempt = 0; attempt <= maxRetries; attempt++) {\n try {\n // 执行请求\n return await requestFn();\n } catch (error) {\n lastError = error;\n \n // 判断是否可以重试\n const canRetry = attempt < maxRetries && (\n error.type === ErrorTypes.NETWORK_ERROR ||\n error.type === ErrorTypes.SERVER_ERROR ||\n error.type === ErrorTypes.RATE_LIMIT_ERROR ||\n (error.statusCode && error.statusCode >= 500)\n );\n\n if (!canRetry) break;\n\n // 计算延迟时间(指数退避策略)\n let retryDelay = Math.min(\n delayMs * Math.pow(2, attempt),\n MAX_BACKOFF_DELAY\n );\n\n // 添加随机抖动,避免多个请求同时重试\n retryDelay += Math.random() * 1000;\n \n // 记录重试日志\n logApiActivity('请求重试', {\n attempt: attempt + 1,\n maxRetries,\n retryDelay,\n errorType: error.type,\n errorMessage: error.message\n }, 'warn');\n \n // 等待一段时间后重试\n await new Promise(resolve => setTimeout(resolve, retryDelay));\n }\n }\n \n // 所有重试都失败,抛出最后一个错误\n throw lastError;\n};\n\n/**\n * 上传文件到DeepSeek V3 API\n * @param {File} file - 要上传的文件\n * @param {Object} options - 可选配置参数\n * @param {boolean} options.retry - 是否启用重试机制,默认为true\n * @param {number} options.maxRetries - 最大重试次数,默认为MAX_RETRY_ATTEMPTS\n * @returns {Promise<Object>} - 返回文件上传响应的Promise,成功时包含file_id\n * @throws {Object} - 当API密钥未配置或API请求失败时抛出错误对象\n */\nexport const uploadFileToDeepSeek = async (file, options = {}) => {\n // 默认启用重试\n const shouldRetry = options.retry !== false;\n const maxRetries = options.maxRetries || MAX_RETRY_ATTEMPTS;\n \n // 定义请求函数\n const performRequest = async () => {\n try {\n // 获取API密钥\n const apiKey = import.meta.env.VITE_DEEPSEEK_API_KEY;\n \n if (!apiKey) {\n const error = {\n type: ErrorTypes.AUTHENTICATION_ERROR,\n message: 'DeepSeek API密钥未配置。请在.env文件中设置VITE_DEEPSEEK_API_KEY',\n originalError: null,\n statusCode: null\n };\n logApiActivity('文件上传失败', { error: '缺少API密钥' }, 'error');\n throw error;\n }\n \n // 记录上传请求\n logApiActivity('文件上传开始', {\n fileName: file.name,\n fileSize: file.size,\n fileType: file.type\n });\n\n // 创建FormData对象\n const formData = new FormData();\n formData.append('file', file);\n formData.append('purpose', 'assistants');\n\n // 构建请求配置\n const requestOptions = {\n method: 'POST',\n headers: {\n 'Authorization': `Bearer ${apiKey}`\n },\n body: formData\n };\n\n // 发送文件上传请求\n let response;\n try {\n response = await fetch(DEFAULT_FILE_UPLOAD_URL, requestOptions);\n } catch (networkError) {\n // 处理网络错误\n const error = {\n type: ErrorTypes.NETWORK_ERROR,\n message: `网络连接错误,无法连接到DeepSeek API。详情: ${networkError.message}`,\n originalError: networkError,\n statusCode: null\n };\n throw error;\n }\n \n // 检查响应状态\n if (!response.ok) {\n const errorDetails = await handleApiResponseError(response);\n throw errorDetails;\n }\n\n // 解析响应数据\n const responseData = await response.json();\n \n // 记录消息发送成功\n logApiActivity('消息发送成功', {\n model: options.model || DEFAULT_MODEL,\n messageCount: messages.length\n });\n \n return responseData;\n } catch (error) {\n // 记录错误信息\n logApiActivity('文件上传失败', {\n fileName: file?.name,\n errorType: error.type || 'unknown',\n errorMessage: error.message || '未知错误'\n }, 'error');\n throw error;\n }\n };\n\n // 执行请求(带重试机制或不带)\n if (shouldRetry) {\n return withRetry(performRequest, maxRetries);\n } else {\n return performRequest();\n }\n};\n\n/**\n * 发送消息到DeepSeek V3 API\n * @param {Array} messages - 消息历史记录数组\n * @param {Object} options - 可选配置参数\n * @param {string} options.model - 模型名称,默认为'deepseek-chat-v3'\n * @param {number} options.temperature - 温度参数,控制随机性,默认为0.7\n * @param {number} options.maxTokens - 最大生成token数,默认为1000\n * @param {boolean} options.stream - 是否使用流式响应,默认为false\n * @param {Array<string>} options.fileIds - 文件ID数组,用于引用上传的文件\n * @param {boolean} options.retry - 是否启用重试机制,默认为true\n * @param {number} options.maxRetries - 最大重试次数,默认为MAX_RETRY_ATTEMPTS\n * @returns {Promise<Object>} - 返回API响应的Promise\n * @throws {Object} - 当API密钥未配置或API请求失败时抛出错误对象\n */\nexport const sendMessageToDeepSeek = async (messages, options = {}) => {\n // 默认启用重试\n const shouldRetry = options.retry !== false;\n const maxRetries = options.maxRetries || MAX_RETRY_ATTEMPTS;\n \n // 定义请求函数\n const performRequest = async () => {\n try {\n // 获取API密钥,优先使用环境变量\n const apiKey = import.meta.env.VITE_DEEPSEEK_API_KEY;\n \n if (!apiKey) {\n const error = {\n type: ErrorTypes.AUTHENTICATION_ERROR,\n message: 'DeepSeek API密钥未配置。请在.env文件中设置VITE_DEEPSEEK_API_KEY',\n originalError: null,\n statusCode: null\n };\n logApiActivity('消息发送失败', { error: '缺少API密钥' }, 'error');\n throw error;\n }\n \n // 记录消息发送请求\n logApiActivity('消息发送开始', {\n messageCount: messages.length,\n model: options.model || DEFAULT_MODEL,\n stream: options.stream || false\n });\n\n // 构建请求配置\n const requestOptions = {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': `Bearer ${apiKey}`\n },\n body: JSON.stringify({\n model: options.model || DEFAULT_MODEL,\n messages: messages,\n temperature: options.temperature || DEFAULT_TEMPERATURE,\n max_tokens: options.maxTokens || DEFAULT_MAX_TOKENS,\n stream: options.stream || false,\n file_ids: options.fileIds || []\n })\n };\n\n // 发送请求到DeepSeek API\n let response;\n try {\n response = await fetch(`${DEFAULT_API_URL}/v1/chat/completions`, requestOptions);\n } catch (networkError) {\n // 处理网络错误\n const error = {\n type: ErrorTypes.NETWORK_ERROR,\n message: `网络连接错误,无法连接到DeepSeek API。详情: ${networkError.message}`,\n originalError: networkError,\n statusCode: null\n };\n throw error;\n }\n \n // 检查响应状态\n if (!response.ok) {\n const errorDetails = await handleApiResponseError(response);\n throw errorDetails;\n }\n\n // 解析响应数据\n const responseData = await response.json();\n \n // 记录消息发送成功\n logApiActivity('消息发送成功', {\n model: options.model || DEFAULT_MODEL,\n messageCount: messages.length\n });\n \n return responseData;\n } catch (error) {\n // 记录错误信息\n logApiActivity('消息发送失败', {\n errorType: error.type || 'unknown',\n errorMessage: error.message || '未知错误',\n model: options.model || DEFAULT_MODEL,\n messageCount: messages?.length || 0\n }, 'error');\n throw error;\n }\n };\n\n // 执行请求(带重试机制或不带)\n if (shouldRetry) {\n return withRetry(performRequest, maxRetries);\n } else {\n return performRequest();\n }\n};\n\n/**\n * 记录API请求日志\n * @param {string} action - 操作类型\n * @param {Object} data - 日志数据\n * @param {string} level - 日志级别 (info, warn, error)\n */\nconst logApiActivity = (action, data, level = 'info') => {\n const timestamp = new Date().toISOString();\n const logEntry = {\n timestamp,\n action,\n ...data\n };\n \n // 根据日志级别选择合适的控制台方法\n switch (level) {\n case 'error':\n console.error(`[DeepSeek API] ${action}:`, logEntry);\n break;\n case 'warn':\n console.warn(`[DeepSeek API] ${action}:`, logEntry);\n break;\n default:\n console.log(`[DeepSeek API] ${action}:`, logEntry);\n }\n \n // 这里可以扩展添加更多日志记录方式,如发送到服务器或保存到本地存储\n};\n\n/**\n * 判断是否应该重试请求\n * @param {Object} error - 错误对象\n * @returns {boolean} - 是否应该重试\n */\nconst shouldRetryRequest = (error) => {\n // 网络错误、服务器错误和频率限制错误可以重试\n return error.type === ErrorTypes.NETWORK_ERROR || \n error.type === ErrorTypes.SERVER_ERROR || \n error.type === ErrorTypes.RATE_LIMIT_ERROR;\n};\n\n/**\n * 格式化聊天消息为DeepSeek API所需格式\n * @param {Array} messages - 应用内的消息格式\n * @param {boolean} messages[].isBot - 是否为机器人消息\n * @param {string} messages[].text - 消息文本内容\n * @param {string} systemMessage - 可选的系统消息\n * @returns {Array} - DeepSeek API所需的消息格式\n */\nexport const formatMessagesForDeepSeek = (messages, systemMessage = null) => {\n if (!Array.isArray(messages)) {\n logApiActivity('格式化消息失败', { error: '消息必须是数组格式' }, 'error');\n throw new Error('消息必须是数组格式');\n }\n\n // 设置默认的系统消息,定义AI助手的角色和回答范围\n const defaultSystemMessage = `您是一位专业的价格弹性分析助手,专注于提供营销和定价相关的咨询服务。您的主要职责包括:\n- 价格弹性分析和建议\n- 定价策略制定\n- 市场营销策略咨询\n- 促销活动效果评估\n- 读取用户所上传excel文件的分析结果,并进行解读和回复\n\n如果用户询问与营销和定价无关的问题,请礼貌地说明您的专业领域,并建议用户咨询其他相关专家。`;\n\n // 使用传入的系统消息或默认系统消息\n const finalSystemMessage = systemMessage || defaultSystemMessage;\n \n const formattedMessages = messages.map(msg => ({\n role: msg.isBot ? 'assistant' : 'user',\n content: msg.text\n }));\n \n // 如果提供了系统消息,添加到消息数组的开头\n if (systemMessage) {\n formattedMessages.unshift({\n role: 'system',\n content: systemMessage\n });\n }\n \n return formattedMessages;\n};"}}}} |