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

feat(editor&aiChat): 新增数学公式渲染和幻灯片切换缩放重置功能

1. 为AI聊天组件添加Katex数学公式渲染支持,实现行内和块级公式的解析与渲染
2. 新增幻灯片索引监听,切换页码时将编辑器字体缩放重置为1
3. 优化AI生成选择题和工具的prompt,保留公式中的$符号以便正确解析
4. 新增.git-commit-message规则配置文件
lsc 5 часов назад
Родитель
Сommit
02316fb15b

+ 6 - 0
.trae/rules/git-commit-message.md

@@ -0,0 +1,6 @@
+---
+alwaysApply: true
+scene: git_message
+---
+
+在此处编写规则,自定义 AI 生成提交信息的风格。要中文,不能是繁体中文。

+ 49 - 5
src/components/CollapsibleToolbar/componets/aiChat.vue

@@ -221,6 +221,8 @@ import FileInput from '@/components/FileInput2.vue'
 import axios from '@/services/config'
 import message from '@/utils/message'
 import AiWeb from './aiWeb.vue'
+import katex from 'katex'
+import 'katex/dist/katex.min.css'
 
 interface ChatMessage {
   uid?: string
@@ -451,7 +453,8 @@ const loadingDots = ref('...')
 const loadingInterval = ref(null)
 
 const sendAction = async (action: string) => {
-  const md = new MarkdownIt()
+  // html: true 保留 katexZ 生成的公式HTML,使其能正常渲染到页面
+  const md = new MarkdownIt({ html: true })
   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)
     noStreamController.value = result
@@ -744,13 +747,13 @@ const sendAction = async (action: string) => {
   
   chat_stream(prompt, agentid, props.userid || '', lang.lang, (event) => {
     if (event.type === 'message') {
-      messages.value.at(-1).aiContent = md.render(event.data)
+      messages.value.at(-1).aiContent = md.render(katexZ(event.data))
 
       messages.value.at(-1).loading = false
       prevChatResult()
     }
     else if (event.type === 'messageEnd') {
-      messages.value.at(-1).aiContent = md.render(event.data)
+      messages.value.at(-1).aiContent = md.render(katexZ(event.data))
       messages.value.at(-1).chatloading = false
       chatLoading.value = false
       prevChatResult()
@@ -768,6 +771,47 @@ import useSlideHandler from '@/hooks/useSlideHandler'
 const { createSlide } = useSlideHandler()
 const { createFrameElement } = useCreateElement()
 
+
+const katexZ = (str) => {
+  if (!str) return ''
+  const escapeHtml = (text) => {
+    return text
+      .replace(/&/g, '&')
+      .replace(/</g, '&lt;')
+      .replace(/>/g, '&gt;')
+  }
+  try {
+    let result = ''
+    let lastIndex = 0
+    // 匹配 $$...$$(块级公式)或 $...$(行内公式)
+    const regex = /\$\$([\s\S]+?)\$\$|\$([^$]+?)\$/g
+    let match
+    while ((match = regex.exec(str)) !== null) {
+      // 转义并拼接公式前的纯文本
+      result += escapeHtml(str.substring(lastIndex, match.index))
+      if (match[1] !== undefined) {
+        // 块级公式
+        result += katex.renderToString(match[1], {
+          throwOnError: false,
+          displayMode: true
+        })
+      }
+      else {
+        // 行内公式
+        result += katex.renderToString(match[2], {
+          throwOnError: false
+        })
+      }
+      lastIndex = regex.lastIndex
+    }
+    // 拼接剩余纯文本
+    result += escapeHtml(str.substring(lastIndex))
+    return result || escapeHtml(str)
+  }
+  catch (e) {
+    return str
+  }
+}
 const setUrl = () => {
   let url = 'https://beta.pbl.cocorobo.cn'
   if (lang.lang === 'cn') {
@@ -792,7 +836,7 @@ const generate = (message: ChatMessage) => {
     const prompt = [
       {
         role: 'user',
-        content: `这是用户输入的内容:“${message.aiContent}”,根据用户输入的内容,生成选择题的json。输出一个json格式的回复,格式如下:{"testCount":1,"testTitle":"","testJson":[{"id":"7de1fdb4-bec3-4324-8986-4623f838e3d7","type":"2","teststitle":"1+1?","checkList":["1","2","3"],"timuList":[],"answer":[1],"userAnswer":[],"explanation":"解析"}]}。`, // 输出语言为${lang.lang === 'en' ? '英文' : lang.lang === 'hk' ? '繁体中文' : '简体中文'}
+        content: `这是用户输入的内容:“${message.aiContent}”,根据用户输入的内容,有数学公式的话要保留对应的符号比如$,生成选择题的json。输出一个json格式的回复,格式如下:{"testCount":1,"testTitle":"","testJson":[{"id":"7de1fdb4-bec3-4324-8986-4623f838e3d7","type":"2","teststitle":"1+1?","checkList":["1","2","3"],"timuList":[],"answer":[1],"userAnswer":[],"explanation":"解析"}]}。`, // 输出语言为${lang.lang === 'en' ? '英文' : lang.lang === 'hk' ? '繁体中文' : '简体中文'}
       },
     ]
     chat_no_stream2(prompt, { type: 'json_object' }).then(async (res: any) => {
@@ -1007,7 +1051,7 @@ const createTool = async (array: any[], tool: any, index: number, slide: any) =>
   
   const res = await chat_no_stream2([{
     role: 'user',
-    content: `这是用户输入的内容:"${content}",根据用户输入的内容,生成json。输出一个json格式的回复,格式如下:${jsonFormat}。`,
+    content: `这是用户输入的内容:"${content}",根据用户输入的内容,有数学公式的话要保留对应的符号比如$,生成json。输出一个json格式的回复,格式如下:${jsonFormat}。`,
   }], { type: 'json_object' })
   
   console.log(`${tool.tool_name}`, res)

+ 6 - 1
src/views/Editor/CanvasTool/index2.vue

@@ -307,7 +307,7 @@ const speakingStore = useSpeakingStore()
 const articleStore = useArticleReadingStore()
 const { creatingElement, creatingCustomShape, showSelectPanel, showSearchPanel, showNotesPanel } = storeToRefs(mainStore)
 const { canUndo, canRedo } = storeToRefs(useSnapshotStore())
-const { currentSlide } = storeToRefs(slidesStore)
+const { currentSlide, slideIndex } = storeToRefs(slidesStore)
 const { previewState } = storeToRefs(speakingStore)
 const { previewStage: articlePreviewStage } = storeToRefs(articleStore)
 
@@ -384,6 +384,11 @@ const changeFontScale = (delta: number) => {
   setToolIframeFontSize()
 }
 
+// 切换页码时重置缩放比例回1
+watch(slideIndex, () => {
+  fontScale.value = 1
+})
+
 const iframeLabel = computed(() => {
   return getTypeLabel(frametype.value)
 })