index.vue 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. <template>
  2. <view class="chat-page">
  3. <scroll-view scroll-y="true" class="chat-container" :scroll-into-view="scrollIntoView" scroll-with-animation>
  4. <view v-for="(msg, index) in messages" :key="index" :id="'msg-' + index" class="message" :class="msg.role">
  5. <text>{{ msg.content }}</text>
  6. </view>
  7. </scroll-view>
  8. <view class="input-bar">
  9. <input v-model="inputText" :disabled="loading" class="chat-input" placeholder="请输入你的问题..." @confirm="sendMessage" />
  10. <button :disabled="loading || !inputText.trim()" @click="sendMessage" class="send-button">发送</button>
  11. </view>
  12. </view>
  13. </template>
  14. <script setup>
  15. import { ref, nextTick } from 'vue'
  16. import {
  17. chartStream
  18. } from '@/api/services/chart.js'
  19. // 响应式数据
  20. const inputText = ref('')
  21. const messages = ref([])
  22. const loading = ref(false)
  23. const scrollIntoView = ref('')
  24. // 发送消息
  25. const sendMessage = () => {
  26. const content = inputText.value.trim()
  27. if (!content || loading.value) return
  28. messages.value.push({ role: 'user', content })
  29. inputText.value = ''
  30. scrollToBottom()
  31. const aiMsg = { role: 'ai', content: '' }
  32. messages.value.push(aiMsg)
  33. loading.value = true
  34. scrollToBottom()
  35. const requestBody = {
  36. query: content,
  37. user: 'test_user_123' // 实际项目中应为真实用户ID
  38. }
  39. uni.request({
  40. url: 'http://localhost:9203/dify/chat/stream', // ⚠️ 换成你自己的后端
  41. method: 'POST',
  42. header: {
  43. 'Content-Type': 'application/json'
  44. },
  45. data: requestBody,
  46. success: (res) => {
  47. const reader = res.data.getReader?.()
  48. const decoder = new TextDecoder()
  49. let buffer = ''
  50. const readStream = () => {
  51. reader.read().then(({ done, value }) => {
  52. if (done) {
  53. loading.value = false
  54. return
  55. }
  56. buffer += decoder.decode(value, { stream: true })
  57. const events = buffer.split('\n\n')
  58. buffer = events.pop() // 保留未完成部分
  59. events.forEach(evt => {
  60. if (!evt.trim()) return
  61. const lines = evt.split('\n')
  62. let eventData = ''
  63. lines.forEach(line => {
  64. if (line.startsWith('data:')) {
  65. eventData = line.replace(/^data:\s*/, '')
  66. }
  67. })
  68. try {
  69. const json = JSON.parse(eventData)
  70. if (json.event === 'message') {
  71. aiMsg.content += json.answer || ''
  72. scrollToBottom()
  73. } else if (json.event === 'message_end') {
  74. loading.value = false
  75. }
  76. } catch (e) {
  77. console.warn('解析流数据失败:', e)
  78. }
  79. })
  80. readStream()
  81. })
  82. }
  83. readStream()
  84. },
  85. fail: (err) => {
  86. loading.value = false
  87. aiMsg.content = '[请求失败: ' + err.errMsg + ']'
  88. }
  89. })
  90. }
  91. const scrollToBottom = () => {
  92. nextTick(() => {
  93. scrollIntoView.value = 'msg-' + (messages.value.length - 1)
  94. })
  95. }
  96. </script>
  97. <style scoped>
  98. .chat-page {
  99. display: flex;
  100. flex-direction: column;
  101. height: 100vh;
  102. background-color: #f3f3f3;
  103. }
  104. .chat-container {
  105. flex: 1;
  106. padding: 20rpx;
  107. overflow-y: auto;
  108. background-color: #f3f3f3;
  109. }
  110. .message {
  111. margin-bottom: 20rpx;
  112. padding: 20rpx;
  113. border-radius: 20rpx;
  114. max-width: 80%;
  115. word-break: break-word;
  116. }
  117. .message.user {
  118. align-self: flex-end;
  119. background-color: #dfffd6;
  120. }
  121. .message.ai {
  122. align-self: flex-start;
  123. background-color: #ffffff;
  124. }
  125. .input-bar {
  126. display: flex;
  127. align-items: center;
  128. padding: 10rpx;
  129. border-top: 1px solid #ddd;
  130. background-color: #fff;
  131. }
  132. .chat-input {
  133. flex: 1;
  134. padding: 10rpx;
  135. border: 1px solid #ccc;
  136. border-radius: 10rpx;
  137. }
  138. .send-button {
  139. margin-left: 10rpx;
  140. background-color: #4caf50;
  141. color: white;
  142. border: none;
  143. border-radius: 10rpx;
  144. padding: 10rpx 20rpx;
  145. }
  146. </style>