Commit 72bbd208 authored by liujiangnan's avatar liujiangnan

feat: 暂存流式接口

parent 45e477f7
...@@ -1165,4 +1165,111 @@ html, body { ...@@ -1165,4 +1165,111 @@ html, body {
.image-name { .image-name {
font-size: 12px; font-size: 12px;
} }
}
/* AI 分析遮罩层样式 */
.ai-analysis-dialog .el-dialog {
border-radius: 8px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.3);
}
.ai-analysis-dialog .el-dialog__header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
border-radius: 8px 8px 0 0;
padding: 15px 20px;
}
.ai-analysis-dialog .el-dialog__title {
color: white;
font-size: 18px;
font-weight: 600;
}
.ai-analysis-dialog .el-dialog__body {
padding: 30px 20px;
background: #f8f9fa;
}
.ai-analysis-content {
text-align: center;
}
.ai-status {
font-size: 14px;
font-weight: 500;
margin-bottom: 20px;
padding: 10px;
background: #e8f4fd;
border-radius: 4px;
border-left: 4px solid #409eff;
}
.ai-text-area {
margin-top: 20px;
}
.ai-text-area .el-textarea__inner {
font-family: 'Courier New', monospace;
font-size: 13px;
line-height: 1.6;
background: #fafafa;
border: 1px solid #e4e7ed;
border-radius: 4px;
resize: none;
}
.ai-text-area .el-textarea__inner:focus {
border-color: #409eff;
box-shadow: 0 0 0 2px rgba(64, 158, 255, 0.2);
}
.ai-analysis-dialog .el-dialog__footer {
background: #f8f9fa;
border-radius: 0 0 8px 8px;
padding: 15px 20px;
text-align: center;
}
.ai-analysis-dialog .dialog-footer .el-button {
min-width: 100px;
border-radius: 20px;
}
/* 进度条样式优化 */
.ai-analysis-content .el-progress {
margin-bottom: 25px;
}
.ai-analysis-content .el-progress__text {
font-size: 14px;
font-weight: 600;
color: #409eff;
}
.ai-analysis-content .el-progress-bar__outer {
background-color: #e4e7ed;
border-radius: 10px;
}
.ai-analysis-content .el-progress-bar__inner {
border-radius: 10px;
background: linear-gradient(90deg, #409eff 0%, #67c23a 100%);
transition: all 0.3s ease;
}
/* 响应式设计 */
@media (max-width: 768px) {
.ai-analysis-dialog .el-dialog {
width: 90% !important;
margin: 5vh auto;
}
.ai-analysis-dialog .el-dialog__body {
padding: 20px 15px;
}
.ai-text-area .el-textarea__inner {
font-size: 12px;
}
} }
\ No newline at end of file
...@@ -30,6 +30,55 @@ ...@@ -30,6 +30,55 @@
<body> <body>
<div id="app" v-loading.fullscreen.lock="loading"> <div id="app" v-loading.fullscreen.lock="loading">
<el-button style="position: absolute; top: 10px; right: 10px;" @click="tryAI">AI重新匹配</el-button> <el-button style="position: absolute; top: 10px; right: 10px;" @click="tryAI">AI重新匹配</el-button>
<!-- AI 分析遮罩层 -->
<el-dialog
title="AI 分析中..."
:visible.sync="showAIAnalysis"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
width="60%"
center
class="ai-analysis-dialog"
>
<div class="ai-analysis-content">
<!-- 进度条 -->
<el-progress
:percentage="aiAnalysisProgress"
:status="aiAnalysisProgress === 100 ? 'success' : ''"
:stroke-width="8"
style="margin-bottom: 20px;"
></el-progress>
<!-- 状态信息 -->
<div class="ai-status" style="margin-bottom: 15px; text-align: center; color: #666;">
{{ aiAnalysisStatus }}
</div>
<!-- 文本显示区域 -->
<div class="ai-text-area">
<el-input
type="textarea"
v-model="aiAnalysisText"
:rows="12"
readonly
placeholder="AI 分析结果将在这里显示..."
style="font-family: 'Courier New', monospace; font-size: 14px;"
></el-input>
</div>
</div>
<span slot="footer" class="dialog-footer">
<el-button
v-if="aiAnalysisProgress === 100 || aiAnalysisText.includes('失败')"
@click="showAIAnalysis = false"
type="primary"
>
关闭
</el-button>
</span>
</el-dialog>
<!-- 文件上传界面 --> <!-- 文件上传界面 -->
<div class="upload-container" v-if="!hasData"> <div class="upload-container" v-if="!hasData">
<!-- 处理进度界面 --> <!-- 处理进度界面 -->
......
...@@ -11,14 +11,14 @@ const callServerMethod = (method, data) => { ...@@ -11,14 +11,14 @@ const callServerMethod = (method, data) => {
}); });
} }
// window.courseware.getData = function (fun, key) { window.courseware.getData = function (fun, key) {
// $.getJSON('../play/defaultData_1.json', function(defaultData) { $.getJSON('../play/defaultData_1.json', function(defaultData) {
// console.log('加载默认数据:', defaultData); console.log('加载默认数据:', defaultData);
// fun(defaultData); fun(defaultData);
// }).fail(function() { }).fail(function() {
// console.error('默认数据加载失败!'); console.error('默认数据加载失败!');
// }); });
// } }
new Vue({ new Vue({
...@@ -107,14 +107,21 @@ new Vue({ ...@@ -107,14 +107,21 @@ new Vue({
// 新增:上传类型 // 新增:上传类型
uploadType: 'pdf', uploadType: 'pdf',
AI_SERVER_URL: 'https://stagingopensource.iteachabc.com', AI_SERVER_URL: 'http://localhost:1339',
// AI_SERVER_URL: 'https://stagingopensource.iteachabc.com',
// AI 分析遮罩层相关
showAIAnalysis: false,
aiAnalysisText: '',
aiAnalysisStatus: '',
aiAnalysisProgress: 0,
}, },
mounted() { mounted() {
this.$nextTick(() => { this.$nextTick(() => {
// 先加载数据 // 先加载数据
this.loadDefaultData().then(() => { this.loadDefaultData().then(() => {
}); });
// 添加全局事件监听 // 添加全局事件监听
...@@ -127,7 +134,7 @@ new Vue({ ...@@ -127,7 +134,7 @@ new Vue({
methods: { methods: {
// 公共的 POST 请求方法 // 公共的 POST 请求方法
async postRequest(endpoint, data, options = {}) { async postRequest(endpoint, data, options = {}) {
...@@ -157,10 +164,10 @@ new Vue({ ...@@ -157,10 +164,10 @@ new Vue({
type: 'POST', type: 'POST',
contentType: 'application/json', contentType: 'application/json',
data: JSON.stringify(data), data: JSON.stringify(data),
success: function(response) { success: function (response) {
resolve(response); resolve(response);
}, },
error: function(xhr, status, error) { error: function (xhr, status, error) {
reject(new Error(error || '请求失败')); reject(new Error(error || '请求失败'));
} }
}); });
...@@ -194,22 +201,108 @@ new Vue({ ...@@ -194,22 +201,108 @@ new Vue({
async tryAI() { async tryAI() {
const urlParams = new URLSearchParams(window.location.search); const urlParams = new URLSearchParams(window.location.search);
const coursewareId = urlParams.get('course_id'); const coursewareId = urlParams.get('course_id');
// 显示遮罩层
this.showAIAnalysis = true;
this.aiAnalysisText = '';
this.aiAnalysisStatus = '正在连接服务器...';
this.aiAnalysisProgress = 0;
try { try {
await this.postRequest('/open/api/ai/dpe/v1/pagerepair', { this.aiAnalysisStatus = '正在发送请求...';
courseware_id: coursewareId || 1, this.aiAnalysisProgress = 20;
page_number: this.currentPageIndex
}, { const requestUrl = this.AI_SERVER_URL + '/open/api/ai/dpe/v1/pagerepair';
loadingText: 'AI 分析中,请稍候...', const requestData = { courseware_id: coursewareId || 1783332, page_number: this.currentPageIndex };
successMessage: 'AI 分析完成',
errorMessage: 'AI 分析失败,请稍后重试' console.log('请求URL:', requestUrl);
console.log('请求数据:', requestData);
// 使用 XMLHttpRequest 来处理流式响应
const xhr = new XMLHttpRequest();
xhr.open('POST', requestUrl, true);
xhr.setRequestHeader('Content-Type', 'application/json');
let lastResponseLength = 0;
xhr.onprogress = (event) => {
if (xhr.readyState === 3 || xhr.readyState === 4) {
const newData = xhr.responseText.substring(lastResponseLength);
lastResponseLength = xhr.responseText.length;
if (newData) {
this.aiAnalysisText += newData;
this.aiAnalysisProgress = Math.min(90, 40 + (this.aiAnalysisText.length / 100));
this.aiAnalysisStatus = `正在接收数据... (${this.aiAnalysisText.length} 字符)`;
}
}
};
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
this.aiAnalysisStatus = 'AI 分析完成,正在重新加载数据...';
this.aiAnalysisProgress = 95;
// 重新加载数据
this.loadDefaultData().then(() => {
this.aiAnalysisStatus = 'AI 分析完成!';
this.aiAnalysisProgress = 100;
// 2秒后自动关闭遮罩层
setTimeout(() => {
this.showAIAnalysis = false;
}, 2000);
});
} else {
throw new Error(`HTTP error! status: ${xhr.status}`);
}
};
xhr.onerror = () => {
throw new Error('Network error');
};
xhr.send(JSON.stringify(requestData));
// 等待 XHR 完成
await new Promise((resolve, reject) => {
xhr.onload = () => {
if (xhr.status >= 200 && xhr.status < 300) {
resolve();
} else {
reject(new Error(`HTTP error! status: ${xhr.status}`));
}
};
xhr.onerror = () => reject(new Error('Network error'));
}); });
this.aiAnalysisStatus = 'AI 分析完成,正在重新加载数据...';
this.aiAnalysisProgress = 95;
// 重新加载数据 // 重新加载数据
await this.loadDefaultData(); await this.loadDefaultData();
// 显示完成消息
this.aiAnalysisStatus = 'AI 分析完成!';
this.aiAnalysisProgress = 100;
// 2秒后自动关闭遮罩层
setTimeout(() => {
this.showAIAnalysis = false;
}, 2000);
} catch (error) { } catch (error) {
// 错误已在 postRequest 中处理
console.error('AI 分析失败:', error); console.error('AI 分析失败:', error);
// 显示错误信息
this.aiAnalysisText = `AI 分析失败: ${error.message}`;
this.aiAnalysisStatus = '分析失败,请重试';
this.aiAnalysisProgress = 0;
// 5秒后自动关闭遮罩层
setTimeout(() => {
this.showAIAnalysis = false;
}, 5000);
} }
}, },
// 心跳实时查询异步处理进度及信息 // 心跳实时查询异步处理进度及信息
...@@ -2251,8 +2344,8 @@ new Vue({ ...@@ -2251,8 +2344,8 @@ new Vue({
}, },
beforeImageUpload(file) { beforeImageUpload(file) {
const isImage = file.type.startsWith('image/') || const isImage = file.type.startsWith('image/') ||
file.name.toLowerCase().match(/\.(jpg|jpeg|png|gif|bmp|webp)$/); file.name.toLowerCase().match(/\.(jpg|jpeg|png|gif|bmp|webp)$/);
if (!isImage) { if (!isImage) {
this.$message.error('请上传图片格式的文件'); this.$message.error('请上传图片格式的文件');
} }
...@@ -2322,7 +2415,7 @@ new Vue({ ...@@ -2322,7 +2415,7 @@ new Vue({
async handleImageUpload(options) { async handleImageUpload(options) {
const { file } = options; const { file } = options;
const fileId = Date.now() + Math.random().toString(36).substr(2, 9); const fileId = Date.now() + Math.random().toString(36).substr(2, 9);
this.$set(this.imageUploadProgress, fileId, 0); this.$set(this.imageUploadProgress, fileId, 0);
const uploadUrl = window.courseware.uploadUrl(); const uploadUrl = window.courseware.uploadUrl();
...@@ -2426,12 +2519,12 @@ new Vue({ ...@@ -2426,12 +2519,12 @@ new Vue({
handleImageDrop(e, targetIndex) { handleImageDrop(e, targetIndex) {
e.preventDefault(); e.preventDefault();
const sourceIndex = parseInt(e.dataTransfer.getData('text/html').match(/data-index="(\d+)"/)?.[1] || 0); const sourceIndex = parseInt(e.dataTransfer.getData('text/html').match(/data-index="(\d+)"/)?.[1] || 0);
if (sourceIndex !== targetIndex) { if (sourceIndex !== targetIndex) {
const item = this.imageFiles.splice(sourceIndex, 1)[0]; const item = this.imageFiles.splice(sourceIndex, 1)[0];
this.imageFiles.splice(targetIndex, 0, item); this.imageFiles.splice(targetIndex, 0, item);
} }
// 重置所有拖拽样式 // 重置所有拖拽样式
document.querySelectorAll('.image-item').forEach(el => { document.querySelectorAll('.image-item').forEach(el => {
el.style.opacity = '1'; el.style.opacity = '1';
...@@ -2449,10 +2542,10 @@ new Vue({ ...@@ -2449,10 +2542,10 @@ new Vue({
} }
this.loading = true; this.loading = true;
// 根据上传的文件类型决定传递的数据 // 根据上传的文件类型决定传递的数据
let uploadData = { audioUrl: this.audioFile }; let uploadData = { audioUrl: this.audioFile };
if (this.pdfFile) { if (this.pdfFile) {
uploadData.pdfUrl = this.pdfFile; uploadData.pdfUrl = this.pdfFile;
} else if (this.imageFiles.length > 0) { } else if (this.imageFiles.length > 0) {
...@@ -2559,17 +2652,17 @@ new Vue({ ...@@ -2559,17 +2652,17 @@ new Vue({
if (!this.audioUploadComplete || !this.audioFile) { if (!this.audioUploadComplete || !this.audioFile) {
return false; return false;
} }
// PDF或图片必须上传完成(二选一) // PDF或图片必须上传完成(二选一)
if (this.pdfFile && this.pdfUploadComplete) { if (this.pdfFile && this.pdfUploadComplete) {
return true; return true;
} }
if (this.imageFiles.length > 0) { if (this.imageFiles.length > 0) {
// 检查所有图片是否都上传完成 // 检查所有图片是否都上传完成
return this.imageFiles.every(file => !this.imageUploadProgress[file.id] || this.imageUploadProgress[file.id] === 100); return this.imageFiles.every(file => !this.imageUploadProgress[file.id] || this.imageUploadProgress[file.id] === 100);
} }
return false; return false;
} }
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment