Quellcode durchsuchen

强生:demo1问答和陪练合并成一个页面,两个模型

liyanbo vor 1 Monat
Ursprung
Commit
3992e2ed71

+ 0 - 5
src/router/index.js

@@ -192,10 +192,6 @@ const routes = [
     path: '/qs-demo1',
     component: () => import('../views/qsfl/qsflDemo1.vue')
   },
-  {
-    path: '/qs-demo1-2',
-    component: () => import('../views/qsfl/qsflDemo1-2.vue')
-  },
   {
     path: '/qs-demo2',
     component: () => import('../views/qsfl/qsflDemo2.vue')
@@ -334,7 +330,6 @@ router.beforeEach(async (to, from, next) => {
   // 如果未登录且不是允许访问的页面,重定向到登录页
   if (!isLoggedIn && !allowedPages.includes(to.path)) {
     if (to.path === '/qs-demo1')next('/qsfl-demo1')
-    if (to.path === '/qs-demo1-2')next('/qsfl-demo1-2')
     if (to.path === '/qs-demo2')next('/qsfl-demo2')
     if (to.path === '/qs-demo3')next('/qsfl-demo3')
     next('/login')

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 840
src/views/qsfl/components/AiQAChat1-2.vue


+ 74 - 33
src/views/qsfl/components/AiQAChat1.vue

@@ -14,6 +14,7 @@ const props = defineProps({
 
 const DEFAULT_AI_QA_CONFIG = Object.freeze({
   roleId: null,
+  entries: [],
   maxFileSizeMB: 20,
   maxFiles: Infinity,
   maxTotalFileSizeMB: Infinity,
@@ -51,8 +52,14 @@ const AI_QA_CONFIG = Object.freeze({ ...DEFAULT_AI_QA_CONFIG, ...props.config })
 const uploadEnabled = computed(() => AI_QA_CONFIG.enableFileUpload === true)
 const hasInputLengthLimit = computed(() => Number.isFinite(AI_QA_CONFIG.maxInputLength) && AI_QA_CONFIG.maxInputLength >= 0)
 const referenceScriptEnabled = computed(() => AI_QA_CONFIG.enableReferenceScript === true)
-const conversationStorageKey = `ai-qa-conversation-id:${AI_QA_CONFIG.roleId}`
-const conversationHistoryStorageKey = `ai-qa-conversation-history:${AI_QA_CONFIG.roleId}`
+const roleEntries = computed(() => {
+  const entries = Array.isArray(AI_QA_CONFIG.entries)
+    ? AI_QA_CONFIG.entries.filter((entry) => entry?.key && entry?.label && entry?.roleId)
+    : []
+  return entries.length || !AI_QA_CONFIG.roleId
+    ? entries
+    : [{ key: 'qa', label: '智能问答', roleId: AI_QA_CONFIG.roleId }]
+})
 
 const input = ref('')
 const referenceScript = ref('')
@@ -73,7 +80,7 @@ const conversationId = ref(null)
 const conversations = ref([])
 const historyDrawerOpen = ref(true)
 const isCreatingConversation = ref(false)
-const initialConversationType = ref('qa')
+const activeEntry = ref(roleEntries.value.length === 1 ? roleEntries.value[0] : null)
 const isSending = ref(false)
 const isVoiceRecording = ref(false)
 const isTyping = ref(false)
@@ -94,28 +101,34 @@ let persistTimer = null
 const hasConversation = computed(() => Boolean(conversationId.value))
 const hasMessages = computed(() => messages.value.length > 0)
 const canSend = computed(() => hasConversation.value && (input.value.trim() || (uploadEnabled.value && attachments.value.length)) && !isSending.value && !isTyping.value)
-const welcomeTitle = computed(() => initialConversationType.value === 'practice' ? '您要去拜访谁呢?' : '有什么可以帮您?')
+const activeRoleId = computed(() => activeEntry.value?.roleId || AI_QA_CONFIG.roleId || null)
+const welcomeTitle = computed(() => activeEntry.value?.key === 'practice' ? '您要去拜访谁呢?' : '有什么可以帮您?')
+const getConversationStorageKey = (roleId) => `ai-qa-conversation-id:${roleId}`
+const getConversationHistoryStorageKey = (roleId) => `ai-qa-conversation-history:${roleId}`
 
-const getCachedConversationId = () => {
+const getCachedConversationId = (roleId = activeRoleId.value) => {
+  if (!roleId) return null
   try {
-    return localStorage.getItem(conversationStorageKey)
+    return localStorage.getItem(getConversationStorageKey(roleId))
   } catch (error) {
     console.warn('读取 AI 会话缓存失败', error)
     return null
   }
 }
 
-const cacheConversationId = (id) => {
+const cacheConversationId = (id, roleId = activeRoleId.value) => {
+  if (!roleId) return
   try {
-    localStorage.setItem(conversationStorageKey, id)
+    localStorage.setItem(getConversationStorageKey(roleId), id)
   } catch (error) {
     console.warn('保存 AI 会话缓存失败', error)
   }
 }
 
-const clearCachedConversationId = () => {
+const clearCachedConversationId = (roleId = activeRoleId.value) => {
+  if (!roleId) return
   try {
-    localStorage.removeItem(conversationStorageKey)
+    localStorage.removeItem(getConversationStorageKey(roleId))
   } catch (error) {
     console.warn('清除 AI 会话缓存失败', error)
   }
@@ -135,17 +148,22 @@ const serializeMessages = () => messages.value.map((message) => ({
   attachments: message.attachments?.map(({ id, name, size, type, url }) => ({ id, name, size, type, url })) || []
 }))
 
-const saveConversationList = () => {
+const saveConversationList = (roleId = activeRoleId.value) => {
+  if (!roleId) return
   try {
-    localStorage.setItem(conversationHistoryStorageKey, JSON.stringify(conversations.value))
+    localStorage.setItem(getConversationHistoryStorageKey(roleId), JSON.stringify(conversations.value))
   } catch (error) {
     console.warn('保存 AI 历史记录失败', error)
   }
 }
 
-const loadConversationList = () => {
+const loadConversationList = (roleId = activeRoleId.value) => {
+  if (!roleId) {
+    conversations.value = []
+    return
+  }
   try {
-    const stored = JSON.parse(localStorage.getItem(conversationHistoryStorageKey) || '[]')
+    const stored = JSON.parse(localStorage.getItem(getConversationHistoryStorageKey(roleId)) || '[]')
     conversations.value = Array.isArray(stored)
         ? stored.filter((item) => item?.id).map((item) => ({
           id: item.id,
@@ -199,18 +217,18 @@ const formatHistoryTime = (value) => {
   return `${date.getMonth() + 1}/${date.getDate()}`
 }
 
-const makeConversation = async () => {
+const makeConversation = async (roleId = activeRoleId.value) => {
   if (conversationId.value || isCreatingConversation.value) return conversationId.value
   isCreatingConversation.value = true
   try {
-    if (!AI_QA_CONFIG.roleId) {
+    if (!roleId) {
       Message().warning('请先选择数字人角色!', true)
       return null
     }
-    const result = await props.createDialogueApi({ roleId: AI_QA_CONFIG.roleId })
+    const result = await props.createDialogueApi({ roleId })
     conversationId.value = result?.data
     if (!conversationId.value) throw new Error('未获取到会话标识')
-    cacheConversationId(conversationId.value)
+    cacheConversationId(conversationId.value, roleId)
     return conversationId.value
   } catch (error) {
     console.error('创建 AI 会话失败', error)
@@ -221,9 +239,11 @@ const makeConversation = async () => {
   }
 }
 
-const createInitialConversation = (type) => {
-  initialConversationType.value = type
-  return makeConversation()
+const createInitialConversation = (entry) => {
+  if (!entry?.roleId || isCreatingConversation.value) return null
+  activeEntry.value = entry
+  loadConversationList(entry.roleId)
+  return makeConversation(entry.roleId)
 }
 
 const restoreActiveConversation = () => {
@@ -427,18 +447,36 @@ setOnPlaybackComplete(() => {
 const resetDigitalHumanVideo = () => {
   const video = digitalHumanVideoRef.value
   if (!video) return
+  if (isAudioSpeaking.value) return
   video.pause()
   if (video.readyState >= HTMLMediaElement.HAVE_METADATA) video.currentTime = 0
 }
 
-watch(isAudioSpeaking, async (speaking) => {
-  await nextTick()
+const playDigitalHumanVideo = async () => {
   const video = digitalHumanVideoRef.value
   if (!video) return
+  if (video.readyState < HTMLMediaElement.HAVE_METADATA) return
+  video.currentTime = 0
+  video.playbackRate = 1.5
+  try {
+    await video.play()
+  } catch (error) {
+    console.warn('数字人视频播放失败', error)
+  }
+}
+
+const handleDigitalHumanVideoLoaded = () => {
+  if (isAudioSpeaking.value) {
+    playDigitalHumanVideo()
+  } else {
+    resetDigitalHumanVideo()
+  }
+}
+
+watch(isAudioSpeaking, async (speaking) => {
+  await nextTick()
   if (speaking) {
-    video.currentTime = 0
-    video.playbackRate = 1.5
-    video.play().catch((error) => console.warn('数字人视频播放失败', error))
+    playDigitalHumanVideo()
   } else {
     resetDigitalHumanVideo()
   }
@@ -646,6 +684,7 @@ const toggleVoicePlayback = () => {
 }
 
 const startNewConversation = () => {
+  const previousRoleId = activeRoleId.value
   persistConversation()
   stopResponse()
   messages.value = []
@@ -653,9 +692,10 @@ const startNewConversation = () => {
   input.value = ''
   referenceScript.value = ''
   referenceScriptVisible.value = false
-  initialConversationType.value = 'qa'
   conversationId.value = null
-  clearCachedConversationId()
+  clearCachedConversationId(previousRoleId)
+  activeEntry.value = roleEntries.value.length === 1 ? roleEntries.value[0] : null
+  if (!activeEntry.value) conversations.value = []
   userHasScrolled.value = false
   nextTick(() => textareaRef.value?.focus())
 }
@@ -667,8 +707,10 @@ watch(messages, () => {
 watch(input, resizeTextarea)
 
 onMounted(() => {
-  loadConversationList()
-  restoreActiveConversation()
+  if (activeEntry.value) {
+    loadConversationList(activeEntry.value.roleId)
+    restoreActiveConversation()
+  }
   resizeTextarea()
 })
 
@@ -727,7 +769,7 @@ onUnmounted(() => {
               playsinline
               preload="auto"
               :loop="isAudioSpeaking"
-              @loadedmetadata="resetDigitalHumanVideo"
+              @loadedmetadata="handleDigitalHumanVideoLoaded"
             ></video>
           </div>
           <div class="digital-human-status"><i></i>{{ isAudioSpeaking ? '正在为您播报' : '在线为您服务' }}</div>
@@ -738,8 +780,7 @@ onUnmounted(() => {
             <p class="eyebrow">AI ASSISTANT</p><h1>开始一次新的问答</h1>
             <p class="welcome-copy">向非临 AI 助教描述您的问题,获得及时的智能解答。</p>
             <div class="initial-conversation-actions">
-              <button class="initial-conversation-button" type="button" :disabled="isCreatingConversation" @click="createInitialConversation('qa')"><el-icon><Plus /></el-icon>智能问答</button>
-<!--              <button class="initial-conversation-button" type="button" :disabled="isCreatingConversation" @click="createInitialConversation('practice')"><el-icon><Plus /></el-icon>智能陪练</button>-->
+              <button v-for="entry in roleEntries" :key="entry.key" class="initial-conversation-button" type="button" :disabled="isCreatingConversation" @click="createInitialConversation(entry)"><el-icon><Plus /></el-icon>{{ entry.label }}</button>
             </div>
           </div>
 

+ 0 - 18
src/views/qsfl/qsflDemo1-2.vue

@@ -1,18 +0,0 @@
-<template>
-  <AiQAChat12
-    :config="qaConfig"
-    :create-dialogue-api="CreateDialogue"
-    :send-chat-stream-api="sendChatMessageStream"
-  />
-</template>
-
-<script setup>
-import AiQAChat12 from './components/AiQAChat1-2.vue'
-import { CreateDialogue, sendChatMessageStream } from '@/api/questions.js'
-
-const qaConfig = Object.freeze({
-  roleId: 266,
-  enableFileUpload: false,
-  suggestions: []
-})
-</script>

+ 4 - 1
src/views/qsfl/qsflDemo1.vue

@@ -11,7 +11,10 @@ import AiQAChat from './components/AiQAChat1.vue'
 import { CreateDialogue, sendChatMessageStream } from '@/api/questions.js'
 
 const qaConfig = Object.freeze({
-  roleId: 263,
+  entries: [
+    { key: 'qa', label: '智能问答', roleId: 263 },
+    { key: 'practice', label: '智能陪练', roleId: 266 }
+  ],
   enableFileUpload: false,
   suggestions: []
 })

Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.