ai-service.js 7.0 KB

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