SanHQin vor 1 Monat
Ursprung
Commit
475187fc73

+ 1 - 1
dist/index.html

@@ -32,7 +32,7 @@
       width: 100%;
       background: #e6eaf0;
       font-family: '黑体';
-    }</style><link href=./static/css/app.3f295093c5232d62e65e07860f5112c4.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.23ea04dc469b57e2b4f8.js></script><script type=text/javascript src=./static/js/vendor.062f14352151ff2301df.js></script><script type=text/javascript src=./static/js/app.ea97d8e640be741e3566.js></script></body></html><script>function stopSafari() {
+    }</style><link href=./static/css/app.97d791c92beb0950fd2f0e97231b6e45.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=./static/js/manifest.23ea04dc469b57e2b4f8.js></script><script type=text/javascript src=./static/js/vendor.062f14352151ff2301df.js></script><script type=text/javascript src=./static/js/app.8e4a79d40a2a42379658.js></script></body></html><script>function stopSafari() {
     //阻止safari浏览器双击放大功能
     let lastTouchEnd = 0  //更新手指弹起的时间
     document.documentElement.addEventListener("touchstart", function (event) {

Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
dist/static/css/app.97d791c92beb0950fd2f0e97231b6e45.css


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
dist/static/css/app.97d791c92beb0950fd2f0e97231b6e45.css.map


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
dist/static/js/app.8e4a79d40a2a42379658.js


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
dist/static/js/app.8e4a79d40a2a42379658.js.map


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
dist/static/js/manifest.23ea04dc469b57e2b4f8.js.map


Datei-Diff unterdrückt, da er zu groß ist
+ 0 - 0
dist/static/js/workPage-manifest.2ece51fa34be51c8610a.js.map


+ 16 - 7
src/components/pages/classroomObservation/components/baseMessage.vue

@@ -976,25 +976,34 @@ ${this.data.editorBarData?this.data.editorBarData.content:""}
 					//解码音频数据
 					const buffer = await audioContext.decodeAudioData(e.target.result);
 
-					//创建离线音频上下文
-					const offlineAudioContext = new OfflineAudioContext({numberOfChannels:buffer.numberOfChannels,length:buffer.length,sampleRate:buffer.sampleRate});
+					// 目标采样率为8000
+					const targetSampleRate = 8000;
+
+					// 计算目标离线音频上下文长度(单位frame数)
+					const duration = buffer.duration;
+					const length = Math.ceil(duration * targetSampleRate);
+
+					// 创建离线音频上下文,采样率为8000
+					const offlineAudioContext = new OfflineAudioContext({
+						numberOfChannels: buffer.numberOfChannels,
+						length: length,
+						sampleRate: targetSampleRate
+					});
 
-					//创建音源节点
+					// 创建音源节点
 					const source = offlineAudioContext.createBufferSource();
 					source.buffer = buffer;
 					source.connect(offlineAudioContext.destination);
 					source.start();
 
-					//渲染音频
+					// 渲染音频
 					const renderedBuffer = await offlineAudioContext.startRendering();
 					const wavBlob = this.bufferToWav(renderedBuffer);
 
 					// blob转成file文件
 					const audioFile = new File([wavBlob], 'audio.wav', { type: 'audio/wav' });
 
-					// console.log(wavBlob)
-
-					this.$emit("getVideoAudioSuccess",audioFile)
+					this.$emit("getVideoAudioSuccess", audioFile)
 					this.$message.success("提取音频成功")
 					this.loading = false;
 				}

+ 348 - 2
src/components/pages/classroomObservation/components/chatArea.vue

@@ -110,12 +110,12 @@
           >
         </el-popover>
         <el-popover placement="top" trigger="hover">
-          <el-button size="small" @click.stop="startContinuousJobs('role')">
+          <el-button size="small" @click.stop="startContinuousJobs2('role')">
             说话人编码
           </el-button>
           <el-button
             size="small"
-            @click.stop="startContinuousJobs('actionType')"
+            @click.stop="startContinuousJobs2('actionType')"
           >
             行为编码
           </el-button>
@@ -2931,6 +2931,352 @@ ${JSON.stringify(_list)}
       });
       this.jobContext = null;
     },
+    async startContinuousJobs2(type /* role, actionType */) {
+      const key = "default";
+      const { appToken, options, attention } = {
+        role: {
+          appToken: "app-TonzLPv7rPG0EtnFKszOWjwt",
+          options: ROLE_OPTIONS_GROUP[key],
+          attention: undefined
+        },
+        actionType: {
+          appToken: "app-zOMxBqyEKoJSvW10e5SS0kgj",
+          options: OPTIONS_GROUP[key],
+          attention: ATTENTION_GROUP[key]
+        }
+      }[type];
+      const config = {
+        headers: {
+          Authorization: `Bearer ${appToken}`,
+          "Content-Type": "application/json"
+        }
+      };
+      const content = this.editorBarData.content;
+      const div = document.createElement("div");
+      div.innerHTML = content;
+      const tableRows = _.slice(div.querySelectorAll(`table tbody tr`), 1);
+      if (!tableRows.length || tableRows[0].cells.length < 7) {
+        this.$notify.info("没有可编码的内容");
+        return;
+      }
+      const ctrl = new AbortController();
+      this.jobContext = {
+        ctrl,
+        status: "running",
+        restart: () => {},
+        pause: () => {},
+        stop: () => {
+          const err = new Error();
+          err.name = "StopError";
+          this.jobContext.ctrl.abort(err);
+        },
+        progress: {
+          current: 0,
+          currentSize: 0,
+          total: tableRows.length,
+          percentage: 0
+        }
+      };
+
+      // 角色编码优化
+      if (type === "role") {
+        // 1. 收集所有唯一的Guest-XX,及空白role
+        // Build: guestRoles: { "Guest-1":[row1,row2...], ... }, emptyRoleRows:[row]
+        const guestRoleMap = {};
+        const emptyRoleRows = [];
+        tableRows.forEach((row, i) => {
+          const roleVal = row.cells[5].textContent.trim();
+          if (/^Guest-\d+$/.test(roleVal)) {
+            if (!guestRoleMap[roleVal]) guestRoleMap[roleVal] = [];
+            guestRoleMap[roleVal].push(row);
+          } else if (!roleVal) {
+            emptyRoleRows.push(row);
+          }
+        });
+
+        console.log("guestRoleMap", guestRoleMap);
+        console.log("emptyRoleRows", emptyRoleRows);
+
+        // 用于追踪已完成编码的条数字段
+        let completedCount = 0;
+
+        // 2. 为每个Guest-XX处理
+        for (const guest in guestRoleMap) {
+          // 不分批,直接全部guest一起处理
+          const rowsBatch = guestRoleMap[guest];
+          this.jobContext.progress.currentSize = rowsBatch.length;
+          // 进度 = (已完成条数) / 总数 * 100
+          this.jobContext.progress.current = completedCount;
+          this.jobContext.progress.percentage = (completedCount / tableRows.length) * 100;
+          // 取rows的content
+          const reqRows = rowsBatch.map(r => ({
+            content: r.cells[3].textContent,
+            role: guest
+          }));
+          let chunkResult = [];
+          let pauseCtrl = new AbortController();
+          this.jobContext.pause = () => {
+            const err = new Error();
+            err.name = "PauseError";
+            pauseCtrl.abort(err);
+          };
+          while (true) {
+            try {
+              const res = await fetch(
+                "https://dify.cocorobo.cn/v1/workflows/run",
+                {
+                  signal: AbortSignal.any([
+                    this.jobContext.ctrl.signal,
+                    pauseCtrl.signal
+                  ]),
+                  method: "POST",
+                  body: JSON.stringify({
+                    inputs: {
+                      rows: JSON.stringify(reqRows),
+                      options: options.join(","),
+                      attention
+                    },
+                    response_mode: "blocking",
+                    user: this.userId
+                  }),
+                  ...config
+                }
+              ).then(res => res.json());
+              const error = _.get(res, ["data", "error"], null);
+              if (error) {
+                const err = new Error(error);
+                err.name = "DifyError";
+                throw err;
+              }
+              chunkResult = _.get(res, ["data", "outputs", "result"], []);
+              break; // 正常
+            } catch (err) {
+              if (err.name === "StopError") return;
+              this.jobContext.status = "paused";
+              if (err.name === "PauseError") {
+                this.jobContext.error = `用户暂停`;
+              } else {
+                this.jobContext.error = `部分生成失败。点击按钮可继续尝试生成`;
+              }
+              // 等待继续
+              try {
+                const restartPromise = new Promise((resolve, reject) => {
+                  this.jobContext.restart = resolve;
+                  this.jobContext.ctrl.signal.addEventListener("abort", reject);
+                });
+                await restartPromise;
+                this.jobContext.status = "running";
+                this.jobContext.error = null;
+                pauseCtrl = new AbortController();
+                this.jobContext.pause = () => {
+                  const err2 = new Error();
+                  err2.name = "PauseError";
+                  pauseCtrl.abort(err2);
+                };
+              } catch (err) {
+                return;
+              }
+            }
+          }
+          // 统计chunkResult里老师/学生数量
+          const teacherCount = chunkResult.filter(x => x === "老师").length;
+          const studentCount = chunkResult.filter(x => x === "学生").length;
+          const finalRole =
+            teacherCount > studentCount
+              ? "老师"
+              : studentCount > teacherCount
+              ? "学生"
+              : "学生"; // 平局默认学生
+          // 替换所有行的role为finalRole
+          for (const row of rowsBatch) {
+            row.cells[5].textContent = finalRole;
+          }
+          // 更新进度
+          completedCount += rowsBatch.length;
+          this.jobContext.progress.current = completedCount;
+          this.jobContext.progress.percentage = (completedCount / tableRows.length) * 100;
+        }
+
+        // 3. 处理空白行,不分批,全部一起处理
+        if (emptyRoleRows.length > 0) {
+          const rowsBatch = emptyRoleRows;
+          // 进度
+          this.jobContext.progress.currentSize = rowsBatch.length;
+          this.jobContext.progress.current = completedCount;
+          this.jobContext.progress.percentage = (completedCount / tableRows.length) * 100;
+          // 取rows的content
+          const reqRows = rowsBatch.map(r => ({
+            content: r.cells[3].textContent,
+            role: "" // 空
+          }));
+          let chunkResult = [];
+          let pauseCtrl = new AbortController();
+          this.jobContext.pause = () => {
+            const err = new Error();
+            err.name = "PauseError";
+            pauseCtrl.abort(err);
+          };
+          while (true) {
+            try {
+              const res = await fetch(
+                "https://dify.cocorobo.cn/v1/workflows/run",
+                {
+                  signal: AbortSignal.any([
+                    this.jobContext.ctrl.signal,
+                    pauseCtrl.signal
+                  ]),
+                  method: "POST",
+                  body: JSON.stringify({
+                    inputs: {
+                      rows: JSON.stringify(reqRows),
+                      options: options.join(","),
+                      attention
+                    },
+                    response_mode: "blocking",
+                    user: this.userId
+                  }),
+                  ...config
+                }
+              ).then(res => res.json());
+              const error = _.get(res, ["data", "error"], null);
+              if (error) {
+                const err = new Error(error);
+                err.name = "DifyError";
+                throw err;
+              }
+              chunkResult = _.get(res, ["data", "outputs", "result"], []);
+              break; // 正常
+            } catch (err) {
+              if (err.name === "StopError") return;
+              this.jobContext.status = "paused";
+              if (err.name === "PauseError") {
+                this.jobContext.error = `用户暂停`;
+              } else {
+                this.jobContext.error = `部分生成失败。点击按钮可继续尝试生成`;
+              }
+              try {
+                const restartPromise = new Promise((resolve, reject) => {
+                  this.jobContext.restart = resolve;
+                  this.jobContext.ctrl.signal.addEventListener("abort", reject);
+                });
+                await restartPromise;
+                this.jobContext.status = "running";
+                this.jobContext.error = null;
+                pauseCtrl = new AbortController();
+                this.jobContext.pause = () => {
+                  const err2 = new Error();
+                  err2.name = "PauseError";
+                  pauseCtrl.abort(err2);
+                };
+              } catch (err) {
+                return;
+              }
+            }
+          }
+          chunkResult.forEach((role, idx) => {
+            rowsBatch[idx].cells[5].textContent = role;
+          });
+          // 更新进度
+          completedCount += rowsBatch.length;
+          this.jobContext.progress.current = completedCount;
+          this.jobContext.progress.percentage = (completedCount / tableRows.length) * 100;
+        }
+
+        // 重新整理actionType (第7列) 留给后续流程处理
+        // 组装回去并保存
+        // 生成actionTypes数组
+        const _actionTypes = tableRows.map(row =>
+          _.get(row, ["cells", 6, "textContent"], "")
+        );
+        this.actionTypesMap.jsonData[key] = _actionTypes;
+        // 保存回HTML
+        const _table = div.querySelector("table");
+        this.editorBarData.content = _table.outerHTML;
+      } else if (type === "actionType") {
+        // 行为编码不进行分批,直接全部处理
+        this.actionTypesMap.jsonData[key] = Array.from({
+          length: tableRows.length
+        }).fill("");
+        let pauseCtrl = new AbortController();
+        this.jobContext.pause = () => {
+          const err = new Error();
+          err.name = "PauseError";
+          pauseCtrl.abort(err);
+        };
+        while (!this.jobContext.ctrl.signal.aborted) {
+          try {
+            this.jobContext.progress.current = 0;
+            this.jobContext.progress.currentSize = tableRows.length;
+            this.jobContext.progress.percentage = 0;
+
+            const res = await fetch("https://dify.cocorobo.cn/v1/workflows/run", {
+              signal: AbortSignal.any([this.jobContext.ctrl.signal, pauseCtrl.signal]),
+              method: "POST",
+              body: JSON.stringify({
+                inputs: {
+                  rows: JSON.stringify(
+                    tableRows.map(r => ({
+                      content: r.cells[3].textContent,
+                      role: r.cells[5].textContent
+                    }))
+                  ),
+                  options: options.join(","),
+                  attention
+                },
+                response_mode: "blocking",
+                user: this.userId
+              }),
+              ...config
+            }).then(res => res.json());
+            const error = _.get(res, ["data", "error"], null);
+            if (error) {
+              const err = new Error(error);
+              err.name = "DifyError";
+              throw err;
+            }
+            const result = _.get(res, ["data", "outputs", "result"], []);
+            this.actionTypesMap.jsonData[key] = Object.assign(
+              new Array(tableRows.length),
+              result.slice(0, tableRows.length)
+            );
+            break;
+          } catch (err) {
+            if (err.name === "StopError") {
+              break;
+            }
+            this.jobContext.status = "paused";
+            if (err.name === "PauseError") {
+              this.jobContext.error = `用户暂停`;
+            } else {
+              this.jobContext.error = `部分生成失败。点击按钮可继续尝试生成`;
+            }
+            try {
+              const restartPromise = new Promise((resolve, reject) => {
+                this.jobContext.restart = resolve;
+                this.jobContext.ctrl.signal.addEventListener("abort", reject);
+              });
+              await restartPromise;
+              this.jobContext.status = "running";
+              this.jobContext.error = null;
+              pauseCtrl = new AbortController();
+              this.jobContext.pause = () => {
+                const err2 = new Error();
+                err2.name = "PauseError";
+                pauseCtrl.abort(err2);
+              };
+            } catch (_err) {
+              // user aborted
+              break;
+            }
+          }
+        }
+      }
+      this.changeEditorBar({
+        transcriptionData: this.transcriptionData.content,
+        editorBarData: this.editorBarData
+      });
+      this.jobContext = null;
+    },
     onEdit(value) {
       const div = document.createElement("div");
       div.innerHTML = value;

+ 2 - 2
src/components/pages/classroomObservation/components/newAnalysisModule.vue

@@ -28,9 +28,9 @@
               type="textarea"
               v-model="form.detail"
               :rows="1"
-              :maxlength="30"
+              :maxlength="100"
               resize="none"
-              placeholder="请输入内容,不超过30字"
+              placeholder="请输入内容,不超过100字"
             ></el-input>
           </el-form-item>
 

Datei-Diff unterdrückt, da er zu groß ist
+ 1004 - 1
src/components/pages/classroomObservation/tools/mixin.js


Einige Dateien werden nicht angezeigt, da zu viele Dateien in diesem Diff geändert wurden.