Browse Source

feat:-自建知识库

zhangwl 4 weeks ago
parent
commit
cf365c2188

+ 9 - 7
chat-ai-ui-main/src/components/ChatInput.vue

@@ -90,7 +90,7 @@ const uploadFile = async (file: File) => {
       filename: data.filename,
       media_type: data.media_type,
       size: data.size,
-      preview_url: previewUrl,
+      preview_url: data.preview_url || previewUrl,  // 优先使用后端预览 URL
       uploading: false,
     };
   } catch (error: any) {
@@ -119,7 +119,7 @@ const hasUploadingFiles = () => attachments.value.some(a => a.uploading);
 
 const sendMessage = () => {
   if ((!message.value.trim() && attachments.value.length === 0) || props.isLoading || hasUploadingFiles()) return;
-  const files = attachments.value.map(({ file_id, filename, media_type, size }) => ({ file_id, filename, media_type, size }));
+  const files = attachments.value.map(({ file_id, filename, media_type, size, preview_url }) => ({ file_id, filename, media_type, size, preview_url }));
   emit('send', message.value, stream.value, files.length > 0 ? files : undefined);
   message.value = '';
   attachments.value = [];
@@ -174,10 +174,10 @@ onMounted(() => {
   }
 });
 
-const toggleVoiceMode = () => {
+/*const toggleVoiceMode = () => {
   if (isRecording.value || isTranscribing.value) return;
   isVoiceMode.value = !isVoiceMode.value;
-};
+};*/
 
 const recordingAreaClasses = computed(() => {
   if (isTranscribing.value) return 'bg-blue-900/50 border-blue-500';
@@ -364,7 +364,7 @@ const sendVoice = async (audioBlob: Blob) => {
 
     if (data.text && data.text.trim()) {
       // 语音转写文字 + 已上传的附件(图片/视频)一起发送
-      const files = attachments.value.map(({ file_id, filename, media_type, size }) => ({ file_id, filename, media_type, size }));
+      const files = attachments.value.map(({ file_id, filename, media_type, size, preview_url }) => ({ file_id, filename, media_type, size, preview_url }));
       emit('send', data.text, stream.value, files.length > 0 ? files : undefined);
       attachments.value = [];
     } else {
@@ -513,7 +513,8 @@ const sendVoice = async (audioBlob: Blob) => {
         </span>
       </div>
 
-      <!-- 麦克风按钮 -->
+      <!-- [临时隐藏] 麦克风按钮 - 待 Android WebView 权限适配完成后恢复 -->
+      <!--
       <button
         v-if="voiceSupported"
         @click="toggleVoiceMode"
@@ -531,8 +532,9 @@ const sendVoice = async (audioBlob: Blob) => {
             d="M19 11a7 7 0 01-7 7m0 0a7 7 0 01-7-7m7 7v4m0 0H8m4 0h4M12 15a3 3 0 003-3V5a3 3 0 00-6 0v7a3 3 0 003 3z" />
         </svg>
       </button>
+      -->
 
-      <!-- 发送按钮(仅文字模式显示) -->
+      <!-- 发送按钮 -->
       <button
         v-if="!isVoiceMode"
         @click="sendMessage"

+ 1 - 0
chat-ai-ui-main/src/stores/chat.ts

@@ -40,6 +40,7 @@ interface FileAttachment {
   filename: string;
   media_type: string;
   size: number;
+  preview_url?: string;  // 图片/视频预览 URL
 }
 
 interface Session {

+ 43 - 7
chat-ai-ui-main/src/views/ChatView.vue

@@ -13,6 +13,20 @@ import { markedHighlight } from 'marked-highlight';
 
 const chatStore = useChatStore();
 
+const API_BASE = import.meta.env.VITE_API_URL;
+
+// 获取附件预览 URL:优先使用 preview_url,否则通过 file_id 拼接后端接口地址
+const getAttachmentUrl = (att: { preview_url?: string; file_id: string }) => {
+  if (att.preview_url) {
+    // 如果 preview_url 已是完整相对路径(以 /main 开头),则拼接 API 基础地址
+    if (att.preview_url.startsWith('/')) {
+      return `${API_BASE.replace(/\/main\/?$/, '')}${att.preview_url}`;
+    }
+    return att.preview_url;
+  }
+  return `${API_BASE}/chat/files/${att.file_id}`;
+};
+
 
 // 配置标记以获得更好的呈现
 
@@ -193,16 +207,38 @@ watch(
               <!-- 附件展示区 -->
               <div v-if="msg.attachments && msg.attachments.length > 0" class="mb-2 flex flex-wrap gap-2">
                 <template v-for="(att, attIdx) in msg.attachments" :key="attIdx">
-                  <!-- 图片附件:缩略图 -->
-                  <div v-if="att.media_type === 'image'" class="rounded overflow-hidden border border-blue-400/30">
-                    <div class="w-16 h-16 bg-blue-700/50 flex items-center justify-center text-xs text-blue-200">
+                  <!-- 图片附件:显示缩略图,点击可放大 -->
+                  <div v-if="att.media_type === 'image'" class="rounded overflow-hidden">
+                    <a
+                      v-if="att.preview_url || att.file_id"
+                      :href="getAttachmentUrl(att)"
+                      target="_blank"
+                      class="block"
+                    >
+                      <img
+                        :src="getAttachmentUrl(att)"
+                        :alt="att.filename"
+                        class="max-w-[200px] max-h-[200px] rounded object-cover"
+                        loading="lazy"
+                      />
+                    </a>
+                    <div v-else class="w-16 h-16 bg-blue-700/50 flex items-center justify-center text-xs text-blue-200 rounded">
                       📷 {{ att.filename.length > 8 ? att.filename.slice(0,8) + '...' : att.filename }}
                     </div>
                   </div>
-                  <!-- 视频附件 -->
-                  <div v-else-if="att.media_type === 'video'" class="flex items-center gap-1 px-2 py-1 rounded bg-blue-700/50 border border-blue-400/30">
-                    <span>🎬</span>
-                    <span class="text-xs truncate max-w-[80px]">{{ att.filename }}</span>
+                  <!-- 视频附件:内嵌播放器 -->
+                  <div v-else-if="att.media_type === 'video'" class="rounded overflow-hidden">
+                    <video
+                      v-if="att.preview_url || att.file_id"
+                      :src="getAttachmentUrl(att)"
+                      class="max-w-[240px] max-h-[180px] rounded"
+                      controls
+                      preload="metadata"
+                    />
+                    <div v-else class="flex items-center gap-1 px-2 py-1 rounded bg-blue-700/50 border border-blue-400/30">
+                      <span>🎬</span>
+                      <span class="text-xs truncate max-w-[80px]">{{ att.filename }}</span>
+                    </div>
                   </div>
                   <!-- 音频附件 -->
                   <div v-else-if="att.media_type === 'audio'" class="flex items-center gap-1 px-2 py-1 rounded bg-blue-700/50 border border-blue-400/30">