ai-service.js 10 KB

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