Prechádzať zdrojové kódy

feat(editor): 添加Coco AI功能并集成用户ID

- 在CollapsibleToolbar中添加Coco AI侧边栏入口和聊天界面
- 实现AI聊天功能,支持流式和非流式响应
- 更新markdown-it依赖至v14.1.1
- 在App.vue和Editor组件中传递用户ID参数
- 重构aiChat.ts代码,优化错误处理和代码风格
lsc 2 týždňov pred
rodič
commit
26be814719

+ 7 - 7
package-lock.json

@@ -22,7 +22,7 @@
         "html2canvas": "^1.4.1",
         "katex": "^0.16.22",
         "lodash": "^4.17.21",
-        "markdown-it": "^14.1.0",
+        "markdown-it": "^14.1.1",
         "mitt": "^3.0.1",
         "nanoid": "^5.0.7",
         "number-precision": "^1.6.0",
@@ -4163,9 +4163,9 @@
       }
     },
     "node_modules/markdown-it": {
-      "version": "14.1.0",
-      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
-      "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+      "version": "14.1.1",
+      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
+      "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
       "dependencies": {
         "argparse": "^2.0.1",
         "entities": "^4.4.0",
@@ -9263,9 +9263,9 @@
       "dev": true
     },
     "markdown-it": {
-      "version": "14.1.0",
-      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.0.tgz",
-      "integrity": "sha512-a54IwgWPaeBCAAsv13YgmALOF1elABB08FxO9i+r4VFk5Vl4pKokRPeX8u5TCgSsPi6ec1otfLjdOpVcgbpshg==",
+      "version": "14.1.1",
+      "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-14.1.1.tgz",
+      "integrity": "sha512-BuU2qnTti9YKgK5N+IeMubp14ZUKUUw7yeJbkjtosvHiP0AZ5c8IAgEMk79D0eC8F23r4Ac/q8cAIFdm2FtyoA==",
       "requires": {
         "argparse": "^2.0.1",
         "entities": "^4.4.0",

+ 1 - 1
package.json

@@ -29,7 +29,7 @@
     "html2canvas": "^1.4.1",
     "katex": "^0.16.22",
     "lodash": "^4.17.21",
-    "markdown-it": "^14.1.0",
+    "markdown-it": "^14.1.1",
     "mitt": "^3.0.1",
     "nanoid": "^5.0.7",
     "number-precision": "^1.6.0",

+ 1 - 0
src/App.vue

@@ -15,6 +15,7 @@
       <Editor3
         v-else-if="viewMode === 'editor3' && _isPC && !screening"
         :courseid="urlParams.courseid"
+        :userid="urlParams.userid"
         key="editor3"
       />
       <Student

+ 430 - 0
src/components/CollapsibleToolbar/componets/aiChat.vue

@@ -0,0 +1,430 @@
+<template>
+    <div class="ai-chat-container">
+        <!-- 聊天区域 -->
+        <div class="chat-section" v-if="messages.length > 0" ref="chatSection">
+            <!-- 消息列表 -->
+            <div v-for="(message, index) in messages" :key="index" class="chat-message">
+                <div class="message-content user-message chat" v-if="message.content">
+                    <div v-html="message.content"></div>
+                </div>
+                <div class="message-content ai-message chat" v-if="message.aiContent">
+                    <div v-html="message.aiContent"></div>
+                    <button class="confirm-btn" v-if="message.jsonData?.isChoice">确定</button>
+                </div>
+            </div>
+        </div>
+        <!-- 输入区域 -->
+        <div class="input-section">
+            <div class="input-wrapper">
+                <textarea class="ai-input"
+                    :placeholder="messages.length === 0 ? '例如:创建45分钟的四年级教学内容为水的三态变化的课程' : '输入 / 获取快捷操作短语'"
+                    v-model="inputText" @keyup.enter.exact="sendMessage" rows="5" />
+                <div class="input-actions">
+                    <button class="attach-btn" v-show="false">
+                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+                            <path
+                                d="M21.44 11.05l-9.19 9.19a6 6 0 01-8.49-8.49l9.19-9.19a4 4 0 015.66 5.66l-9.2 9.19a2 2 0 01-2.83-2.83l8.49-8.48">
+                            </path>
+                        </svg>
+                    </button>
+                    <button class="send-btn" @click="sendMessage">
+                        <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
+                            stroke-linecap="round" stroke-linejoin="round">
+                            <line x1="22" y1="2" x2="11" y2="13"></line>
+                            <polygon points="22 2 15 22 11 13 2 9 22 2"></polygon>
+                        </svg>
+                    </button>
+                </div>
+            </div>
+        </div>
+        <!-- 初始状态 -->
+        <div class="initial-state" v-if="messages.length === 0">
+            <!-- 快捷操作 -->
+            <div class="quick-actions">
+                <button class="quick-action-btn" @click="sendQuickAction('为当前页面内容生成2道选择题')">为当前页面内容生成2道选择题</button>
+                <button class="quick-action-btn" @click="sendQuickAction('为当前页面生成2页内容页面')">为当前页面生成2页内容页面</button>
+            </div>
+        </div>
+    </div>
+</template>
+
+<script lang="ts" setup>
+import { ref, onMounted, useTemplateRef, nextTick } from 'vue'
+import { chat_no_stream, chat_stream, getAgentModel } from '@/tools/aiChat'
+import MarkdownIt from 'markdown-it'
+
+interface ChatMessage {
+    uid?: string
+    role: 'ai' | 'user'
+    content?: string
+    aiContent?: string
+    oldContent?: string
+    rawContent?: string
+    timestamp?: Date
+    like?: boolean
+    unlike?: boolean
+    isTyping?: boolean
+    AI?: string
+    isShowSynchronization?: boolean
+    filename?: string
+    is_mind_map?: boolean
+    sourceFiles?: Array<{
+        title: string
+        id?: string
+        url?: string
+    }>
+    jsonData?: {
+        isChoice?: boolean
+        headUrl?: string
+        assistantName?: string
+        sourceArray?: Array<{
+            text?: string
+            id?: string
+            title?: string
+        }>
+    }
+}
+
+const props = withDefaults(defineProps<{
+    userid?: string | null
+}>(), {
+  userid: null,
+})
+
+
+
+const inputText = ref('')
+const messages = ref<ChatMessage[]>([])
+const chatSection = useTemplateRef<HTMLElement>('chatSection')
+const chatLoading = ref(false)
+
+const sendMessage = () => {
+  if (inputText.value.trim()) {
+    // 添加用户消息
+    messages.value.push({
+      role: 'user',
+      content: inputText.value
+    })
+
+    // 模拟AI回复
+    // setTimeout(() => {
+
+    //   setTimeout(() => {
+    //     messages.value.at(-1).aiContent = '课程生成完成!为您创建了5个内容页面和3个互动工具。您可以查看底部课程大纲,或在中央区域开始编辑。',
+    //     messages.value.at(-1).jsonData = {
+    //       isChoice: true
+    //     }
+    //   }, 1000)
+    // }, 500)
+    prevChatResult()
+    messages.value.at(-1).loading = true
+    chatLoading.value = true
+    sendAction(inputText.value)
+    inputText.value = ''
+  }
+}
+
+const prevChatResult = () => {
+  nextTick(() => {
+    if (chatSection.value) {
+      chatSection.value.scrollTop = chatSection.value.scrollHeight
+    }
+  })
+}
+
+const sendQuickAction = (action: string) => {
+  // 添加用户消息
+  messages.value.push({
+    role: 'user',
+    content: action
+  })
+
+  // 模拟AI回复
+  //   setTimeout(() => {
+  //     messages.value.at(-1).aiContent = '请问您想聚焦哪一重点?'
+  //   }, 500)
+  sendAction(action)
+}
+const sendAction = async (action: string) => {
+  const content = await chat_no_stream(action, agentid1.value, props.userid || '', 'zh-CN')
+  console.log(content)
+  // 渲染 Markdown 格式
+  const md = new MarkdownIt()
+  const html = md.render(content)
+  messages.value.at(-1).aiContent = html
+
+  chat_stream(action, agentid1.value, props.userid || '', 'zh-CN', (event) => {
+    if (event.type === 'message') {
+      messages.value.at(-1).aiContent = md.render(event.data)
+
+      messages.value.at(-1).loading = false
+      prevChatResult()
+    }
+    else if (event.type === 'messageEnd') {
+      messages.value.at(-1).aiContent = md.render(event.data)
+      chatLoading.value = false
+      prevChatResult()
+    }
+  }).catch(err => {
+    chatLoading.value = false
+    console.log('err', err)
+  })
+}
+const agentid1 = ref('4535eb8a-851b-4c47-a059-234f702d89c4')
+const agentid2 = ref('6c5b2386-f305-4062-bf53-125c0058fafa')
+
+
+onMounted(() => {
+  getAgentModel(agentid1.value)
+  getAgentModel(agentid2.value)
+})
+</script>
+
+<style lang="scss" scoped>
+.ai-chat-container {
+    width: 100%;
+    height: 100%;
+    display: flex;
+    flex-direction: column;
+    padding: 16px;
+    gap: 16px;
+}
+
+.input-section {
+    display: flex;
+    flex-direction: column;
+    gap: 12px;
+}
+
+.input-wrapper {
+    position: relative;
+    display: flex;
+    flex-direction: column;
+    background: #fafbfc;
+    border: 1.5px solid #e5e7eb;
+    border-radius: 8px;
+    padding: 8px 12px;
+    min-height: 120px;
+}
+
+.ai-input {
+    flex: 1;
+    border: none;
+    background: transparent;
+    font-size: 14px;
+    color: #374151;
+    outline: none;
+    resize: none;
+    min-height: 80px;
+
+    &::placeholder {
+        color: #9CA3AF;
+    }
+}
+
+.input-actions {
+    display: flex;
+    // justify-content: space-between;
+    align-items: center;
+    margin-top: 8px;
+}
+
+.attach-btn {
+    width: 32px;
+    height: 32px;
+    background: none;
+    border: none;
+    cursor: pointer;
+    padding: 4px;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    transition: all 0.3s ease;
+    color: #6b7280;
+
+    svg {
+        width: 20px;
+        height: 20px;
+    }
+
+    &:hover {
+        background: #FFF4E5;
+        color: #F78B22;
+        border-radius: 4px;
+    }
+}
+
+.send-btn {
+    margin-left: auto;
+    width: 32px;
+    height: 32px;
+    border: none;
+    background: #FF9300;
+    color: #fff;
+    border-radius: 50%;
+    cursor: pointer;
+    display: flex;
+    align-items: center;
+    justify-content: center;
+    transition: all 0.3s ease;
+
+    svg {
+        width: 20px;
+        height: 20px;
+    }
+
+    &:hover {
+        background: #E68A00;
+    }
+}
+
+.quick-actions {
+    // display: flex;
+    // flex-direction: column;
+    // gap: 8px;
+
+    .quick-action-btn {
+        padding: 5px 10px;
+        border: 1px solid #f7c58f;
+        background: #FFF9F2;
+        color: #6b4a1f;
+        border-radius: 16px;
+        font-size: 12px;
+        cursor: pointer;
+        text-align: left;
+        display: block;
+
+        &:hover {
+            border-color: #F78B22;
+            background: #FFF4E5;
+            color: #111827;
+        }
+
+        +.quick-action-btn {
+            margin-top: 8px;
+        }
+    }
+}
+
+
+
+.chat-section {
+    // flex: 1;
+    height: calc(100% - 155px);
+    overflow-y: auto;
+    display: flex;
+    flex-direction: column;
+    // gap: 16px;
+    padding-right: 8px;
+
+    &::-webkit-scrollbar {
+        width: 6px;
+    }
+
+    &::-webkit-scrollbar-track {
+        background: #F3F4F6;
+        border-radius: 3px;
+    }
+
+    &::-webkit-scrollbar-thumb {
+        background: #D1D5DB;
+        border-radius: 3px;
+
+        &:hover {
+            background: #9CA3AF;
+        }
+    }
+}
+
+.chat-message {
+    max-width: 100%;
+    margin-bottom: 10px;
+    display: flex;
+    flex-direction: column;
+
+    .message-content {
+        display: flex;
+        flex-direction: column;
+        border-radius: 8px;
+        padding: 8px 10px;
+        font-size: 14px;
+        line-height: 1.5;
+        color: #374151;
+        width: fit-content;
+
+        +.message-content {
+            margin-top: 10px;
+        }
+    }
+}
+
+.message-content {
+    &.ai-message {
+        align-self: flex-start;
+        background: #fafbfc;
+        border: 1.5px solid #e5e7eb;
+        border-bottom-left-radius: 2px;
+    }
+}
+
+.message-content {
+    &.user-message {
+        align-self: flex-end;
+        background: #FFF4E5;
+        border: 1.5px solid #F78B22;
+        border-bottom-right-radius: 2px;
+    }
+}
+
+.initial-state {
+    display: flex;
+    flex-direction: column;
+    justify-content: flex-start;
+    align-items: flex-start;
+    // padding: 24px;
+    gap: 16px;
+}
+
+
+.confirm-btn {
+    margin-top: 10px;
+    padding: 6px 15px;
+    background: #FF9300;
+    color: white;
+    border: none;
+    border-radius: 8px;
+    font-size: 14px;
+    cursor: pointer;
+    margin-left: auto;
+    transition: all 0.3s ease;
+
+    &:hover {
+        background: #E68A00;
+    }
+}
+
+ul {
+    margin: 8px 0;
+    padding-left: 20px;
+
+    li {
+        margin: 4px 0;
+    }
+}
+</style>
+
+<style>
+.chat table {
+    text-align: center;
+    border-spacing: 0;
+    border-left: 1px solid #000;
+    border-bottom: 1px solid #000;
+}
+
+.chat table td,
+.chat table th {
+    border-top: 1px solid #000;
+    border-right: 1px solid #000;
+    padding: 10px;
+}
+</style>

+ 40 - 2
src/components/CollapsibleToolbar/index2.vue

@@ -2,6 +2,14 @@
   <div class="collapsible-toolbar" :class="{ collapsed: isCollapsed }">
     <div class="toolbar-content" v-show="!isCollapsed">
       <div class="sidebar-content">
+        <div class="sidebar-item" :class="{ active: activeSubmenu === 'cocoai' }" @click="toggleSubmenu('cocoai')">
+          <svg class="item-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
+            <path d="M12 2L2 7l10 5 10-5-10-5z"></path>
+            <path d="M2 17l10 5 10-5"></path>
+            <path d="M2 12l10 5 10-5"></path>
+          </svg>
+          <span class="item-label">Coco AI</span>
+        </div>
         <div class="sidebar-item" :class="{ active: activeSubmenu === 'page' }" @click="toggleSubmenu('page')">
           <svg class="item-icon" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
             <g id="Component 1">
@@ -72,6 +80,23 @@
         </div>
       </div>
     </div>
+    <div class="submenu" :class="{ visible: activeSubmenu === 'cocoai' }">
+      <div class="submenu-title" style="margin-bottom: 0;">
+        <div class="title">Coco AI</div>
+        <div class="close-icon" @click="toggleSubmenu('cocoai')">
+          <svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg">
+            <g id="Component 3">
+              <g id="Component 1">
+                <path id="Vector" d="M16 18L12 14L16 10" stroke="#9CA3AF" stroke-width="1.33333" />
+              </g>
+            </g>
+          </svg>
+        </div>
+      </div>
+      <div class="submenu-content">
+        <AiChat :userid="props.userid" />
+      </div>
+    </div>
     <div class="submenu" :class="{ visible: activeSubmenu === 'page' }">
       <div class="submenu-title">
         <div class="title">{{ lang.ssAddTemplatePage }}</div>
@@ -495,6 +520,7 @@ import useCreateElement from '@/hooks/useCreateElement'
 import useSlideHandler from '@/hooks/useSlideHandler'
 import { useSlidesStore } from '@/store'
 import FileInput from '@/components/FileInput.vue'
+import AiChat from './componets/aiChat.vue'
 import { lang } from '@/main'
 import toolChoice from '@/assets/img/tool_choice.jpeg'
 import toolAnswer from '@/assets/img/tool_answer.png'
@@ -506,10 +532,14 @@ interface ContentItem {
   id?: string
 }
 
+
+
 const props = withDefaults(defineProps<{
   defaultCollapsed?: boolean
+  userid?: string | null
 }>(), {
-  defaultCollapsed: false
+  defaultCollapsed: false,
+  userid: null,
 })
 
 const emit = defineEmits<{
@@ -1063,8 +1093,8 @@ const handleParsingClose = () => {
   display: flex;
   justify-content: space-between;
   align-items: center;
-  margin-bottom: 20px;
   border-bottom: 1px solid #f0f0f0;
+  margin-bottom: 20px;
   width: 100%;
   box-sizing: border-box;
   padding: 12px 15px;
@@ -1102,6 +1132,14 @@ const handleParsingClose = () => {
   }
 }
 
+.submenu-content {
+  width: 100%;
+  height: calc(100% - 50px);
+  box-sizing: border-box;
+  overflow: hidden;
+}
+
+
 .submenu-img {
   width: calc(100% - 30px);
   margin: 0 auto;

+ 53 - 52
src/tools/aiChat.ts

@@ -1,8 +1,8 @@
-import axios from "@/services/config"
+import axios from '@/services/config'
 import { v4 as uuidv4 } from 'uuid'
 import { fetchEventSource } from '@microsoft/fetch-event-source'
 
-let model = {}
+const model = {}
 
 interface ChatParams {
   id: string;
@@ -21,56 +21,55 @@ interface ChatParams {
 }
 
 const DEFAULT_PARAMS: Omit<ChatParams, 'message' | 'uid' | 'stream'> = {
-  id: "a7741704-ba56-40b7-a6b8-62a423ef9376",
-  userId: "6c56ec0e-2c74-11ef-bee5-005056b86db5",
-  model: "open-doubao",
+  id: 'a7741704-ba56-40b7-a6b8-62a423ef9376',
+  userId: '6c56ec0e-2c74-11ef-bee5-005056b86db5',
+  model: 'open-doubao',
   file_ids: [],
-  sound_url: "",
+  sound_url: '',
   temperature: 0.2,
   top_p: 1,
   max_completion_tokens: 4096,
-  session_name: "pptSession_name",
-  tts_language: "zh-CN"
+  session_name: 'pptSession_name',
+  tts_language: 'zh-CN'
 }
 
 export const chat_no_stream = async (msg: string, agentId: string, userId: string, language: string): Promise<string> => {
-  let modelType = await getAgentModel(agentId)
+  const agentData = await getAgentModel(agentId)
   const params: ChatParams = {
     ...DEFAULT_PARAMS,
     id: agentId,
     message: msg,
     uid: uuidv4(),
     stream: false,
-    model: modelType,
+    model: agentData?.modelType || 'open-doubao',
     userId: userId,
     tts_language: language,
-    session_name:uuidv4()
-   };
-
-  try {
-    const res = await axios.post('https://appapi.cocorobo.cn/api/agentchats/ai_agent_chat', params);
-    let content = res.FunctionResponse.choices[0].message.content;
+    session_name: uuidv4()
+  }
 
-    // 清理可能的 markdown 格式
-    if (content.includes('```json')) {
-      // 提取 ```json 和 ``` 之间的内容
-      const jsonMatch = content.match(/```json\s*([\s\S]*?)\s*```/);
-      if (jsonMatch) {
-        content = jsonMatch[1].trim();
-      }
-    } else if (content.includes('```')) {
-      // 提取 ``` 和 ``` 之间的内容
-      const codeMatch = content.match(/```\s*([\s\S]*?)\s*```/);
-      if (codeMatch) {
-        content = codeMatch[1].trim();
-      }
+  const res = await axios.post('https://appapi.cocorobo.cn/api/agentchats/ai_agent_chat', params)
+  let content = res?.message || ''
+  console.log(content)
+  
+  // 清理可能的 markdown 格式
+  if (content.includes('```json')) {
+    // 提取 ```json 和 ``` 之间的内容
+    const jsonMatch = content.match(/```json\s*([\s\S]*?)\s*```/)
+    if (jsonMatch) {
+      content = jsonMatch[1].trim()
     }
-
-    return content
-  } catch (err) {
-    throw err;
   }
-};
+  else if (content.includes('```')) {
+    // 提取 ``` 和 ``` 之间的内容
+    const codeMatch = content.match(/```\s*([\s\S]*?)\s*```/)
+    if (codeMatch) {
+      content = codeMatch[1].trim()
+    }
+  }
+
+  return content
+
+}
 
 export const chat_stream = async (
   msg: string,
@@ -79,20 +78,20 @@ export const chat_stream = async (
   language: string,
   onMessage: (event: { type: 'message' | 'close' | 'error' | 'messageEnd'; data: string }) => void
 ): Promise<void> => {
-  let modelType = await getAgentModel(agentId)
+  const agentData = await getAgentModel(agentId)
   const params: ChatParams = {
     ...DEFAULT_PARAMS,
     id: agentId,
     message: msg,
     uid: uuidv4(),
     stream: true,
-    model: modelType,
+    model: agentData?.modelType || 'open-doubao',
     userId: userId,
     tts_language: language,
-    session_name:uuidv4()
-  };
+    session_name: uuidv4()
+  }
 
-  const ctrl = new AbortController();
+  const ctrl = new AbortController()
   let content = ''
   try {
     await fetchEventSource('https://appapi.cocorobo.cn/api/agentchats/ai_agent_chat', {
@@ -103,19 +102,20 @@ export const chat_stream = async (
         'Content-Type': 'application/json',
       },
       onmessage(event) {
-        let data = JSON.parse(event.data)
+        const data = JSON.parse(event.data)
         if (data.content) {
           if (data.content != '[DONE]') {
             content += data.content
             onMessage({
               type: 'message',
               data: content
-            });
-          }else{
+            })
+          }
+          else {
             onMessage({
               type: 'messageEnd',
               data: content
-            });
+            })
           }
 
         }
@@ -124,27 +124,28 @@ export const chat_stream = async (
         onMessage({
           type: 'close',
           data: 'SSE Connection closed'
-        });
+        })
       },
       onerror(err) {
         onMessage({
           type: 'error',
           data: err.message || 'Unknown error'
-        });
+        })
         // 返回 undefined 以阻止自动重连,如需重连则删除此行
-        throw err;
+        throw err
       },
-    });
-  } finally {
-    ctrl.abort();
+    })
+  }
+  finally {
+    ctrl.abort()
   }
-};
+}
 
 export const getAgentModel = async (agentId: string) => {
   if (model[agentId]) {
     return model[agentId]
   }
-  let res = await axios.get(`https://appapi.cocorobo.cn/api/agents/agent/${agentId}`)
-  model[agentId] = res['modelType']
+  const res = await axios.get(`https://appapi.cocorobo.cn/api/agents/agent/${agentId}`)
+  model[agentId] = res
   return model[agentId]
 }

+ 3 - 1
src/views/Editor/index3.vue

@@ -142,7 +142,7 @@
     </div>
     <!-- <EditorHeader class="layout-header" /> -->
     <div class="layout-content">
-      <CollapsibleToolbar class="layout-sidebar" @toggle="handleToolbarToggle" />
+      <CollapsibleToolbar class="layout-sidebar" @toggle="handleToolbarToggle" :userid="props.userid" />
       <div class="layout-content-center">
         <CanvasTool class="center-top" />
         <Canvas class="center-body" :style="{ height: `calc(100% - ${remarkHeight + 60}px  - 120px)` }"
@@ -217,10 +217,12 @@ const parentWindow = window.parent as ParentWindowWithToolList
 
 interface Props {
   courseid?: string | null
+  userid?: string | null
 }
 
 const props = withDefaults(defineProps<Props>(), {
   courseid: null,
+  userid: null,
 })
 
 const mainStore = useMainStore()

+ 107 - 102
src/views/Student/components/choiceQuestionDetailDialog.vue

@@ -231,7 +231,7 @@ import useImport from '@/hooks/useImport'
 import { lang } from '@/main'
 import selectUserDialog from './selectUserDialog.vue'
 import { chat_stream } from '@/tools/aiChat'
-import axios from "@/services/config"
+import axios from '@/services/config'
 const props = defineProps<{
   visible: number[];
   workIndex: number;
@@ -266,7 +266,7 @@ const previewImageToolRef = ref<any>(null)
 // 选择用户组件
 const selectUserDialogRef = ref<any>(null)
 
-//ai分析数据
+// ai分析数据
 const aiAnalysisData = ref<Array<any>>([])
 
 const md = new MarkdownIt()
@@ -484,7 +484,7 @@ const setEchartsArea1 = () => {
   if (myChart.value) {
     const _work =
       props.showData.choiceQuestionListData[props.showData.workIndex]
-      console.log('_work', _work)
+    console.log('_work', _work)
     // 修正版,处理xAxis.data内为图片对象的case,formatter始终只拿到src或自定义label,保证无[object Object]问题
     const option = {
       tooltip: {
@@ -644,22 +644,23 @@ const setEchartsArea1 = () => {
 }
 
 // 获取分析
-const getAnalysis = ()=>{
-  if(!props.showData.workDetail.id){
+const getAnalysis = () => {
+  if (!props.showData.workDetail.id) {
     return
   }
   const params = {
-    pid:props.showData.workDetail.id,
+    pid: props.showData.workDetail.id,
   }
-  axios.get('https://pbl.cocorobo.cn/api/pbl/select_pptAnalysisByPid?pid='+params.pid).then(res => {
+  axios.get('https://pbl.cocorobo.cn/api/pbl/select_pptAnalysisByPid?pid=' + params.pid).then(res => {
     const data = res[0]
-    if(data.length){
+    if (data.length) {
       aiAnalysisData.value = data
-    }else{
+    }
+    else {
       aiAnalysisData.value = []
     }
   }).catch(err => {
-    console.log('get_pptAnalysis_err',err)
+    console.log('get_pptAnalysis_err', err)
   })
 }
 
@@ -777,8 +778,8 @@ const viewUnsubmittedStudents = () => {
 }
 
 // ai生成选择题分析 选择题
-const aiAnalysisRefresh45 = ()=>{
-   const _work = props.showData.choiceQuestionListData[props.showData.workIndex]
+const aiAnalysisRefresh45 = () => {
+  const _work = props.showData.choiceQuestionListData[props.showData.workIndex]
   const msg = `# CONTEXT #
 你是K-12阶段的AI教育课堂分析助手,基于上传的课件、逐字稿,以及当页的学生答题数据(选择题/问答题/智能体对话)进行智能分析。
 
@@ -792,7 +793,7 @@ const aiAnalysisRefresh45 = ()=>{
 当前页面答题数据(选择题):【分析重点】
 - 选择题题目:${_work.teststitle}
 - 选项数据:${JSON.stringify(_work.choiceUser)}
-- 题目图片:${_work.timuList.lenght?_work.timuList[0].src:''}
+- 题目图片:${_work.timuList.lenght ? _work.timuList[0].src : ''}
 - 未提交学生:${JSON.stringify(props.showData.unsubmittedStudents.map((item: any) => item.name))}
 
 # ANALYSIS RULES #
@@ -809,53 +810,55 @@ const aiAnalysisRefresh45 = ()=>{
 样例:
 选择题正确率62%,核心概念“机器学习三步骤”(输入数据→训练模型→预测结果)掌握尚可,但“训练与预测区分”混淆率达38%;建议强化训练vs预测对比教学。`
 
-if(!currentAnalysis.value){
-  aiAnalysisData.value.push({
-    pid:props.showData.workDetail.id,
-    index:props.showData.workIndex,
-    loading:true,
-    json:'',
-    noEnd:true,
-    update_at:'',
-    create_at:"",
-  })
-}else{
-  aiAnalysisData.value.find((item:any)=>{
+  if (!currentAnalysis.value) {
+    aiAnalysisData.value.push({
+      pid: props.showData.workDetail.id,
+      index: props.showData.workIndex,
+      loading: true,
+      json: '',
+      noEnd: true,
+      update_at: '',
+      create_at: '',
+    })
+  }
+  else {
+    aiAnalysisData.value.find((item:any) => {
       return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
     }).loading = true
-  aiAnalysisData.value.find((item:any)=>{
+    aiAnalysisData.value.find((item:any) => {
       return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
-    }).json = ""
-}
+    }).json = ''
+  }
 
-chat_stream(msg,'a7741704-ba56-40b7-a6b8-62a423ef9376', props.userId, 'zh-CN', (event) => {
-  if (event.type === 'message') { 
-    aiAnalysisData.value.find((item:any)=>{
-      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
-    }).json = event.data
+  chat_stream(msg, 'a7741704-ba56-40b7-a6b8-62a423ef9376', props.userId, 'zh-CN', (event) => {
+    if (event.type === 'message') { 
+      aiAnalysisData.value.find((item:any) => {
+        return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+      }).json = event.data
 
-    aiAnalysisData.value.find((item:any)=>{
-      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
-    }).loading = false
-  }else if (event.type === 'messageEnd') {
-    aiAnalysisData.value.find((item:any)=>{
-      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
-    }).json = event.data
-    aiAnalysisData.value.find((item:any)=>{
-      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
-    }).noEnd = false
-    aiAnalysisData.value.find((item:any)=>{
-      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
-    }).update_at = new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-')
-    saveAnalysis()
-  }
-}).catch(err => {
-  console.log('err',err)
-})
+      aiAnalysisData.value.find((item:any) => {
+        return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+      }).loading = false
+    }
+    else if (event.type === 'messageEnd') {
+      aiAnalysisData.value.find((item:any) => {
+        return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+      }).json = event.data
+      aiAnalysisData.value.find((item:any) => {
+        return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+      }).noEnd = false
+      aiAnalysisData.value.find((item:any) => {
+        return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+      }).update_at = new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-')
+      saveAnalysis()
+    }
+  }).catch(err => {
+    console.log('err', err)
+  })
 }
 
 // 问答题
-const aiAnalysisRefresh15 = ()=>{
+const aiAnalysisRefresh15 = () => {
   const msg = `# CONTEXT #
 你是K-12阶段的AI教育课堂分析助手,基于上传的课件、逐字稿,以及当页的学生答题数据(选择题/问答题/智能体对话)进行智能分析。
 
@@ -868,7 +871,7 @@ const aiAnalysisRefresh15 = ()=>{
 - 课程学科:${props.courseDetail.name}
 当前页面答题数据(问答题):【分析重点】
 - 问答题题目:${props.showData.workDetail.json.answerQ}
-- 回答数据:${JSON.stringify(processedWorkArray.value.map((i)=>({user:i.name,answer:i.content.answer})))}
+- 回答数据:${JSON.stringify(processedWorkArray.value.map((i) => ({user: i.name, answer: i.content.answer})))}
 - 未提交学生:${JSON.stringify(props.showData.unsubmittedStudents.map((item: any) => item.name))}
 
 # ANALYSIS RULES #
@@ -884,76 +887,78 @@ const aiAnalysisRefresh15 = ()=>{
 # EXAMPLES #
 样例:
 选择题正确率62%,核心概念“机器学习三步骤”(输入数据→训练模型→预测结果)掌握尚可,但“训练与预测区分”混淆率达38%;建议强化训练vs预测对比教学。`
-if(!currentAnalysis.value){
-  aiAnalysisData.value.push({
-    pid:props.showData.workDetail.id,
-    index:props.showData.workIndex,
-    loading:true,
-    json:'',
-    noEnd:true,
-    update_at:'',
-    create_at:"",
-  })
-}else{
-  aiAnalysisData.value.find((item:any)=>{
+  if (!currentAnalysis.value) {
+    aiAnalysisData.value.push({
+      pid: props.showData.workDetail.id,
+      index: props.showData.workIndex,
+      loading: true,
+      json: '',
+      noEnd: true,
+      update_at: '',
+      create_at: '',
+    })
+  }
+  else {
+    aiAnalysisData.value.find((item:any) => {
       return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
     }).loading = true
-  aiAnalysisData.value.find((item:any)=>{
+    aiAnalysisData.value.find((item:any) => {
       return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
-    }).json = ""
-}
+    }).json = ''
+  }
 
-chat_stream(msg,'a7741704-ba56-40b7-a6b8-62a423ef9376', props.userId, 'zh-CN', (event) => {
-  if (event.type === 'message') { 
-    aiAnalysisData.value.find((item:any)=>{
-      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
-    }).json = event.data
+  chat_stream(msg, 'a7741704-ba56-40b7-a6b8-62a423ef9376', props.userId, 'zh-CN', (event) => {
+    if (event.type === 'message') { 
+      aiAnalysisData.value.find((item:any) => {
+        return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+      }).json = event.data
 
-    aiAnalysisData.value.find((item:any)=>{
-      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
-    }).loading = false
-  }else if (event.type === 'messageEnd') {
-    aiAnalysisData.value.find((item:any)=>{
-      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
-    }).json = event.data
-    aiAnalysisData.value.find((item:any)=>{
-      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
-    }).noEnd = false
-    aiAnalysisData.value.find((item:any)=>{
-      return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
-    }).update_at = new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-')
-    saveAnalysis()
-  }
-}).catch(err => {
-  console.log('err',err)
-})
+      aiAnalysisData.value.find((item:any) => {
+        return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+      }).loading = false
+    }
+    else if (event.type === 'messageEnd') {
+      aiAnalysisData.value.find((item:any) => {
+        return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+      }).json = event.data
+      aiAnalysisData.value.find((item:any) => {
+        return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+      }).noEnd = false
+      aiAnalysisData.value.find((item:any) => {
+        return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
+      }).update_at = new Date().toLocaleString('zh-CN', { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit', hour12: false }).replace(/\//g, '-')
+      saveAnalysis()
+    }
+  }).catch(err => {
+    console.log('err', err)
+  })
 }
 
 
 // 当前分析
-const currentAnalysis = computed(()=>{
-  return aiAnalysisData.value.find((item:any)=>{
+const currentAnalysis = computed(() => {
+  return aiAnalysisData.value.find((item:any) => {
     return item.pid === props.showData.workDetail.id && item.index === props.showData.workIndex
   })
 })
 
 // 保存分析
-const saveAnalysis = ()=>{
-  if(!currentAnalysis.value){
+const saveAnalysis = () => {
+  if (!currentAnalysis.value) {
     return
   }
   const params = [{
-    pid:props.showData.workDetail.id,
-    idx:props.showData.workIndex,
-    json:currentAnalysis.value.json,
+    pid: props.showData.workDetail.id,
+    idx: props.showData.workIndex,
+    json: currentAnalysis.value.json,
   }]
 
-  axios.post('https://pbl.cocorobo.cn/api/pbl/insert_pptAnalysis',params).then(res => {
-    if(res==1){
-      console.log("保存成功")
+  axios.post('https://pbl.cocorobo.cn/api/pbl/insert_pptAnalysis', params).then(res => {
+    if (res == 1) {
+      console.log('保存成功')
     }
   }).catch(err => {
-    console.log('insert_pptAnalysis_err',err)
+    console.log('insert_pptAnalysis_err', err)
   })
 }