zhangjie 1 сар өмнө
parent
commit
df99849216

+ 1 - 0
src/assets/icons/think.svg

@@ -0,0 +1 @@
+<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1747185422940" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2888" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M168.618667 168.618667c94.776889-94.833778 325.461333-17.92 515.072 171.690666 189.667556 189.667556 266.581333 420.238222 171.747555 515.128889-94.890667 94.833778-325.404444 17.92-515.128889-171.747555C150.641778 494.08 73.784889 263.395556 168.618667 168.618667z m469.845333 216.974222C470.641778 217.770667 275.171556 152.576 213.902222 213.902222c-61.326222 61.269333 3.811556 256.682667 171.690667 424.561778 167.879111 167.822222 363.292444 232.96 424.561778 171.690667 61.326222-61.269333-3.811556-256.682667-171.690667-424.561778z" fill="#2c2c2c" p-id="2889"></path><path d="M340.309333 340.252444c189.667556-189.610667 420.295111-266.467556 515.128889-171.633777 94.833778 94.776889 17.92 325.404444-171.747555 515.072-189.610667 189.667556-420.295111 266.581333-515.072 171.747555-94.833778-94.890667-17.92-325.518222 171.690666-515.185778z m469.902223-126.350222c-61.326222-61.326222-256.739556 3.811556-424.618667 171.690667-167.822222 167.822222-233.016889 363.178667-171.690667 424.561778 61.269333 61.326222 256.682667-3.811556 424.561778-171.690667 167.822222-167.879111 232.96-363.292444 171.690667-424.561778z" fill="#2c2c2c" p-id="2890"></path><path d="M442.595556 512a69.404444 69.404444 0 1 0 138.808888 0 69.404444 69.404444 0 0 0-138.808888 0z" fill="#2c2c2c" p-id="2891"></path></svg>

+ 45 - 5
src/assets/styles/pages.scss

@@ -1925,20 +1925,20 @@
     font-weight: bold;
     border: 1px solid #dcdfe6;
     border-bottom: none;
-    border-top-left-radius: 3px;
-    border-top-right-radius: 3px;
+    border-top-left-radius: 5px;
+    border-top-right-radius: 5px;
   }
 
   .sse-output-container {
     flex-grow: 1;
     border: 1px solid #dcdfe6;
-    padding: 15px;
+    padding: 20px;
     overflow-y: auto;
     background-color: #fff;
     white-space: pre-wrap;
     word-break: break-all;
-    border-bottom-left-radius: 3px;
-    border-bottom-right-radius: 3px;
+    border-bottom-left-radius: 5px;
+    border-bottom-right-radius: 5px;
     color: #313444;
   }
 
@@ -1947,4 +1947,44 @@
     text-align: center;
     flex-shrink: 0;
   }
+
+  .sse-thinking-title {
+    line-height: 18px;
+    background: #f5f5f5;
+    user-select: none;
+    border-radius: 10px;
+    justify-content: center;
+    align-items: center;
+    width: fit-content;
+    padding: 7px 14px;
+    display: flex;
+
+    color: #262626;
+    margin-bottom: 12px;
+    font-size: 12px;
+    line-height: 18px;
+    cursor: pointer;
+
+    &:hover {
+      background-color: #e5e5e5;
+    }
+
+    .svg-btn {
+      margin-right: 6px;
+      height: 18px;
+      padding: 0;
+    }
+    .el-icon {
+      margin-left: 6px;
+    }
+  }
+  .sse-thinking-container {
+    color: #8b8b8b;
+    white-space: pre-wrap;
+    margin-bottom: 20px;
+    padding: 0 0 0 13px;
+    line-height: 26px;
+    position: relative;
+    border-left: 2px solid #e5e5e5;
+  }
 }

+ 64 - 40
src/modules/question/components/ai-question/AiQuestionCreateDialog.vue

@@ -69,21 +69,11 @@
             ></el-input-number>
           </el-form-item>
 
-          <el-form-item label="教学大纲">
+          <el-form-item v-if="courseInfo.outlineFilePath" label="教学大纲">
             <div class="box-justify">
               <el-checkbox v-model="formModel.syllabus"
                 >教学大纲pdf</el-checkbox
               >
-              <upload-button
-                btn-content="上传文件"
-                btn-icon="icon icon-import"
-                :disabled="uploading || !courseInfo.id"
-                :upload-data="{ id: courseInfo.id }"
-                :upload-url="uploadUrl"
-                :format="importFileTypes"
-                @valid-error="validError"
-                @upload-success="uploadSuccess"
-              ></upload-button>
             </div>
             <el-input
               v-if="formModel.syllabus"
@@ -126,7 +116,27 @@
       <div class="right-panel">
         <div class="sse-output-title">试题预览</div>
         <div class="sse-output-container">
-          <!-- {{ output }} -->
+          <template v-if="aiThinkingResult">
+            <div
+              class="sse-thinking-title"
+              @click="thinkVisible = !thinkVisible"
+            >
+              <template v-if="thinking">
+                <i class="el-icon-loading"></i>
+                <span>深度思考中...</span>
+              </template>
+              <template v-else>
+                <svg-btn name="think"></svg-btn>
+                <span>已深度思考(用时:{{ thinkDuration }}秒)</span>
+                <i v-if="thinkVisible" class="el-icon el-icon-arrow-down"></i>
+                <i v-else class="el-icon el-icon-arrow-down"></i>
+              </template>
+            </div>
+            <div v-show="thinkVisible" class="sse-thinking-container">
+              <sse-result-view :output="aiThinkingResult"></sse-result-view>
+            </div>
+          </template>
+
           <sse-result-view :output="aiResult"></sse-result-view>
         </div>
         <div class="action-buttons">
@@ -147,8 +157,6 @@ import { BASE_QUESTION_TYPES } from "@/constants/constants";
 import { createParser } from "eventsource-parser";
 import PropertyTreeSelect from "../PropertyTreeSelect.vue";
 import SseResultView from "./SseResultView.vue";
-import UploadButton from "@/components/UploadButton.vue";
-import { QUESTION_API } from "@/constants/constants";
 import {
   sourceDetailPageListApi,
   aiBuildQuestionApi,
@@ -162,7 +170,6 @@ export default {
   name: "AiQuestionCreateDialog",
   components: {
     PropertyTreeSelect,
-    UploadButton,
     SseResultView,
   },
   props: {
@@ -206,14 +213,13 @@ export default {
           },
         ],
       },
-      // upload
-      uploading: false,
-      importFileTypes: ["pdf"],
-      uploadUrl: `${QUESTION_API}/course/outline/upload`,
-      showIframeDialog: false,
       // ai question result
       taskId: "",
       aiResult: "",
+      aiThinkingResult: "",
+      thinking: false,
+      thinkVisible: true,
+      thinkDuration: "",
       loading: false,
       controller: null,
       parser: null,
@@ -226,14 +232,6 @@ export default {
       );
     },
   },
-  watch: {
-    cancel() {
-      this.modalIsShow = false;
-    },
-    open() {
-      this.modalIsShow = true;
-    },
-  },
   methods: {
     close() {
       this.modalIsShow = false;
@@ -283,17 +281,12 @@ export default {
     },
     handleClose() {
       this.aiResult = "";
+      this.aiThinkingResult = "";
       this.taskId = "";
+      this.thinking = false;
+      this.thinkDuration = "";
       this.stopStream();
     },
-    validError(error) {
-      this.$message.error(error.message);
-    },
-    uploadSuccess(response) {
-      console.log(response);
-      // TODO:
-      this.$message.success("上传成功!");
-    },
     setAuth(config) {
       const headers = {};
       let userSession = sessionStorage.getItem("user");
@@ -323,6 +316,9 @@ export default {
 
       this.stopStream(); // 清理前一个流
       this.aiResult = "";
+      this.aiThinkingResult = "";
+      this.thinkDuration = "";
+      this.thinking = false;
       this.taskId = "";
       this.loading = true;
 
@@ -347,10 +343,12 @@ export default {
             signal: (this.controller = new AbortController()).signal, // Ensures a new controller for each call
           }
         );
+        const startThinkingTime = Date.now();
+        this.thinking = true;
 
         const onEvent = (event) => {
           if (event.data === "[DONE]") {
-            console.log(this.aiResult);
+            // console.log(this.aiResult);
             return;
           }
           try {
@@ -363,9 +361,23 @@ export default {
               parsed.choices[0] &&
               parsed.choices[0].delta
             ) {
-              const content = parsed.choices[0].delta.content;
-              // console.log(content);
-              if (typeof content === "string") {
+              const { content, reasoning_content } = parsed.choices[0].delta;
+
+              // 处理思考过程内容
+              if (reasoning_content) {
+                requestAnimationFrame(() => {
+                  this.aiThinkingResult += reasoning_content;
+                });
+                return;
+              } else {
+                this.thinking = false;
+                this.thinkDuration = Math.round(
+                  (Date.now() - startThinkingTime) / 1000
+                );
+              }
+
+              // 处理生成结果内容
+              if (content) {
                 requestAnimationFrame(() => {
                   this.aiResult += content;
                 });
@@ -381,6 +393,7 @@ export default {
           }
         };
         const onError = (error) => {
+          this.thinking = false;
           console.error("Error parsing event:", error);
         };
 
@@ -401,6 +414,7 @@ export default {
         }
       } catch (err) {
         console.error("流式处理错误:", err);
+        this.thinking = false;
       } finally {
         this.loading = false;
         this.controller = null; // Ensure controller is reset
@@ -427,6 +441,8 @@ export default {
     },
     clearPreview() {
       this.aiResult = "";
+      this.aiThinkingResult = "";
+      this.taskId = "";
     },
   },
   beforeDestroy() {
@@ -434,3 +450,11 @@ export default {
   },
 };
 </script>
+
+<style>
+.thinking-container {
+  background-color: #f8f9fa;
+  border-left: 3px solid #409eff;
+  margin-bottom: 20px;
+}
+</style>

+ 10 - 0
src/modules/questions/api.js

@@ -83,3 +83,13 @@ export const courseOurlineImportApi = (data, headData) => {
     headers: headData,
   });
 };
+export const courseDetailApi = (courseId) => {
+  return $httpWithMsg.post(`${QUESTION_API}/course/${courseId}`, {});
+};
+export const courseOutlineParsedCheckApi = (courseId) => {
+  return $httpWithMsg.post(
+    `${QUESTION_API}/ai/question/file/find`,
+    {},
+    { params: { courseId } }
+  );
+};