瀏覽代碼

优化语音输入样式更新id

liyanbo 1 月之前
父節點
當前提交
282d4e3dcd

+ 15 - 7
src/components/ai/voice/VoiceInputDoubao.vue

@@ -69,24 +69,31 @@ defineExpose({
 :deep(.speech-btn .el-icon) { color: currentColor !important; font-size: 18px !important; }
 
 :deep(.speech-btn.recording) {
-  display: flex !important;
-  width: 86px;
-  padding: 0 9px !important;
+  display: grid !important;
+  grid-template-columns: 50px max-content;
+  width: 104px;
+  padding: 0 8px !important;
   gap: 5px !important;
+  justify-content: center;
   color: #fff;
   background: linear-gradient(135deg, #e44250, #b91f47) !important;
   box-shadow: 0 7px 20px rgba(209, 45, 70, .3) !important;
   animation: voice-breath 1.35s ease-in-out infinite;
 }
 
-:deep(.speech-btn.recording .waveform-container) { max-width: 43px !important; min-width: 43px; }
+:deep(.speech-btn.recording .waveform-container) {
+  width: 50px;
+  min-width: 50px;
+  max-width: none !important;
+  overflow: visible;
+}
 :deep(.speech-btn.recording .countdown-text) { color: #fff !important; font-size: 12px !important; }
 :deep(.speech-btn.loading) { color: #c7dbff; background: rgba(137, 180, 255, .12) !important; }
 :deep(.loading-spinner) { border-color: rgba(194, 218, 255, .28) !important; border-top-color: #c6dcff !important; }
 
 .voice-status {
   position: absolute;
-  right: 0;
+  left: 50%;
   bottom: calc(100% + 9px);
   padding: 5px 8px;
   border: 1px solid rgba(167, 198, 250, .24);
@@ -98,12 +105,13 @@ defineExpose({
   white-space: nowrap;
   pointer-events: none;
   opacity: 0;
-  transform: translateY(3px);
+  transform: translate(-50%, 3px);
   transition: .2s ease;
+  z-index: 1;
 }
 
 .doubao-voice-input:hover .voice-status,
-.doubao-voice-input.is-recording .voice-status { opacity: 1; transform: translateY(0); }
+.doubao-voice-input.is-recording .voice-status { opacity: 1; transform: translate(-50%, 0); }
 .doubao-voice-input.is-recording .voice-status { border-color: rgba(255, 160, 172, .36); }
 
 @keyframes voice-breath { 50% { transform: translateY(-1px); box-shadow: 0 9px 26px rgba(222, 58, 83, .5) !important; } }

+ 1 - 1
src/views/qsfl/LoginQsfl.vue

@@ -23,7 +23,7 @@ import { autoLogin } from '@/utils/loginUtils.js'
 const QSFL_LOGIN_CONFIG = Object.freeze({
   tenantName: import.meta.env.VITE_APP_TITLE,
   username: 'test',
-  password: 'test',
+  password: 'test@2026',
   redirectPath: '/ai-qa'
 })
 

+ 1 - 1
src/views/qsfl/aiQA.vue

@@ -11,7 +11,7 @@ import AiQAChat from './components/AiQAChat.vue'
 import { CreateDialogue, sendChatMessageStream } from '@/api/questions.js'
 
 const qaConfig = Object.freeze({
-  roleId: 258,
+  roleId: 263,
   enableFileUpload: false,
   suggestions: ['开始陪练', '你都知道什么?', '我需要知识点....']
 })

+ 1 - 1
src/views/qsfl/aiQANew.vue

@@ -11,7 +11,7 @@ import AiQAChat from './components/AiQAChat.vue'
 import { createQsAiDialogue, sendQsAiChatMessageStream } from '@/api/qsAiQuestions.js'
 
 const qaConfig = Object.freeze({
-  roleId: 259,
+  roleId: 264,
   enableFileUpload: true,
   maxFiles: 3,
   maxFileSizeMB: 10,

+ 42 - 6
src/views/qsfl/components/AiQAChat.vue

@@ -55,6 +55,8 @@ const DEFAULT_AI_QA_CONFIG = Object.freeze({
 const AI_QA_CONFIG = Object.freeze({ ...DEFAULT_AI_QA_CONFIG, ...props.config })
 const uploadEnabled = computed(() => AI_QA_CONFIG.enableFileUpload === true)
 const uploadUrl = computed(() => `${AI_QA_CONFIG.apiBaseUrl}${AI_QA_CONFIG.uploadPath}`)
+const hasInputLengthLimit = computed(() => Number.isFinite(AI_QA_CONFIG.maxInputLength) && AI_QA_CONFIG.maxInputLength >= 0)
+const conversationStorageKey = `ai-qa-conversation-id:${AI_QA_CONFIG.roleId}`
 const input = ref('')
 const textareaRef = ref(null)
 const messages = ref([])
@@ -142,8 +144,38 @@ const switchQaPage = (path) => {
   if (route.path !== path) router.push(path)
 }
 
+const getCachedConversationId = () => {
+  try {
+    return localStorage.getItem(conversationStorageKey)
+  } catch (error) {
+    console.warn('读取 AI 会话缓存失败:', error)
+    return null
+  }
+}
+
+const cacheConversationId = (id) => {
+  try {
+    localStorage.setItem(conversationStorageKey, id)
+  } catch (error) {
+    console.warn('保存 AI 会话缓存失败:', error)
+  }
+}
+
+const clearCachedConversationId = () => {
+  try {
+    localStorage.removeItem(conversationStorageKey)
+  } catch (error) {
+    console.warn('清除 AI 会话缓存失败:', error)
+  }
+}
+
 const makeConversation = async () => {
   if (conversationId.value || isCreatingConversation.value) return conversationId.value
+  const cachedConversationId = getCachedConversationId()
+  if (cachedConversationId) {
+    conversationId.value = cachedConversationId
+    return conversationId.value
+  }
   isCreatingConversation.value = true
 
   try {
@@ -154,6 +186,7 @@ const makeConversation = async () => {
     const result = await props.createDialogueApi({ roleId: AI_QA_CONFIG.roleId })
     conversationId.value = result?.data
     if (!conversationId.value) throw new Error('未获取到会话标识')
+    cacheConversationId(conversationId.value)
     return conversationId.value
   } catch (error) {
     console.error('创建 AI 会话失败:', error)
@@ -165,7 +198,7 @@ const makeConversation = async () => {
 }
 
 const chooseSuggestion = (suggestion) => {
-  input.value = suggestion.slice(0, AI_QA_CONFIG.maxInputLength)
+  input.value = hasInputLengthLimit.value ? suggestion.slice(0, AI_QA_CONFIG.maxInputLength) : suggestion
   nextTick(() => document.querySelector('.ai-qa-textarea')?.focus())
 }
 
@@ -242,7 +275,8 @@ const handleKeydown = (event) => {
 }
 
 const onVoiceRecognized = ({ processedText }) => {
-  input.value = (processedText || input.value).slice(0, AI_QA_CONFIG.maxInputLength)
+  const value = processedText || input.value
+  input.value = hasInputLengthLimit.value ? value.slice(0, AI_QA_CONFIG.maxInputLength) : value
 }
 
 const onRecordingStatusChanged = (recording) => {
@@ -257,7 +291,7 @@ const formatSize = (size) => {
 }
 
 const getFileExtension = (fileName) => fileName.split('.').pop()?.toLowerCase() || ''
-const isSupportedFile = (file) => file.type.startsWith('image/') || AI_QA_CONFIG.acceptedFileExtensions.includes(getFileExtension(file.name))
+const isSupportedFile = (file) => file.type?.startsWith('image/') || AI_QA_CONFIG.acceptedFileExtensions.includes(getFileExtension(file.name))
 const getAttachmentSize = () => attachments.value.reduce((total, file) => total + file.size, 0)
 
 const handleFileChange = async (event) => {
@@ -308,7 +342,7 @@ const removeAttachment = (index) => attachments.value.splice(index, 1)
 
 const sendMessage = async () => {
   if (!canSend.value) return
-  if (input.value.length > AI_QA_CONFIG.maxInputLength) {
+  if (hasInputLengthLimit.value && input.value.length > AI_QA_CONFIG.maxInputLength) {
     Message().warning(`单次提问最多输入 ${AI_QA_CONFIG.maxInputLength.toLocaleString()} 个字符`, true)
     return
   }
@@ -448,13 +482,15 @@ const toggleDigitalHuman = () => {
   }
 }
 
-const startNewConversation = () => {
+const startNewConversation = async () => {
   stopResponse()
   messages.value = []
   attachments.value = []
   input.value = ''
   conversationId.value = null
+  clearCachedConversationId()
   userHasScrolled.value = false
+  await makeConversation()
 }
 
 watch(messages, () => scrollToBottom(), { deep: true })
@@ -587,7 +623,7 @@ onUnmounted(() => {
             v-model="input"
             class="ai-qa-textarea"
             rows="1"
-            :maxlength="AI_QA_CONFIG.maxInputLength"
+            :maxlength="hasInputLengthLimit ? AI_QA_CONFIG.maxInputLength : undefined"
             :placeholder="AI_QA_CONFIG.inputHint"
             :disabled="isSending"
             @keydown="handleKeydown"