FanHong 5 horas atrás
pai
commit
faddeadc01

+ 47 - 0
shudao-chat-py/services/qwen_service.py

@@ -39,10 +39,24 @@ class QwenService:
         normalized = (raw_url or "").rstrip("/")
         if normalized.endswith("/chat/completions"):
             return normalized
+        if normalized.endswith("/api/v1"):
+            return f"{normalized}/chat/completions"
         if normalized.endswith("/v1"):
             return f"{normalized}/chat/completions"
         return f"{normalized}/v1/chat/completions"
 
+    def _build_api_v1_fallback_url(self, target_url: str) -> Optional[str]:
+        normalized = (target_url or "").rstrip("/")
+        if not normalized.endswith("/v1/chat/completions"):
+            return None
+        if normalized.endswith("/api/v1/chat/completions"):
+            return None
+        return normalized.replace(
+            "/v1/chat/completions",
+            "/api/v1/chat/completions",
+            1,
+        )
+
     def _should_fallback(self, status_code: int) -> bool:
         return status_code in (429, 500, 502, 503, 504)
 
@@ -451,6 +465,39 @@ class QwenService:
             logger.error(
                 f"[Qwen API] HTTP 错误 - 状态码: {e.response.status_code}, URL: {target_url}")
             logger.error(f"[Qwen API] HTTP 错误响应: {e.response.text[:500]}")
+            fallback_url = None
+            if e.response.status_code == 404:
+                fallback_url = self._build_api_v1_fallback_url(target_url)
+            if fallback_url:
+                logger.warning(
+                    f"[Qwen API] 检测到 404,尝试兼容地址重试: {fallback_url}"
+                )
+                response = await self._client.post(
+                    fallback_url,
+                    json=data,
+                    headers=headers,
+                )
+                elapsed_ms = int((time.monotonic() - start_at) * 1000)
+                logger.info(
+                    f"[Qwen API] 兼容地址响应: status={response.status_code} elapsed_ms={elapsed_ms}"
+                )
+                logger.debug(f"[Qwen API] 兼容地址响应头: {dict(response.headers)}")
+                logger.debug(
+                    f"[Qwen API] 兼容地址响应预览: {(response.text[:500] if response.text else '(空响应)')}"
+                )
+                response.raise_for_status()
+                if not response.text:
+                    logger.error("[Qwen API] 兼容地址返回空响应")
+                    return ""
+                try:
+                    result = response.json()
+                    content = result.get('response', result.get('choices', [{}])[
+                                         0].get('message', {}).get('content', ''))
+                    logger.info(f"[Qwen API] 兼容地址 JSON 解析成功,内容长度: {len(content)}")
+                    return content
+                except json.JSONDecodeError as je:
+                    logger.error(f"[Qwen API] 兼容地址响应不是有效的 JSON: {response.text[:200]}")
+                    raise ValueError(f"无效的 JSON 响应: {str(je)}")
             if is_qwen3_target and self._should_fallback(e.response.status_code):
                 return await self._fallback_deepseek(final_messages)
             raise

+ 35 - 5
shudao-vue-frontend/src/components/FileReportCard.vue

@@ -17,10 +17,16 @@
 
     <!-- Metadata Row -->
     <div class="file-meta" v-if="hasMetaRow">
-      <el-tag v-if="report.metadata?.primary_category" size="small" type="info" class="meta-tag" effect="plain">
-        {{ report.metadata.primary_category }}
+      <el-tag v-if="displayPrimaryTag" size="small" type="info" class="meta-tag" effect="plain">
+        {{ displayPrimaryTag }}
       </el-tag>
-      <el-tag v-if="report.metadata?.secondary_category" size="small" type="info" class="meta-tag" effect="plain">
+      <el-tag v-if="displaySecondaryTag" size="small" type="info" class="meta-tag" effect="plain">
+        {{ displaySecondaryTag }}
+      </el-tag>
+      <el-tag v-if="displayTertiaryTag" size="small" type="info" class="meta-tag" effect="plain">
+        {{ displayTertiaryTag }}
+      </el-tag>
+      <el-tag v-else-if="!displaySecondaryTag && report.metadata?.secondary_category && report.metadata?.secondary_category !== displayPrimaryTag" size="small" type="info" class="meta-tag" effect="plain">
         {{ report.metadata.secondary_category }}
       </el-tag>
       
@@ -105,14 +111,38 @@ const sourcePreviewUrl = ref('')
 const sourcePreviewTitle = ref('')
 
 const hasMetaRow = computed(() => {
-  return props.report.metadata?.primary_category || 
-         props.report.metadata?.secondary_category || 
+  return displayPrimaryTag.value || 
+         displaySecondaryTag.value || 
+         displayTertiaryTag.value ||
          documentNumber.value !== '未提取' || 
          issuingUnit.value !== '未提取' || 
          documentDate.value !== '未提取' ||
          sourceUrl.value
 })
 
+const displayPrimaryTag = computed(() => {
+  return props.report?.metadata?.display_group ||
+         props.report?.metadata?._displayCategory ||
+         props.report?.metadata?.primary_category ||
+         ''
+})
+
+const displaySecondaryTag = computed(() => {
+  const secondary = props.report?.metadata?.secondary_category || ''
+  if (!secondary || secondary === displayPrimaryTag.value) {
+    return ''
+  }
+  return secondary
+})
+
+const displayTertiaryTag = computed(() => {
+  const primary = props.report?.metadata?.primary_category || ''
+  if (!primary || primary === displayPrimaryTag.value || primary === displaySecondaryTag.value) {
+    return ''
+  }
+  return primary
+})
+
 const statusClass = computed(() => {
   return {
     'status-processing': props.report.status === 'processing',

+ 33 - 6
shudao-vue-frontend/src/components/MobileFileReportCard.vue

@@ -10,11 +10,14 @@
     </div>
 
     <div class="file-meta" v-if="hasMetaRow">
-      <span v-if="report.metadata?.primary_category" class="category-tag">
-        {{ report.metadata.primary_category }}
+      <span v-if="displayPrimaryTag" class="category-tag">
+        {{ displayPrimaryTag }}
       </span>
-      <span v-if="report.metadata?.secondary_category" class="category-tag">
-        {{ report.metadata.secondary_category }}
+      <span v-if="displaySecondaryTag" class="category-tag">
+        {{ displaySecondaryTag }}
+      </span>
+      <span v-if="displayTertiaryTag" class="category-tag">
+        {{ displayTertiaryTag }}
       </span>
       
       <span class="meta-text" v-if="documentNumber !== '未提取'">{{ documentNumber }}</span>
@@ -85,13 +88,37 @@ const briefSummary = computed(() => {
 const previewSource = computed(() => getReportPreviewSource(props.report))
 
 const hasMetaRow = computed(() => {
-  return props.report.metadata?.primary_category || 
-         props.report.metadata?.secondary_category || 
+  return displayPrimaryTag.value || 
+         displaySecondaryTag.value || 
+         displayTertiaryTag.value ||
          documentNumber.value !== '未提取' || 
          issuingUnit.value !== '未提取' || 
          documentDate.value !== '未提取'
 })
 
+const displayPrimaryTag = computed(() => {
+  return props.report?.metadata?.display_group ||
+         props.report?.metadata?._displayCategory ||
+         props.report?.metadata?.primary_category ||
+         ''
+})
+
+const displaySecondaryTag = computed(() => {
+  const secondary = props.report?.metadata?.secondary_category || ''
+  if (!secondary || secondary === displayPrimaryTag.value) {
+    return ''
+  }
+  return secondary
+})
+
+const displayTertiaryTag = computed(() => {
+  const primary = props.report?.metadata?.primary_category || ''
+  if (!primary || primary === displayPrimaryTag.value || primary === displaySecondaryTag.value) {
+    return ''
+  }
+  return primary
+})
+
 const openFile = () => {
   if (previewSource.value) {
     const fileName = props.report.report?.display_name || props.report.source_file || '未命名文件'

+ 4 - 3
shudao-vue-frontend/src/views/Chat.vue

@@ -3811,7 +3811,7 @@ const handleSSEMessage = (data, aiMessageIndex) => {
         similarity: data.similarity,
         metadata: {
           ...data.metadata,
-          _displayCategory: data.metadata?.primary_category || aiMessage.currentCategory // 存储当前显示的分类名
+          _displayCategory: data.metadata?.display_group || data.metadata?.primary_category || aiMessage.currentCategory // 存储当前显示的分类名
         },
         report: {
           display_name: '',
@@ -3855,7 +3855,8 @@ const handleSSEMessage = (data, aiMessageIndex) => {
       if (idx !== undefined) {
         const existingReport = aiMessage.reports[idx]
         const hadStreamingContent = Boolean(existingReport?._streamingStarted)
-        const displayCategory = reportData.metadata?.primary_category ||
+        const displayCategory = reportData.metadata?.display_group ||
+          reportData.metadata?.primary_category ||
           existingReport?.metadata?._displayCategory ||
           aiMessage.currentCategory
         const fullSummary = reportData.report?.summary || ''
@@ -3905,7 +3906,7 @@ const handleSSEMessage = (data, aiMessageIndex) => {
           status: 'completed',
           metadata: {
             ...reportData.metadata, // 保留所有metadata字段
-            _displayCategory: reportData.metadata?.primary_category || aiMessage.currentCategory
+            _displayCategory: reportData.metadata?.display_group || reportData.metadata?.primary_category || aiMessage.currentCategory
           },
           _fullContent: {
             display_name: fullDisplayName,

+ 4 - 3
shudao-vue-frontend/src/views/mobile/m-Chat.vue

@@ -3261,7 +3261,7 @@ const handleSSEMessage = (data, aiMessageIndex) => {
         similarity: data.similarity,
         metadata: {
           ...data.metadata,
-          _displayCategory: data.metadata?.primary_category || aiMessage.currentCategory // 存储当前显示的分类名
+          _displayCategory: data.metadata?.display_group || data.metadata?.primary_category || aiMessage.currentCategory // 存储当前显示的分类名
         },
         report: {
           display_name: '',
@@ -3305,7 +3305,8 @@ const handleSSEMessage = (data, aiMessageIndex) => {
       if (idx !== undefined) {
         const existingReport = aiMessage.reports[idx]
         const hadStreamingContent = Boolean(existingReport?._streamingStarted)
-        const displayCategory = reportData.metadata?.primary_category ||
+        const displayCategory = reportData.metadata?.display_group ||
+          reportData.metadata?.primary_category ||
           existingReport?.metadata?._displayCategory ||
           aiMessage.currentCategory
         const fullSummary = reportData.report?.summary || ''
@@ -3355,7 +3356,7 @@ const handleSSEMessage = (data, aiMessageIndex) => {
           status: 'completed',
           metadata: {
             ...reportData.metadata, // 保留所有metadata字段
-            _displayCategory: reportData.metadata?.primary_category || aiMessage.currentCategory
+            _displayCategory: reportData.metadata?.display_group || reportData.metadata?.primary_category || aiMessage.currentCategory
           },
           _fullContent: {
             display_name: fullDisplayName,