AIWorkModal.vue 4.7 KB

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