Просмотр исходного кода

feat(aiChat): add batch generate interactive web tool feature

1. 新增多语言文案用于批量生成互动工具功能
2. 实现批量生成互动工具的快捷按钮与聊天流程
3. 添加加载状态UI与对应业务逻辑处理
4. 调整部分提示文案与类型命名规范
lsc 2 недель назад
Родитель
Сommit
1101ac24ac
4 измененных файлов с 128 добавлено и 16 удалено
  1. 116 13
      src/components/CollapsibleToolbar/componets/aiChat.vue
  2. 4 1
      src/views/lang/cn.json
  3. 4 1
      src/views/lang/en.json
  4. 4 1
      src/views/lang/hk.json

+ 116 - 13
src/components/CollapsibleToolbar/componets/aiChat.vue

@@ -72,7 +72,7 @@
                 </svg>
                 </svg>
               </span>
               </span>
             </button>
             </button>
-            <!-- <button type="button" class="coco-quick-action" v-if="cocoQuickTab === 'course'">
+            <!-- <button type="button" class="coco-quick-action" v-if="cocoQuickTab === 'course'" @click="sendQuickAction(lang.ssBatchInteractiveWeb)">
                 <span class="coco-quick-action-copy">
                 <span class="coco-quick-action-copy">
                     <span class="coco-quick-action-title">{{ lang.ssAiChatQuickBatchGenToolsTitle }}</span>
                     <span class="coco-quick-action-title">{{ lang.ssAiChatQuickBatchGenToolsTitle }}</span>
                     <span class="coco-quick-action-desc">{{ lang.ssAiChatQuickBatchGenToolsDesc }}</span>
                     <span class="coco-quick-action-desc">{{ lang.ssAiChatQuickBatchGenToolsDesc }}</span>
@@ -101,7 +101,12 @@
             </div>
             </div>
           </div>
           </div>
         </div>
         </div>
-        <div class="message-content ai-message chat" v-if="message.aiContent || message.loading">
+        <div class="message-content ai-message chat" :class="{'loading-state': message.chatloading && message.jsonData?.gType === 'trigger_batch_tool'}" v-if="message.aiContent || message.chatloading">
+          <div class="submenu-icon loading-icon" v-show="message.chatloading && message.jsonData?.gType === 'trigger_batch_tool'">
+            <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" class="spin">
+              <circle cx="12" cy="12" r="10" stroke-dasharray="32" stroke-dashoffset="12"></circle>
+            </svg>
+          </div>
           <div v-if="message.aiContent" v-html="message.aiContent"></div>
           <div v-if="message.aiContent" v-html="message.aiContent"></div>
           <svg v-else xmlns="http://www.w3.org/2000/svg" width="32" height="32"
           <svg v-else xmlns="http://www.w3.org/2000/svg" width="32" height="32"
             viewBox="0 0 24 24"><!-- Icon from SVG Spinners by Utkarsh Verma - https://github.com/n3r4zzurr0/svg-spinners/blob/main/LICENSE -->
             viewBox="0 0 24 24"><!-- Icon from SVG Spinners by Utkarsh Verma - https://github.com/n3r4zzurr0/svg-spinners/blob/main/LICENSE -->
@@ -119,7 +124,7 @@
             </circle>
             </circle>
           </svg>
           </svg>
           <button class="confirm-btn" :class="{ disabled: message.jsonData?.isGenerate }"
           <button class="confirm-btn" :class="{ disabled: message.jsonData?.isGenerate }"
-            v-if="message.jsonData?.gType !== 'chat' && message.jsonData?.gType !== 'generate_interactive_web' && message.jsonData?.gType !== 'generate_tool_recommend' && !message.chatloading && message.aiContent"
+            v-if="message.jsonData?.gType !== 'chat' && message.jsonData?.gType !== 'generate_interactive_web' && message.jsonData?.gType !== 'trigger_tool_recommend' && message.jsonData?.gType !== 'trigger_batch_tool' && !message.chatloading && message.aiContent"
             @click="generate(message)">{{ message.gLoading ? lang.ssLoading : lang.ssConfirm }}</button>
             @click="generate(message)">{{ message.gLoading ? lang.ssLoading : lang.ssConfirm }}</button>
           <button type="button" class="coco-interactive-web-card"
           <button type="button" class="coco-interactive-web-card"
             v-if="message.jsonData?.gType === 'generate_interactive_web' && !message.chatloading" :class="{ 'loading-state': create_app_loading }" @click="!create_app_loading && handle_add_aiWeb()">
             v-if="message.jsonData?.gType === 'generate_interactive_web' && !message.chatloading" :class="{ 'loading-state': create_app_loading }" @click="!create_app_loading && handle_add_aiWeb()">
@@ -254,6 +259,7 @@ interface ChatMessage {
       id?: string
       id?: string
       title?: string
       title?: string
     }>
     }>
+    aiContent?: string
   }
   }
 }
 }
 
 
@@ -433,7 +439,7 @@ const gType = ref('chat')
 
 
 const sendAction = async (action: string) => {
 const sendAction = async (action: string) => {
   const md = new MarkdownIt()
   const md = new MarkdownIt()
-  if (gType.value === 'chat' || gType.value === 'generate_interactive_web' || gType.value === 'generate_tool_recommend') {
+  if (gType.value === 'chat' || gType.value === 'generate_interactive_web' || gType.value === 'trigger_tool_recommend' || gType.value === 'trigger_batch_tool') {
     const result = chat_no_stream(action, agentid1.value, props.userid || '', lang.lang)
     const result = chat_no_stream(action, agentid1.value, props.userid || '', lang.lang)
     noStreamController.value = result
     noStreamController.value = result
     console.log(result)
     console.log(result)
@@ -452,7 +458,7 @@ const sendAction = async (action: string) => {
     }
     }
 
 
     // generate_qa // generate_choice_question // generate_photo // generate_poll
     // generate_qa // generate_choice_question // generate_photo // generate_poll
-    if (gType.value !== 'chat' && gType.value !== 'generate_interactive_web' && gType.value !== 'generate_tool_recommend') {
+    if (gType.value !== 'chat' && gType.value !== 'generate_interactive_web' && gType.value !== 'trigger_batch_tool') {
       messages.value.at(-1).jsonData = {
       messages.value.at(-1).jsonData = {
         gType: gType.value,
         gType: gType.value,
         isGenerate: false
         isGenerate: false
@@ -469,6 +475,17 @@ const sendAction = async (action: string) => {
       chatLoading.value = false
       chatLoading.value = false
       return
       return
     }
     }
+    else if (gType.value === 'trigger_batch_tool') {
+      messages.value.at(-1).jsonData = {
+        gType: gType.value,
+        isGenerate: false
+      }
+      messages.value.at(-1).aiContent = lang.ssBatchInteractiveWebDesc
+      // messages.value.at(-1).loading = false
+      // messages.value.at(-1).chatloading = false
+      // chatLoading.value = false
+      // return
+    }
 
 
     if (content.includes(lang.ssAiChatQuickGenWebTitle)) {
     if (content.includes(lang.ssAiChatQuickGenWebTitle)) {
       gType.value = 'generate_interactive_web'
       gType.value = 'generate_interactive_web'
@@ -485,11 +502,24 @@ const sendAction = async (action: string) => {
     }
     }
 
 
     if (content.includes(lang.ssAiChatQuickAction5)) {
     if (content.includes(lang.ssAiChatQuickAction5)) {
-      gType.value = 'generate_tool_recommend'
+      gType.value = 'trigger_tool_recommend'
+      messages.value.at(-1).jsonData = {
+        gType: 'trigger_tool_recommend',
+        isGenerate: false
+      }
+    }
+
+    if (content.includes(lang.ssBatchInteractiveWeb)) {
+      gType.value = 'trigger_batch_tool'
       messages.value.at(-1).jsonData = {
       messages.value.at(-1).jsonData = {
-        gType: 'generate_tool_recommend',
+        gType: 'trigger_batch_tool',
         isGenerate: false
         isGenerate: false
       }
       }
+      messages.value.at(-1).aiContent = lang.ssBatchInteractiveWebDesc
+      // messages.value.at(-1).loading = false
+      // chatLoading.value = false
+      // messages.value.at(-1).chatloading = false
+      return
     }
     }
   }
   }
   else {
   else {
@@ -578,7 +608,9 @@ const sendAction = async (action: string) => {
   console.log('textContents', textContents)
   console.log('textContents', textContents)
   let prompt = ''
   let prompt = ''
   let agentid = agentid2.value
   let agentid = agentid2.value
-  if (gType.value === 'generate_tool_recommend') {
+
+
+  if (gType.value === 'trigger_tool_recommend') {
     agentid = agentid3.value
     agentid = agentid3.value
     prompt = `
     prompt = `
   #课程标题(course_title): ${props.courseTitle || ''}
   #课程标题(course_title): ${props.courseTitle || ''}
@@ -586,6 +618,14 @@ const sendAction = async (action: string) => {
   #query: ${action}
   #query: ${action}
   `
   `
   }
   }
+  else if (gType.value === 'trigger_batch_tool') {
+    agentid = agentid4.value
+    prompt = `
+  #课程标题(course_title): ${props.courseTitle || ''}
+  #当前页面内容(all_pages_content): ${allTextContents.length > 0 ? allTextContents.join('\n') : '无文本内容'}
+  #query: ${action}
+  `
+  }
   else {
   else {
     prompt = `
     prompt = `
   #课程标题(course_title): ${props.courseTitle || ''}
   #课程标题(course_title): ${props.courseTitle || ''}
@@ -593,6 +633,35 @@ const sendAction = async (action: string) => {
   #query: ${action}
   #query: ${action}
   `
   `
   }
   }
+
+  if (gType.value === 'trigger_batch_tool') {
+    chat_stream(prompt, agentid, props.userid || '', lang.lang, (event) => {
+      if (event.type === 'message') {
+        messages.value.at(-1).jsonData.aiContent = md.render(event.data)
+        messages.value.at(-1).loading = false
+        
+      }
+      else if (event.type === 'messageEnd') {
+        messages.value.at(-1).jsonData.aiContent = md.render(event.data)
+        messages.value.at(-1).chatloading = false
+        console.log('event.data', event.data)
+        messages.value.push({
+          role: 'ai',
+          aiContent: lang.ssBatchInteractiveWebDesc2,
+        })
+        chatLoading.value = false
+      }
+    }, session_name.value, messages.value.at(-1).sourceFiles?.map(file => file.id).filter(Boolean)).then(controller => {
+      streamController.value = controller
+    }).catch(err => {
+      chatLoading.value = false
+      console.log('err', err)
+      stopMessage()
+    })
+    return
+
+  }
+  
   chat_stream(prompt, agentid, props.userid || '', lang.lang, (event) => {
   chat_stream(prompt, agentid, props.userid || '', lang.lang, (event) => {
     if (event.type === 'message') {
     if (event.type === 'message') {
       messages.value.at(-1).aiContent = md.render(event.data)
       messages.value.at(-1).aiContent = md.render(event.data)
@@ -664,7 +733,7 @@ const generate = (message: ChatMessage) => {
     const prompt = [
     const prompt = [
       {
       {
         role: 'user',
         role: 'user',
-        content: `这是用户输入的内容:“${message.aiContent}”,根据用户输入的内容,生成问答题的json。输出一个json格式的回复,格式如下:{"answerQ":"问题","answer":"","fileList":[],"imageList":[],"evaluationCriteria":"评价标准"}。输出语言为${lang.lang === 'en' ? '英文' : lang.lang === 'hk' ? '繁体中文' : '简体中文'}`,
+        content: `这是用户输入的内容:“${message.aiContent}”,根据用户输入的内容,生成问答题的json。输出一个json格式的回复,格式如下:{"answerQ":"问题","answer":"","fileList":[],"imageList":[],"evaluationCriteria":"评价标准"}。`, // 输出语言为${lang.lang === 'en' ? '英文' : lang.lang === 'hk' ? '繁体中文' : '简体中文'}
       },
       },
     ]
     ]
     chat_no_stream2(prompt, { type: 'json_object' }).then((res: any) => {
     chat_no_stream2(prompt, { type: 'json_object' }).then((res: any) => {
@@ -706,7 +775,7 @@ const generate = (message: ChatMessage) => {
     const prompt = [
     const prompt = [
       {
       {
         role: 'user',
         role: 'user',
-        content: `这是用户输入的内容:“${message.aiContent}”,根据用户输入的内容,生成拍照的json。输出一个json格式的回复,格式如下:{"answerQ":"问题","answer":"","fileList":[],"imageList":[],"evaluationCriteria":"评价标准"}。输出语言为${lang.lang === 'en' ? '英文' : lang.lang === 'hk' ? '繁体中文' : '简体中文'}`,
+        content: `这是用户输入的内容:“${message.aiContent}”,根据用户输入的内容,生成拍照的json。输出一个json格式的回复,格式如下:{"answerQ":"问题","answer":"","fileList":[]}。`, // ,"imageList":[],"evaluationCriteria":"评价标准" 输出语言为${lang.lang === 'en' ? '英文' : lang.lang === 'hk' ? '繁体中文' : '简体中文'}
       },
       },
     ]
     ]
     chat_no_stream2(prompt, { type: 'json_object' }).then((res: any) => {
     chat_no_stream2(prompt, { type: 'json_object' }).then((res: any) => {
@@ -781,6 +850,7 @@ const uploadFile2 = async (file: File, signal?: AbortSignal): Promise<any> => {
 const agentid1 = ref('cbb29b41-2a4a-4453-bf8d-357929ced4bd')// 判断意图
 const agentid1 = ref('cbb29b41-2a4a-4453-bf8d-357929ced4bd')// 判断意图
 const agentid2 = ref('f86aa63c-b7b7-4d03-9b37-b59f116d36f3')// 生成内容
 const agentid2 = ref('f86aa63c-b7b7-4d03-9b37-b59f116d36f3')// 生成内容
 const agentid3 = ref('caaa43ab-41a5-466a-94bc-74e0c9265068')// 互动工具推荐
 const agentid3 = ref('caaa43ab-41a5-466a-94bc-74e0c9265068')// 互动工具推荐
+const agentid4 = ref('b41bc650-9861-468c-a624-b52269dfff22')// 批量生成工具
 
 
 const isQuickActions = ref<string[]>([])
 const isQuickActions = ref<string[]>([])
 const toggleQuickActions = (section: string) => {
 const toggleQuickActions = (section: string) => {
@@ -877,6 +947,7 @@ onMounted(() => {
   getAgentModel(agentid1.value)
   getAgentModel(agentid1.value)
   getAgentModel(agentid2.value)
   getAgentModel(agentid2.value)
   getAgentModel(agentid3.value)
   getAgentModel(agentid3.value)
+  getAgentModel(agentid4.value)
 })
 })
 </script>
 </script>
 
 
@@ -1113,10 +1184,42 @@ onMounted(() => {
     line-height: 1.5;
     line-height: 1.5;
     color: #374151;
     color: #374151;
     width: fit-content;
     width: fit-content;
+    position: relative;
 
 
     +.message-content {
     +.message-content {
       margin-top: 10px;
       margin-top: 10px;
     }
     }
+
+    &.loading-state {
+      opacity: 0.7;
+      cursor: not-allowed;
+      pointer-events: none;
+
+      // .submenu-icon {
+      //   animation: pulse 1s infinite;
+      // }
+
+      .spin {
+        animation: spin 1s linear infinite;
+      }
+
+      .submenu-icon {
+        position: absolute;
+        width: 100%;
+        height: 100%;
+        background: #00000026;
+        top: 0;
+        left: 0;
+        display: flex;
+        align-items: center;
+        justify-content: center;
+
+        svg {
+          height: 30px;
+          color: #fff;
+        }
+      }
+    }
   }
   }
 }
 }
 
 
@@ -1421,9 +1524,9 @@ ul {
     cursor: not-allowed;
     cursor: not-allowed;
     pointer-events: none;
     pointer-events: none;
 
 
-    .submenu-icon {
-      animation: pulse 1s infinite;
-    }
+    // .submenu-icon {
+    //   animation: pulse 1s infinite;
+    // }
 
 
     .spin {
     .spin {
       animation: spin 1s linear infinite;
       animation: spin 1s linear infinite;

+ 4 - 1
src/views/lang/cn.json

@@ -928,5 +928,8 @@
   "ssInteractiveWebTitle": "当前页面互动网页",
   "ssInteractiveWebTitle": "当前页面互动网页",
   "ssInteractiveWebDesc": "已生成互动网页草稿,点击打开工作台后可确认新增为交互网页页面。",
   "ssInteractiveWebDesc": "已生成互动网页草稿,点击打开工作台后可确认新增为交互网页页面。",
   "ssOpenWorkspace": "点击打开工作台",
   "ssOpenWorkspace": "点击打开工作台",
-  "ssInteractiveWebWorkspaceDesc": "已生成互动网页草稿,点击卡片进入工作台。"
+  "ssInteractiveWebWorkspaceDesc": "已生成互动网页草稿,点击卡片进入工作台。",
+  "ssBatchInteractiveWeb": "为课件批量生成互动工具",
+  "ssBatchInteractiveWebDesc": "已收到「新课程」的批量生成任务,正在读取资料来源...",
+  "ssBatchInteractiveWebDesc2": "工具待选区,请在底部待选区页面处理「保留/丢弃/全部保留」。"
 }
 }

+ 4 - 1
src/views/lang/en.json

@@ -928,5 +928,8 @@
   "ssInteractiveWebTitle": "Current Page Interactive Web",
   "ssInteractiveWebTitle": "Current Page Interactive Web",
   "ssInteractiveWebDesc": "A draft interactive web page has been generated. Click to confirm and add it to the web page.",
   "ssInteractiveWebDesc": "A draft interactive web page has been generated. Click to confirm and add it to the web page.",
   "ssOpenWorkspace": "Open Workspace",
   "ssOpenWorkspace": "Open Workspace",
-  "ssInteractiveWebWorkspaceDesc": "Click to enter the workspace to confirm the interactive web page."
+  "ssInteractiveWebWorkspaceDesc": "Click to enter the workspace to confirm the interactive web page.",
+  "ssBatchInteractiveWeb": "Batch Generate Interactive Web Tools",
+  "ssBatchInteractiveWebDesc": "Received a batch generation task for the new course. Reading the source materials...",
+  "ssBatchInteractiveWebDesc2": "Tools are pending selection. Please process in the bottom selection page."
 }
 }

+ 4 - 1
src/views/lang/hk.json

@@ -928,5 +928,8 @@
   "ssInteractiveWebTitle": "當前頁面互動網頁",
   "ssInteractiveWebTitle": "當前頁面互動網頁",
   "ssInteractiveWebDesc": "已生成互動網頁草稿,點擊打開工作台後可確認新增為互動網頁頁面。",
   "ssInteractiveWebDesc": "已生成互動網頁草稿,點擊打開工作台後可確認新增為互動網頁頁面。",
   "ssOpenWorkspace": "打開工作台",
   "ssOpenWorkspace": "打開工作台",
-  "ssInteractiveWebWorkspaceDesc": "點擊卡片進入工作台,確認互動網頁。"
+  "ssInteractiveWebWorkspaceDesc": "點擊卡片進入工作台,確認互動網頁。",
+  "ssBatchInteractiveWeb": "為課件批量生成互動工具",
+  "ssBatchInteractiveWebDesc": "已收到「新课程」的批量生成任务,正在读取资料来源...",
+  "ssBatchInteractiveWebDesc2": "工具待选区,请在底部待选区页面处理「保留/丢弃/全部保留」。"
 }
 }