ai-service.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import OpenAI from 'openai'
  2. import hljs from 'highlight.js'
  3. import 'highlight.js/styles/atom-one-dark.css' // 导入一个暗色主题样式
  4. import { getActivePinia } from 'pinia'
  5. import { useMsgStore } from '@/store/modules/msg.ts'
  6. export async function sendMessage(message) {
  7. const pinia = getActivePinia()
  8. if (!pinia) {
  9. throw new Error('No active Pinia instance found')
  10. }
  11. const store = useMsgStore(pinia)
  12. const { openai, AIModel } = store
  13. try {
  14. // 创建新的 AbortController
  15. const controller = new AbortController()
  16. const a = +new Date()
  17. return await openai.chat.completions.create({
  18. //模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
  19. model: AIModel.value,
  20. messages: message,
  21. stream: true
  22. })
  23. } catch (error) {
  24. return { error: error }
  25. } finally {
  26. }
  27. }
  28. export async function getFileContent(data) {
  29. try {
  30. const res = await fetch('http://180.76.147.97:18899/upload_file', {
  31. method: 'post',
  32. body: data
  33. }).then((res) => res.json())
  34. return res
  35. } catch (error) {}
  36. }
  37. export async function getFormKey(data) {
  38. try {
  39. const res = await fetch('http://180.76.147.97:18899/uie', {
  40. method: 'post',
  41. body: JSON.stringify(data)
  42. }).then((res) => res.json())
  43. return res
  44. } catch (error) {}
  45. }
  46. export async function hepl(data) {
  47. try {
  48. const res = await fetch(' http://180.76.147.97:18899/test', {
  49. method: 'post',
  50. body: JSON.stringify(data)
  51. }).then((res) => res.json())
  52. return res
  53. } catch (error) {}
  54. }
  55. function formatMessages(currentMessage, Summary, html) {
  56. const messages = [
  57. {
  58. role: 'user',
  59. content: currentMessage
  60. }
  61. ]
  62. // const messages = this.context.filter(msg => msg.role === 'user').map((msg) => ({
  63. // role: msg.role,
  64. // content: msg.content,
  65. // }));
  66. // console.log(currentMessage, this.currentPageInfo, input);
  67. // // 如果存在页面信息,添加到当前消息的上下文
  68. // if (this.currentPageInfo && !input) {
  69. // currentMessage = `基于之前总结的页面内容(标题:${JSON.stringify(this.currentPageInfo)}),${currentMessage}`;
  70. // }
  71. // messages.push()
  72. if (Summary) return messages
  73. if (html)
  74. messages.unshift({
  75. role: 'user',
  76. content: `页面主要内容${html}`
  77. })
  78. return messages
  79. }
  80. export function getSummaryPrompt(pageInfo) {
  81. // return `请根据所有网页总结以下网页内容的要点:
  82. //
  83. // 页面标题:${pageInfo.title}
  84. // 网站:${pageInfo.siteName}
  85. // URL:${pageInfo.url}
  86. //
  87. // 主要内容:
  88. // ${pageInfo.mainContent}
  89. //
  90. // 要求:
  91. // 1. 用简洁清晰的语言提取3-5个核心要点
  92. // 2. 保持客观中立的语气
  93. // 3. 按重要性排序
  94. // 4. 如果内容是新闻,需要包含时间、地点、人物等关键信息
  95. // 5. 如果内容是教程,需要突出操作步骤和关键提示
  96. // 6. 如果内容是产品介绍,需要包含主要特点和优势
  97. // 7. 返回内容做好换行,以及展示样式
  98. //
  99. // 请以"以下是对该页面内容的总结:"开头,然后用要点的形式列出主要内容。`
  100. return `页面标题:${pageInfo.title};
  101. 网站:${pageInfo.siteName};
  102. 网站地址:${pageInfo.url};
  103. 主要内容:${pageInfo.mainContent}`
  104. }
  105. export function getFileSummaryPrompt(file, name) {
  106. return `请帮我总结以下文件内容的要点:
  107. 文件名称:${name}
  108. 主要内容:
  109. ${file}
  110. 要求:
  111. 1. 用简洁清晰的语言提取3-5个核心要点
  112. 2. 保持客观中立的语气
  113. 3. 按重要性排序
  114. 4. 返回内容做好换行,以及展示样式
  115. 请以"以下是对该文件内容的总结:"开头,然后用要点的形式列出主要内容。`
  116. }
  117. export function formatMessage(text) {
  118. if (!text) return ''
  119. // 用于转义代码块中的 HTML 字符
  120. const escapeHtml = (str) => {
  121. return str
  122. .replace(/&/g, '&')
  123. .replace(/</g, '&lt;')
  124. .replace(/>/g, '&gt;')
  125. .replace(/"/g, '&quot;')
  126. .replace(/'/g, '&#039;')
  127. }
  128. // 处理代码高亮
  129. const highlightCode = (code, language) => {
  130. if (language && language.trim() !== '') {
  131. try {
  132. return hljs.highlight(code, { language: language.trim() }).value
  133. } catch (e) {
  134. // 如果指定的语言不支持,回退到自动检测
  135. return hljs.highlightAuto(code).value
  136. }
  137. }
  138. return hljs.highlightAuto(code).value
  139. }
  140. return (
  141. text
  142. // 处理带有语言标识的代码块
  143. .replace(/```(\w+)?\n([\s\S]*?)```/g, (match, lang, code) => {
  144. const highlightedCode = highlightCode(code, lang || '')
  145. return `<pre style="background-color: #282c34; padding: 10px; border-radius: 5px; overflow-x: auto;"><code class="hljs ${lang || ''}">${highlightedCode}</code></pre>`
  146. })
  147. // 处理不带语言标识的代码块
  148. .replace(/```([\s\S]*?)```/g, (match, code) => {
  149. const highlightedCode = highlightCode(code, '')
  150. return `<pre style="background-color: #282c34; padding: 10px; border-radius: 5px; overflow-x: auto;"><code class="hljs">${highlightedCode}</code></pre>`
  151. })
  152. // 处理行内代码
  153. .replace(
  154. /`([^`]+)`/g,
  155. (match, code) =>
  156. `<code style="background-color: #f0f0f0; padding: 2px 4px; border-radius: 3px;">${escapeHtml(code)}</code>`
  157. )
  158. // 处理标题 (h1 ~ h6)
  159. .replace(/^#{1,6}\s+(.+)$/gm, (match, content) => {
  160. const level = match.trim().split('#').length - 1
  161. return `<h${level}>${content.trim()}</h${level}>`
  162. })
  163. // 处理换行
  164. .replace(/\n/g, '<br>')
  165. // 处理连续空格
  166. .replace(/ {2,}/g, (match) => '&nbsp;'.repeat(match.length))
  167. // 处理粗体
  168. .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
  169. // 处理斜体
  170. .replace(/\*(.*?)\*/g, '<em>$1</em>')
  171. // 处理链接
  172. .replace(
  173. /\[([^\]]+)\]\(([^)]+)\)/g,
  174. '<a href="$2" target="_blank">$1</a>'
  175. )
  176. // 处理无序列表:先将每行列表项转换为 <li> 标签
  177. .replace(/^[*-]\s+(.+)$/gm, '<li>$1</li>')
  178. // 然后将连续的 <li> 包裹在 <ul> 中
  179. .replace(/(<li>.*<\/li>)/gs, '<ul>$1</ul>')
  180. // 处理有序列表:先将每行列表项转换为 <li> 标签
  181. .replace(/^\d+\.\s+(.+)$/gm, '<li>$1</li>')
  182. // 然后将连续的 <li> 包裹在 <ol> 中
  183. .replace(/(<li>.*<\/li>)/gs, '<ol>$1</ol>')
  184. // 处理分隔线
  185. .replace(/^---+$/gm, '<hr>')
  186. // 处理引用
  187. .replace(/^>\s+(.+)$/gm, '<blockquote>$1</blockquote>')
  188. )
  189. }
  190. export function buildExcelUnderstandingPrompt(data, fileName, pageInfo) {
  191. // if (!data || data.length < 2) {
  192. // return "这是一个空的Excel文件,请检查文件内容。";
  193. // }
  194. // const headers = data[0];
  195. // const rows = data.slice(1);
  196. // data[0].forEach((header, i) => {
  197. // if (!this.excelData[header]) this.excelData[header] = []
  198. // this.excelData[header].push(data[1][i])
  199. // })
  200. return `我将向你展示一个通过SheetJS库读取的Excel文件内容和一个form表单。请帮我理解这些数据:
  201. 文件名:${fileName}
  202. 列标题:${data.join(', ')}
  203. 表单内容:
  204. ${pageInfo}
  205. 要求:
  206. 1. 请根据表单中的表单项和列标题进行匹配,一定以表单为准,列标题只能匹配一次,没有和列标题匹配到的表单项不返回!
  207. 2. 生成表单项与列标题对应的数组,并使用findBy告诉我通过表单项的什么字段信息匹配到的,使用findByValue告诉我匹配到的表单项字段值,使用excelColumn字段告诉我excel文件中列标题的值。
  208. 3. 表单项有id根据id匹配,findBy是id,并通过findByValue告诉我id的值,没有id根据label匹配,findBy是label,并通过findByValue给我label元素的值,没有label根据placeholder匹配,findBy是placeholder,并通过findByValue告诉我placeholder的值,没有placeholder,再根据其他内容匹配
  209. 4. 并去除没有匹配到的表单项和excel文件中没有匹配到的列,
  210. 5. 通过type字段告诉我输入项的类型,表单项有label,通过label字段返回label的值。
  211. . 返回json格式数组,不要返回其他任何内容`
  212. }
  213. // 5. 如果表单项有label标签,同时返回label, 通过label字段告诉我label元素的文本
  214. export function buildObjPrompt(obj, pageInfo) {
  215. return `我将向你展示一个对象和一个form表单。请帮我理解这些数据:
  216. 对象:${JSON.stringify(obj)}
  217. 表单内容:
  218. ${pageInfo}
  219. 要求:
  220. 1. 请根据表单中的表单项和对象的key进行匹配,一定以表单为准,对象的key只能匹配一次,使用data字段返回匹配到的key对应的value值。没有和对象匹配到的表单项不返回!
  221. 2. 生成表单项与对象的key对应的数组,并使用findBy告诉我通过表单项的什么字段信息匹配到的,使用findByValue告诉我匹配到的表单项字段值,使用excelColumn字段告诉我对象key的值。
  222. 3. 表单项有label,根据label匹配,没有label根据placeholder匹配,没有placeholder,根据id匹配,再根据其他内容匹配
  223. 4. 表单中没有和对象的key匹配到的表单项不要返回!。
  224. 5. 通过type字段告诉我输入项的类型,如果对象中key对应的是日期,type统一是date。
  225. 6. 表单项有label,通过label字段返回label的值。
  226. 7. 将匹配到的所有对象放在数组中返回,返回json格式返回数组,不要返回其他任何内容`
  227. }
  228. function escapeHtml(html) {
  229. const div = document.createElement('div')
  230. div.textContent = html
  231. return div.innerHTML
  232. }
  233. export async function modelFileUpload(file) {
  234. const pinia = getActivePinia()
  235. if (!pinia) {
  236. throw new Error('No active Pinia instance found')
  237. }
  238. const store = useMsgStore(pinia)
  239. const { openai, AIModel } = store
  240. const fileObject = await openai.files.create(
  241. {
  242. file,
  243. purpose: 'file-extract'
  244. },
  245. {
  246. headers: {
  247. 'Content-Type': 'multipart/form-data'
  248. }
  249. }
  250. )
  251. return fileObject.id
  252. }