فهرست منبع

feat: 细节优化

zhangjie 1 ماه پیش
والد
کامیت
a885491031

+ 1 - 1
src/assets/styles/pages.scss

@@ -1912,7 +1912,7 @@
     flex-grow: 1;
     display: flex;
     flex-direction: column;
-    padding: 0 20px;
+    padding: 0 0 0 20px;
     overflow: hidden;
   }
 

+ 4 - 2
src/modules/question/components/PropertyTreeSelect.vue

@@ -6,7 +6,7 @@
     <div
       :class="[
         'el-input el-input--small el-input--suffix',
-        { 'is-focus': isFocus },
+        { 'is-focus': isFocus, 'is-disabled': disabled },
       ]"
       @mouseenter="inputHovering = true"
       @mouseleave="inputHovering = false"
@@ -19,6 +19,7 @@
         class="el-input__inner"
         :value="selectedPropName"
         readonly
+        :disabled="disabled"
       />
       <span class="el-input__suffix">
         <span class="el-input__suffix-inner">
@@ -32,7 +33,7 @@
             ]"
           ></i>
           <i
-            v-if="showClose"
+            v-if="showClose && !disabled"
             class="el-select__caret el-input__icon el-icon-circle-close"
             @click="handleClearClick"
           ></i>
@@ -168,6 +169,7 @@ export default {
       if (this.value) this.initSelected(this.value);
     },
     switchOpen() {
+      if (this.disabled) return;
       if (this.visible) {
         this.handleClose();
       } else {

+ 59 - 19
src/modules/question/components/ai-question/AiQuestionCreateDialog.vue

@@ -69,7 +69,10 @@
             ></el-input-number>
           </el-form-item>
 
-          <el-form-item v-if="courseInfo.outlineFilePath" label="教学大纲">
+          <el-form-item
+            v-if="courseInfo.outlineFilePath && courseOutlineParsed"
+            label="教学大纲"
+          >
             <div class="box-justify">
               <el-checkbox v-model="formModel.syllabus"
                 >教学大纲pdf</el-checkbox
@@ -88,6 +91,7 @@
             <property-tree-select
               v-model="formModel.propertyIdList"
               :course-id="courseInfo.id"
+              :disabled="loading"
               :style="{ width: '100%' }"
             ></property-tree-select>
             <el-input
@@ -115,14 +119,14 @@
 
       <div class="right-panel">
         <div class="sse-output-title">试题预览</div>
-        <div class="sse-output-container">
+        <div class="sse-output-container" ref="sseOuiputContainerRef">
           <template v-if="aiThinkingResult">
             <div
               class="sse-thinking-title"
               @click="thinkVisible = !thinkVisible"
             >
               <template v-if="thinking">
-                <i class="el-icon-loading"></i>
+                <i class="el-icon-loading" style="margin-right: 6px"></i>
                 <span>深度思考中...</span>
               </template>
               <template v-else>
@@ -163,6 +167,8 @@ import {
   aiBuildQuestionSaveApi,
 } from "../../api";
 
+import { courseOutlineParsedCheckApi } from "@/modules/questions/api";
+
 import { fetchTime } from "@/plugins/syncServerTime";
 import { getAuthorization } from "@/plugins/crypto";
 
@@ -213,6 +219,7 @@ export default {
           },
         ],
       },
+      courseOutlineParsed: false,
       // ai question result
       taskId: "",
       aiResult: "",
@@ -233,12 +240,26 @@ export default {
     },
   },
   methods: {
+    scrollToBottom() {
+      const container = this.$refs.sseOuiputContainerRef;
+      if (container) {
+        container.scrollTop = container.scrollHeight;
+      }
+    },
     close() {
       this.modalIsShow = false;
     },
     open() {
       this.modalIsShow = true;
     },
+    async checkCourseOutlineParsed() {
+      if (!this.courseInfo.id || !this.courseInfo.outlineFilePath) return;
+      const res = await courseOutlineParsedCheckApi(this.courseInfo.id).catch(
+        () => {}
+      );
+      if (!res) return;
+      this.courseOutlineParsed = res.data;
+    },
     async getQuestionTypes() {
       if (!this.courseInfo.id) return;
       const res = await sourceDetailPageListApi({
@@ -278,6 +299,7 @@ export default {
       this.formModel = this.getInitForm();
       this.getQuestionTypes();
       this.getInitForm();
+      this.checkCourseOutlineParsed();
     },
     handleClose() {
       this.aiResult = "";
@@ -285,6 +307,7 @@ export default {
       this.taskId = "";
       this.thinking = false;
       this.thinkDuration = "";
+      this.courseOutlineParsed = false;
       this.stopStream();
     },
     setAuth(config) {
@@ -318,11 +341,18 @@ export default {
       this.aiResult = "";
       this.aiThinkingResult = "";
       this.thinkDuration = "";
-      this.thinking = false;
       this.taskId = "";
       this.loading = true;
 
+      // 设置60秒超时
+      const timeoutId = setTimeout(() => {
+        this.controller.abort();
+        this.$message.error("请求超时!");
+        this.loading = false;
+      }, 60000);
+
       try {
+        this.controller = new AbortController();
         const res = await aiBuildQuestionApi(
           {
             ...this.formModel,
@@ -340,9 +370,12 @@ export default {
                 url: "/api/uq_basic/ai/question/stream/build",
               }),
             },
-            signal: (this.controller = new AbortController()).signal, // Ensures a new controller for each call
+            signal: this.controller.signal, // 使用AbortController的signal
           }
         );
+
+        // 请求成功后清除超时定时器
+        clearTimeout(timeoutId);
         const startThinkingTime = Date.now();
         this.thinking = true;
 
@@ -355,7 +388,9 @@ export default {
             const parsed = JSON.parse(event.data);
             if (!this.taskId && parsed.taskId) {
               this.taskId = parsed.taskId;
+              return;
             }
+
             if (
               parsed.choices &&
               parsed.choices[0] &&
@@ -363,25 +398,28 @@ export default {
             ) {
               const { content, reasoning_content } = parsed.choices[0].delta;
 
-              // 处理思考过程内容
-              if (reasoning_content) {
-                requestAnimationFrame(() => {
-                  this.aiThinkingResult += reasoning_content;
-                });
-                return;
-              } else {
+              // 只要content不为null,则表示思考结束,开始处理生成结果内容
+              if (content !== null) {
                 this.thinking = false;
-                this.thinkDuration = Math.round(
-                  (Date.now() - startThinkingTime) / 1000
-                );
-              }
 
-              // 处理生成结果内容
-              if (content) {
+                if (!this.thinkDuration) {
+                  this.thinkDuration = Math.round(
+                    (Date.now() - startThinkingTime) / 1000
+                  );
+                }
+
                 requestAnimationFrame(() => {
-                  this.aiResult += content;
+                  this.aiResult += content || "";
+                  this.scrollToBottom();
                 });
+                return;
               }
+
+              // 处理思考过程内容
+              requestAnimationFrame(() => {
+                this.aiThinkingResult += reasoning_content || "";
+                this.scrollToBottom();
+              });
             }
           } catch (e) {
             console.error(
@@ -415,6 +453,8 @@ export default {
       } catch (err) {
         console.error("流式处理错误:", err);
         this.thinking = false;
+        // 请求出错时清除超时定时器
+        clearTimeout(timeoutId);
       } finally {
         this.loading = false;
         this.controller = null; // Ensure controller is reset

+ 2 - 0
src/modules/question/components/ai-question/SseResultView.vue

@@ -26,6 +26,8 @@ export default {
       handler(newValue) {
         if (!newValue) {
           this.parsedContent = "";
+          this.latexCache.clear();
+          this.lastParsedText = "";
           return;
         }
         this.parseLatex(newValue);