ai-service.js 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. /**
  2. * AI服务类
  3. * 处理与AI模型的通信和响应
  4. */
  5. class AIService {
  6. constructor() {
  7. this.apiEndpoint = CONFIG.AI_API.ENDPOINT;
  8. this.model = CONFIG.AI_API.MODEL;
  9. this.context = [];
  10. this.currentExcelData = null; // 保存当前Excel数据
  11. // 使用默认API密钥
  12. this.apiKey = CONFIG.AI_API.DEFAULT_API_KEY;
  13. this.controller = null; // 用于中断请求的 AbortController
  14. this.currentPageInfo = null; // 保存当前页面信息
  15. this.openai = new OpenAI(
  16. {
  17. // 若没有配置环境变量,请用百炼API Key将下行替换为:apiKey: "sk-xxx",
  18. apiKey: 'sk-e9855234f47346049809ce23ed3ebe3f',
  19. baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1",
  20. dangerouslyAllowBrowser: true,
  21. }
  22. );
  23. }
  24. /**
  25. * 初始化AI服务
  26. */
  27. async init() {
  28. try {
  29. // 尝试从storage中获取用户设置的API密钥
  30. const result = await Utils.getStorageData("apiKey");
  31. if (result?.apiKey) {
  32. this.apiKey = result.apiKey;
  33. }
  34. console.log("AI Service initialized");
  35. } catch (error) {
  36. console.error("Failed to initialize AI service:", error);
  37. }
  38. }
  39. /**
  40. * 设置API密钥
  41. * @param {string} apiKey
  42. */
  43. async setApiKey(apiKey) {
  44. this.apiKey = apiKey;
  45. await Utils.setStorageData("apiKey", { apiKey });
  46. }
  47. /**
  48. * 发送消息到DeepSeek API
  49. * @param {string} message 用户消息
  50. * @returns {Promise<string>} AI响应
  51. */
  52. async sendMessage(message) {
  53. try {
  54. // 创建新的 AbortController
  55. this.controller = new AbortController();
  56. const a = +new Date()
  57. const response = await this.openai.chat.completions.create({
  58. model: "qwen-plus", //模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
  59. messages: this.formatMessages(message),
  60. stream: true
  61. });
  62. // console.log(completion);
  63. // const signal = response.controller.signal;
  64. // let text = ''
  65. // try {
  66. // const iterator = response.iterator();
  67. // for await (const chunk of iterator) {
  68. // if (chunk) {
  69. // console.log(chunk);
  70. // const decodedChunk = chunk.choices[0].delta.content;
  71. // if (decodedChunk) {
  72. // text += decodedChunk;
  73. // }
  74. // }
  75. // }
  76. // console.log(text);
  77. // } catch (error) {
  78. // if (signal.aborted) {
  79. // console.log("Stream reading aborted");
  80. // } else {
  81. // console.error("Error reading stream:", error);
  82. // }
  83. // }
  84. return response;
  85. // console.log((+new Date() - a) / 1000)
  86. // console.log(completion.choices[0].message.content);
  87. // const response = await fetch(this.apiEndpoint, {
  88. // method: "POST",
  89. // headers: {
  90. // "Content-Type": "application/json",
  91. // Authorization: `Bearer ${this.apiKey}`,
  92. // },
  93. // body: JSON.stringify({
  94. // model: this.model,
  95. // messages: this.formatMessages(message),
  96. // }),
  97. // signal: this.controller.signal,
  98. // });
  99. // if (!response.ok) {
  100. // throw new Error(`API request failed: ${response.status}`);
  101. // }
  102. // const data = await response.json();
  103. // const aiResponse = data.choices[0]?.message?.content;
  104. // const aiResponse = completion.choices[0].message.content
  105. // if (!aiResponse) {
  106. // throw new Error("无效的API响应");
  107. // }
  108. // return aiResponse;
  109. } catch (error) {
  110. if (error.name === "AbortError") {
  111. throw new Error("REQUEST_ABORTED");
  112. }
  113. console.error("API call failed:", error);
  114. throw error;
  115. } finally {
  116. this.controller = null;
  117. }
  118. }
  119. /**
  120. * 格式化消息历史
  121. * @param {string} currentMessage 当前消息
  122. * @returns {Array} 格式化后的消息数组
  123. */
  124. formatMessages(currentMessage) {
  125. const messages = this.context.map((msg) => ({
  126. role: msg.role,
  127. content: msg.content,
  128. }));
  129. // 如果存在页面信息,添加到当前消息的上下文
  130. if (this.currentPageInfo) {
  131. currentMessage = `基于之前总结的页面内容(标题:${this.currentPageInfo.title}),${currentMessage}`;
  132. }
  133. messages.push({
  134. role: "user",
  135. content: currentMessage,
  136. });
  137. return messages;
  138. }
  139. /**
  140. * 更新对话上下文
  141. * @param {string} message 新消息
  142. * @param {string} role 消息角色(user/assistant)
  143. */
  144. updateContext(message, role) {
  145. this.context.push({
  146. role,
  147. content: message,
  148. timestamp: new Date().toISOString(),
  149. });
  150. // 保持上下文长度在合理范围内
  151. if (this.context.length > 10) {
  152. this.context = this.context.slice(-10);
  153. }
  154. }
  155. /**
  156. * 清除对话上下文
  157. */
  158. clearContext() {
  159. this.context = [];
  160. }
  161. /**
  162. * 获取当前对话上下文
  163. * @returns {Array} 对话上下文数组
  164. */
  165. getContext() {
  166. return this.context;
  167. }
  168. // 添加中断方法
  169. abortRequest() {
  170. if (this.controller) {
  171. this.controller.abort();
  172. this.controller = null;
  173. }
  174. }
  175. /**
  176. * 获取页面总结提示词
  177. * @param {Object} pageInfo 页面信息
  178. * @returns {string} 提示词
  179. */
  180. // 4. 按重要性排序
  181. // 5. 如果内容是新闻,需要包含时间、地点、人物等关键信息
  182. // 6. 如果内容是教程,需要突出操作步骤和关键提示
  183. // 7. 如果内容是产品介绍,需要包含主要特点和优势
  184. getSummaryPrompt(pageInfo) {
  185. return `请帮我总结以下网页内容的要点:
  186. 页面标题:${pageInfo.title}
  187. 网站:${pageInfo.siteName}
  188. URL:${pageInfo.url}
  189. 主要内容:
  190. ${pageInfo.mainContent}
  191. 要求:
  192. 1. 帮助我分析表单项与上传excel文件中每一列的关系
  193. 2- 将excel文件中每一列的内容与表单项进行匹配,并生成对应的数组,在一个字段内返回
  194. `;
  195. }
  196. /**
  197. * 设置当前Excel数据
  198. * @param {Object} data Excel数据和元信息
  199. */
  200. setExcelData(data) {
  201. this.currentExcelData = data;
  202. }
  203. /**
  204. * 设置当前页面信息
  205. * @param {Object} pageInfo 页面信息
  206. */
  207. setPageInfo(pageInfo) {
  208. this.currentPageInfo = pageInfo;
  209. }
  210. }
  211. // 确保在DOM加载完成后再创建实例
  212. document.addEventListener("DOMContentLoaded", () => {
  213. // 只有在实例不存在时才创建
  214. if (!window.aiService) {
  215. window.aiService = new AIService();
  216. }
  217. });