AIWorkModal.vue 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. <template>
  2. <Modal :visible="visible" :width="720" :closeButton="true" @update:visible="val => emit('update:visible', val)">
  3. <div class="sddd_box">
  4. <template v-for="(item,index) in messageList" :key="item.id">
  5. <div v-if="item.messages || item.imageUrls">
  6. <div class="messageNode">
  7. <div class="mn_title">节点{{ index+1 }}</div>
  8. <div class="mn_content">
  9. <template v-for="(item2,index2) in item.messages" :key="`${index}-${index2}`">
  10. <div>
  11. <div class="na_m_item" v-if="item2.role == 'user' && item2.content">
  12. <div class="na_m_i_name">
  13. {{ item2.sender }}
  14. </div>
  15. <div class="na_m_i_content" v-html="item2.content"></div>
  16. </div>
  17. <div class="na_m_item" v-if="item2.role == 'assistant' && item2.content">
  18. <div class="na_m_i_name aiName">
  19. {{ item2.sender }}
  20. </div>
  21. <div class="na_m_i_content" v-html="item2.content"></div>
  22. </div>
  23. </div>
  24. </template>
  25. <template v-if="item.imageUrls" v-for="(item3,index3) in item.imageUrls" :key="`${index}-${index3}`">
  26. <div class="na_m_item">
  27. <div class="na_m_i_name">
  28. {{ item.type }}
  29. </div>
  30. <div class="na_m_i_content">
  31. <img style="height: 100px;width: auto;" :src="item3" />
  32. </div>
  33. </div>
  34. </template>
  35. </div>
  36. </div>
  37. </div>
  38. </template>
  39. </div>
  40. </Modal>
  41. </template>
  42. <script lang="ts" setup>
  43. import { computed, watch, ref} from 'vue'
  44. import Modal from '@/components/Modal.vue'
  45. import MarkdownIt from 'markdown-it'
  46. import useImport from '@/hooks/useImport'
  47. // md.render(_addText)
  48. const props = defineProps<{
  49. visible: boolean
  50. work: any
  51. }>()
  52. const emit = defineEmits<{
  53. (e: 'update:visible', v: boolean): void
  54. }>()
  55. const md = new MarkdownIt()
  56. const { getFile } = useImport()
  57. const visible = computed({
  58. get: () => props.visible,
  59. set: (v: boolean) => emit('update:visible', v)
  60. })
  61. // 存储解析后的内容
  62. const messageList = ref<any[]>([])
  63. // 判断是否是 URL 链接
  64. const isUrl = (str: string): boolean => {
  65. try {
  66. const url = new URL(str)
  67. return (url.protocol === 'http:' || url.protocol === 'https:') && str.includes('json')
  68. }
  69. catch {
  70. return false
  71. }
  72. }
  73. // 从链接获取文件内容
  74. const loadContentFromUrl = async (url: string): Promise<string> => {
  75. try {
  76. const fileData = await getFile(url)
  77. if (fileData && fileData.data) {
  78. // 将 ArrayBuffer 转为字符串
  79. const uint8Array = new Uint8Array(fileData.data)
  80. const jsonStr = new TextDecoder('utf-8').decode(uint8Array)
  81. return jsonStr
  82. }
  83. return ''
  84. }
  85. catch (error) {
  86. console.error('获取文件内容失败:', error)
  87. return ''
  88. }
  89. }
  90. // 处理内容
  91. const processContent = async (content: string) => {
  92. let contentToParse = content
  93. // 如果是链接,先获取文件内容
  94. if (isUrl(content)) {
  95. contentToParse = await loadContentFromUrl(content)
  96. }
  97. if (!contentToParse) {
  98. return []
  99. }
  100. try {
  101. const messageListRaw = JSON.parse(contentToParse)
  102. messageListRaw.forEach((item:any) => {
  103. if (item.messages && item.messages.length) {
  104. item.messages.forEach((item2: any) => {
  105. // 如果已经包含html标签则不再渲染
  106. if (!/^(\s*<[^>]+>.*<\/[^>]+>\s*|<[^>]+\/>\s*)$/s.test(item2.content.trim())) {
  107. item2.content = md.render(item2.content)
  108. item2.content = item2.content.replace(/&lt;/g, '<').replace(/&quot;/g, '"').replace(/&gt;/g, '>')
  109. }
  110. })
  111. }
  112. })
  113. return messageListRaw
  114. }
  115. catch (error) {
  116. console.error('解析内容失败:', error)
  117. return []
  118. }
  119. }
  120. // 监听 work 和 visible 变化
  121. watch(
  122. () => [props.work, props.visible],
  123. async () => {
  124. if (props.work && props.visible) {
  125. messageList.value = await processContent(props.work.content)
  126. }
  127. else {
  128. messageList.value = []
  129. }
  130. },
  131. { immediate: true }
  132. )
  133. // watch(() => props.visible, () => {
  134. // if (props.work && props.visible) {
  135. // const messageListRaw = JSON.parse(props.work.content)
  136. // console.log(messageListRaw)
  137. // const messages: any[] = messageListRaw
  138. // messageList.value = messages
  139. // }
  140. // })
  141. </script>
  142. <style scoped>
  143. .sddd_box {
  144. width: 100%;
  145. height: 60vh;
  146. overflow: auto;
  147. background-color: #f4f4f4;
  148. box-sizing: border-box;
  149. padding: 10px;
  150. gap: 20px;
  151. }
  152. .sddd_bottom {
  153. display: flex;
  154. margin-top: 20px;
  155. justify-content: flex-end;
  156. }
  157. .na_m_item {
  158. width: 100%;
  159. height: auto;
  160. margin: 10px 0;
  161. }
  162. .na_m_i_name {
  163. width: fit-content;
  164. max-width: 100%;
  165. height: auto;
  166. box-sizing: border-box;
  167. padding: 10px 10px;
  168. display: flex;
  169. align-items: center;
  170. border-radius: 10px 10px 0 0;
  171. background-color: #9747ff;
  172. color: #fff;
  173. text-overflow: ellipsis;
  174. overflow: hidden;
  175. white-space: nowrap;
  176. }
  177. .aiName {
  178. background-color: #0560fc;
  179. }
  180. .na_m_i_content {
  181. padding: 10px;
  182. border: solid 1px #e7e7e7;
  183. box-shadow: 0 4px 20px 0 rgba(0, 0, 0, 0.1);
  184. border-radius: 0 0 12px 12px;
  185. background-color: #fff;
  186. }
  187. .na_m_i_content :deep(img){
  188. max-width: 100%;
  189. }
  190. .messageNode{
  191. width: 100%;
  192. height: auto;
  193. box-sizing: border-box;
  194. padding: 10px 10px;
  195. border: .5px solid #e7e7e7;
  196. border-radius: 12px;
  197. gap: 10px;
  198. margin-top: 20px;
  199. }
  200. </style>