Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
A
AI_BOOK_TOOL
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
template admin
AI_BOOK_TOOL
Commits
72bbd208
Commit
72bbd208
authored
Jul 30, 2025
by
liujiangnan
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
feat: 暂存流式接口
parent
45e477f7
Changes
3
Hide whitespace changes
Inline
Side-by-side
Showing
3 changed files
with
281 additions
and
32 deletions
+281
-32
index.css
form/index.css
+107
-0
index.html
form/index.html
+49
-0
index.js
form/index.js
+125
-32
No files found.
form/index.css
View file @
72bbd208
...
@@ -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
form/index.html
View file @
72bbd208
...
@@ -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"
>
<!-- 处理进度界面 -->
<!-- 处理进度界面 -->
...
...
form/index.js
View file @
72bbd208
...
@@ -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
;
}
}
}
}
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment