Browse Source

feat:引入自动刷新token以及在AI回复期间发送禁用

zhangwl 1 month ago
parent
commit
b7667fdcc8

+ 17 - 33
chat-ai-ui-main/src/components/ChatInput.vue

@@ -1,10 +1,11 @@
 <script setup lang="ts">
 <script setup lang="ts">
 import { ref } from 'vue';
 import { ref } from 'vue';
 
 
+const props = defineProps<{
+  isLoading?: boolean;
+}>();
+
 const message = ref('');
 const message = ref('');
-// const model =  ref("qwen-turbo-2025-04-28");
-// const temperature = ref(0.7);
-// const maxTokens = ref(2000);
 const stream = ref(true);
 const stream = ref(true);
 const emit = defineEmits(['send']);
 const emit = defineEmits(['send']);
 
 
@@ -15,25 +16,9 @@ const emit = defineEmits(['send']);
 //   { value: "qwen-flash", label: "Qwen Flash" }
 //   { value: "qwen-flash", label: "Qwen Flash" }
 // ];
 // ];
 
 
-/**
- * 发送消息函数
- *
- * 该函数用于发送用户输入的消息,并触发相应的事件
- *
- * @param {string} message.value - 要发送的消息内容
- * @param {string} model.value - 使用的模型参数
- * @param {number} temperature.value - 温度参数,控制生成文本的随机性
- * @param {number} maxTokens.value - 最大令牌数参数,限制生成文本的长度
- * @param {boolean} stream.value - 是否启用流式传输
- *
- * @returns {void}
- */
 const sendMessage = () => {
 const sendMessage = () => {
-  // 检查消息是否为空,如果为空则直接返回
-  if (!message.value.trim()) return;
-  // 触发send事件,传递消息和相关参数
+  if (!message.value.trim() || props.isLoading) return;
   emit('send', message.value, stream.value);
   emit('send', message.value, stream.value);
-  // 清空消息输入框
   message.value = '';
   message.value = '';
 };
 };
 </script>
 </script>
@@ -43,14 +28,14 @@ const sendMessage = () => {
     <!-- 第一行:流式输出、状态指示器、模型选择 -->
     <!-- 第一行:流式输出、状态指示器、模型选择 -->
     <div class="flex items-center space-x-6">
     <div class="flex items-center space-x-6">
       <!-- 流式输出开关 -->
       <!-- 流式输出开关 -->
-      <label class="flex items-center space-x-2 text-white cursor-pointer">
-        <input
-          v-model="stream"
-          type="checkbox"
-          class="rounded bg-gray-700 border-gray-600 text-blue-500 focus:ring-blue-500"
-        />
-        <span class="text-sm">流式输出</span>
-      </label>
+<!--      <label class="flex items-center space-x-2 text-white cursor-pointer">-->
+<!--        <input-->
+<!--          v-model="stream"-->
+<!--          type="checkbox"-->
+<!--          class="rounded bg-gray-700 border-gray-600 text-blue-500 focus:ring-blue-500"-->
+<!--        />-->
+<!--        <span class="text-sm">流式输出</span>-->
+<!--      </label>-->
 
 
       <!-- 状态指示器 -->
       <!-- 状态指示器 -->
       <div class="flex items-center space-x-1">
       <div class="flex items-center space-x-1">
@@ -154,19 +139,18 @@ const sendMessage = () => {
     <div class="flex space-x-2">
     <div class="flex space-x-2">
       <input
       <input
         v-model="message"
         v-model="message"
-        @keyup.enter="sendMessage"
         placeholder="输入你的消息..."
         placeholder="输入你的消息..."
         type="text"
         type="text"
         class="flex-1 p-3 rounded-lg bg-gray-700 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 border border-gray-600"
         class="flex-1 p-3 rounded-lg bg-gray-700 text-white placeholder-gray-400 focus:outline-none focus:ring-2 focus:ring-blue-500 border border-gray-600"
       />
       />
       <button
       <button
         @click="sendMessage"
         @click="sendMessage"
-        :disabled="!message.trim()"
+        :disabled="!message.trim() || isLoading"
         :class="[
         :class="[
           'px-6 py-3 rounded-lg font-medium transition-all duration-200',
           'px-6 py-3 rounded-lg font-medium transition-all duration-200',
-          message.trim()
-            ? 'bg-blue-500 hover:bg-blue-600 text-white cursor-pointer transform hover:scale-105'
-            : 'bg-gray-600 text-gray-400 cursor-not-allowed'
+          !message.trim() || isLoading
+            ? 'bg-gray-600 text-gray-400 cursor-not-allowed'
+            : 'bg-blue-500 hover:bg-blue-600 text-white cursor-pointer transform hover:scale-105'
         ]"
         ]"
       >
       >
         <div class="flex items-center space-x-2">
         <div class="flex items-center space-x-2">

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

@@ -3,6 +3,42 @@ import { ref } from 'vue';
 import axios from 'axios';
 import axios from 'axios';
 import { useUserStore } from './user';
 import { useUserStore } from './user';
 
 
+let refreshTokenPromise: Promise<any> | null = null;
+
+axios.interceptors.response.use(
+  response => response,
+  async error => {
+    const originalRequest = error.config;
+
+    if (error.response?.status === 401 && !originalRequest._retry) {
+      originalRequest._retry = true;
+
+      if (!refreshTokenPromise) {
+        const userStore = useUserStore();
+        refreshTokenPromise = axios.post(
+          `${import.meta.env.VITE_API_URL}/users/refresh`,
+          { refresh_token: userStore.refreshToken }
+        ).finally(() => { refreshTokenPromise = null; });
+      }
+
+      try {
+        const { data } = await refreshTokenPromise;
+        const userStore = useUserStore();
+        userStore.setUser({ userId: data.access_token, name: userStore.name!, refreshToken: data.refresh_token });
+        originalRequest.headers.Authorization = `Bearer ${data.access_token}`;
+        return axios(originalRequest);
+      } catch {
+        const userStore = useUserStore();
+        userStore.logout();
+        window.location.href = '/';
+        return Promise.reject(error);
+      }
+    }
+
+    return Promise.reject(error);
+  }
+);
+
 interface FormattedMessage {
 interface FormattedMessage {
   role: 'user' | 'assistant';
   role: 'user' | 'assistant';
   content: string;
   content: string;
@@ -119,6 +155,7 @@ export const useChatStore = defineStore('chat', () => {
           displayContent: data.message.content,
           displayContent: data.message.content,
           ...blankMeta(),
           ...blankMeta(),
         });
         });
+        isLoading.value = false;
       }
       }
     } catch (error) {
     } catch (error) {
       console.error('Error sending message: ', error);
       console.error('Error sending message: ', error);
@@ -127,7 +164,6 @@ export const useChatStore = defineStore('chat', () => {
         displayContent: 'Error: unable to process request',
         displayContent: 'Error: unable to process request',
         ...blankMeta(),
         ...blankMeta(),
       });
       });
-    } finally {
       isLoading.value = false;
       isLoading.value = false;
     }
     }
   };
   };
@@ -238,6 +274,7 @@ export const useChatStore = defineStore('chat', () => {
         } else {
         } else {
           messages.value[aiMessageIndex].isStreaming = false;
           messages.value[aiMessageIndex].isStreaming = false;
           messages.value[aiMessageIndex].phase = 'done';
           messages.value[aiMessageIndex].phase = 'done';
+          isLoading.value = false;
         }
         }
       };
       };
       waitTypingDone();
       waitTypingDone();
@@ -245,6 +282,7 @@ export const useChatStore = defineStore('chat', () => {
     } catch (error) {
     } catch (error) {
       console.error('Stream error:', error);
       console.error('Stream error:', error);
       messages.value.splice(aiMessageIndex, 1);
       messages.value.splice(aiMessageIndex, 1);
+      isLoading.value = false;
       throw error;
       throw error;
     }
     }
   };
   };

+ 5 - 2
chat-ai-ui-main/src/stores/user.ts

@@ -4,16 +4,19 @@ export const useUserStore = defineStore('user', {
   state: () => ({
   state: () => ({
     userId: null as string | null,
     userId: null as string | null,
     name: null as string | null,
     name: null as string | null,
+    refreshToken: null as string | null,
   }),
   }),
   actions: {
   actions: {
-    setUser(data: { userId: string; name: string }) {
+    setUser(data: { userId: string; name: string; refreshToken?: string }) {
       this.userId = data.userId;
       this.userId = data.userId;
       this.name = data.name;
       this.name = data.name;
+      if (data.refreshToken) this.refreshToken = data.refreshToken;
     },
     },
     logout() {
     logout() {
       this.userId = null;
       this.userId = null;
       this.name = null;
       this.name = null;
+      this.refreshToken = null;
     },
     },
   },
   },
-  persist: true, // Keep user logged in across page reloads
+  persist: true,
 });
 });

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

@@ -277,7 +277,7 @@ watch(
 
 
         <!-- Chat input -->
         <!-- Chat input -->
         <div class="flex-shrink-0">
         <div class="flex-shrink-0">
-          <ChatInput @send="chatStore.sendMessage" />
+          <ChatInput @send="chatStore.sendMessage" :isLoading="chatStore.isLoading" />
         </div>
         </div>
       </div>
       </div>
     </div>
     </div>

+ 1 - 0
chat-ai-ui-main/src/views/HomeView.vue

@@ -34,6 +34,7 @@ try {
   userStore.setUser({
   userStore.setUser({
     userId: data.access_token,
     userId: data.access_token,
     name: data.username,
     name: data.username,
+    refreshToken: data.refresh_token,
   });
   });
 
 
   router.push('/chat');
   router.push('/chat');