Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Contribute to GitLab
Sign in
Toggle navigation
P
pms-dispatch-assistant
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
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
姜耀祖
pms-dispatch-assistant
Commits
6bf7630a
Commit
6bf7630a
authored
Apr 22, 2025
by
姜耀祖
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
前端修改
parent
72adaf97
Pipeline
#21063
passed with stages
in 3 minutes and 18 seconds
Changes
7
Pipelines
1
Expand all
Show whitespace changes
Inline
Side-by-side
Showing
7 changed files
with
207 additions
and
214 deletions
+207
-214
LangChainController.java
...chassistant/controller/langchain/LangChainController.java
+13
-165
markdown-it.min.js
src/main/resources/static/libs/marked/markdown-it.min.js
+2
-0
marked.umd.min.js
src/main/resources/static/libs/marked/marked.umd.min.js
+11
-0
ai-chat.html
src/main/resources/static/pages/langchain/ai-chat.html
+37
-3
ai-chat-vue.js
src/main/resources/static/pages/langchain/js/ai-chat-vue.js
+46
-41
ai-chat.css
src/main/resources/static/pages/langchain/style/ai-chat.css
+94
-5
require-config.js
src/main/resources/static/scripts/require-config.js
+4
-0
No files found.
src/main/java/com/infoepoch/pms/dispatchassistant/controller/langchain/LangChainController.java
View file @
6bf7630a
This diff is collapsed.
Click to expand it.
src/main/resources/static/libs/marked/markdown-it.min.js
0 → 100644
View file @
6bf7630a
This source diff could not be displayed because it is too large. You can
view the blob
instead.
src/main/resources/static/libs/marked/marked.umd.min.js
0 → 100644
View file @
6bf7630a
This diff is collapsed.
Click to expand it.
src/main/resources/static/pages/langchain/ai-chat.html
View file @
6bf7630a
...
...
@@ -55,17 +55,32 @@
<div
class=
"chat-area"
>
<!-- 消息历史区域 -->
<div
id=
"scrollContainer"
class=
"scrollContainer"
>
<div
id=
"chat-messages"
class=
"chat-messages"
>
<div
class=
"fist-loading"
v-if=
"fistLoading"
>
<p>
快速查找专家
</p>
<div
v-for=
"(item, index) in guides"
:key=
"index"
@
click=
"questionClick(item)"
>
<p>
{{ item }}
</p>
</div>
</div>
<div
id=
"chat-messages"
class=
"chat-messages"
v-if=
"!fistLoading"
>
<div
v-for=
"(message, index) in messages"
:key=
"index"
class=
"message"
:class=
"message.role + '-message'"
>
<div
class=
"avatar"
>
{{ message.role === 'user' ? '我' : 'AI' }}
</div>
<div
class=
"content"
>
<p
>
{{ message.content }}
</p
>
<p
v-html=
"message.content"
></p>
<!--v-html可能会有xss攻击,但是数据来源于大模型,是否需要清洗数据然后再显示?--
>
<div
v-if=
"message.typing"
class=
"typing-indicator"
></div>
</div>
</div>
<div
class=
"question"
>
<div
class=
"question_left"
></div>
<div
v-if=
"questions.length"
class=
"question_right"
>
<p>
您可以继续问我:
</p>
<div
class=
"questionContent"
v-for=
"(q, i) in questions"
:key=
"i"
@
click=
"questionClick(q)"
>
<p>
{{ q }}
</p>
</div>
</div>
</div>
</div>
</div>
...
...
@@ -73,7 +88,7 @@
<div
class=
"input-area"
>
<textarea
id=
"user-input"
v-model=
"userInput"
placeholder=
"请输入您的问题 shift+enter换行"
rows=
"3"
></textarea>
<div
class=
"input-area-content"
>
<div>
<div
style=
"display: flex;align-items: center;justify-content: flex-start;"
>
<div
class=
"custom-select"
v-click-outside=
"closeExpertDropdown"
>
<div
class=
"selected-option"
@
click
.
stop=
"toggleExpertDropdown"
>
<span>
{{ selectedExpert }}
</span>
...
...
@@ -90,6 +105,25 @@
</div>
</div>
</div>
<div
class=
"custom-select"
v-click-outside=
"closeOrgDropdown"
>
<div
class=
"selected-option"
@
click
.
stop=
"toggleOrgDropdown"
>
<span>
{{ selectedOrg }}
</span>
<svg
class=
"dropdown-icon"
:class=
"{ 'rotated': showOrgDropdown }"
viewBox=
"0 0 24 24"
width=
"16"
height=
"16"
stroke=
"currentColor"
stroke-width=
"2"
fill=
"none"
>
<polyline
points=
"6 9 12 15 18 9"
></polyline>
</svg>
</div>
<div
class=
"dropdown-menu"
v-show=
"showOrgDropdown"
>
<div
class=
"dropdown-item"
@
click
.
stop=
"selectOrg('全部组织')"
:class=
"{ 'active': selectedOrg === '全部组织' }"
>
<span>
全部
</span>
</div>
<div
class=
"dropdown-item"
@
click
.
stop=
"selectOrg('组织内')"
:class=
"{ 'active': selectedOrg === '组织内' }"
>
<span>
组织内
</span>
</div>
<div
class=
"dropdown-item"
@
click
.
stop=
"selectOrg('组织外')"
:class=
"{ 'active': selectedOrg === '组织外' }"
>
<span>
组织外
</span>
</div>
</div>
</div>
</div>
<div>
<button
id=
"send-btn"
class=
"send-btn"
@
click=
"sendMessage"
v-show=
"!isResponding"
>
...
...
src/main/resources/static/pages/langchain/js/ai-chat-vue.js
View file @
6bf7630a
/**
* AI聊天页面Vue应用
*/
require
([
'jquery'
,
'vue'
,
'utils'
,
'echarts'
,
'global'
],
function
(
$
,
Vue
,
utils
,
echarts
)
{
require
([
'jquery'
,
'vue'
,
'utils'
,
'marked'
,
'markdown'
,
'global'
],
function
(
$
,
Vue
,
utils
,
marked
,
markdown
)
{
// 添加点击外部关闭指令
Vue
.
directive
(
'click-outside'
,
{
bind
:
function
(
el
,
binding
,
vnode
)
{
...
...
@@ -22,7 +22,7 @@ require(['jquery', 'vue', 'utils', 'echarts', 'global'], function ($, Vue, utils
data
:
{
// 用户相关数据
currentLoginUser
:
{},
fistLoading
:
true
,
// 聊天相关数据
chatHistory
:
[],
sessionId
:
""
,
...
...
@@ -36,7 +36,11 @@ require(['jquery', 'vue', 'utils', 'echarts', 'global'], function ($, Vue, utils
// 专家选择相关数据
selectedExpert
:
'内部专家'
,
showExpertDropdown
:
false
,
// 组织内网选择相关数据
selectedOrg
:
'全部组织'
,
showOrgDropdown
:
false
,
questions
:[],
guides
:[
'请帮我推荐5位南京地区的区块链外部专家'
,
'我需要找既懂通信又擅长软件开发的内部专家'
,
'我需要网络安全领域的专家,不确定选内部还是外部'
],
// 历史对话分类
historySections
:
[
{
...
...
@@ -74,7 +78,7 @@ require(['jquery', 'vue', 'utils', 'echarts', 'global'], function ($, Vue, utils
// 消息列表
messages
:
[
{
role
:
'ai'
,
content
:
'您好!我是您的专家推荐助手,有什么可以帮助您的吗?'
}
//
{ role: 'ai', content: '您好!我是您的专家推荐助手,有什么可以帮助您的吗?' }
]
},
computed
:
{
...
...
@@ -87,7 +91,6 @@ require(['jquery', 'vue', 'utils', 'echarts', 'global'], function ($, Vue, utils
this
.
getSessionId
();
},
mounted
:
function
()
{
this
.
pageEvent
();
// 监听输入框的键盘事件
this
.
$nextTick
(()
=>
{
const
textarea
=
document
.
getElementById
(
'user-input'
);
...
...
@@ -102,37 +105,9 @@ require(['jquery', 'vue', 'utils', 'echarts', 'global'], function ($, Vue, utils
this
.
closeAllMenus
();
}
});
// // 获取滚动容器和消息容器
// const scrollContainer = document.getElementById('chat-messages').parentElement;
// const chatMessages = document.getElementById('chat-messages');
// // 创建MutationObserver监听消息变化
// const observer = new MutationObserver(function(mutations) {
// mutations.forEach(mutation => {
// if (mutation.addedNodes.length) {
// // 使用延时确保内容已渲染
// setTimeout(() => {
// scrollContainer.scrollTo({
// top: scrollContainer.scrollHeight,
// behavior: 'smooth' // 可选平滑滚动
// });
// }, 50);
// }
// });
// });
//
// // 开始观察子元素变化
// observer.observe(chatMessages, {
// childList: true
// });
});
},
methods
:
{
pageEvent
:
function
(){
// 确保在DOM加载完成后执行
document
.
addEventListener
(
'DOMContentLoaded'
,
function
()
{
});
},
// 用户相关方法
currentUser
:
function
()
{
const
that
=
this
;
...
...
@@ -147,7 +122,6 @@ require(['jquery', 'vue', 'utils', 'echarts', 'global'], function ($, Vue, utils
}
});
},
// 主题相关方法
toggleTheme
()
{
this
.
theme
=
this
.
theme
===
'light'
?
'dark'
:
'light'
;
...
...
@@ -174,11 +148,16 @@ require(['jquery', 'vue', 'utils', 'echarts', 'global'], function ($, Vue, utils
this
.
sendMessage
();
}
},
questionClick
:
function
(
message
){
this
.
userInput
=
message
;
this
.
sendMessage
();
},
sendMessage
()
{
const
message
=
this
.
userInput
.
trim
();
if
(
!
message
)
return
;
if
(
this
.
fistLoading
)
this
.
fistLoading
=
false
;
this
.
questions
=
[];
this
.
stopResponse
();
// 添加用户消息
this
.
addMessage
(
'user'
,
message
);
...
...
@@ -237,16 +216,30 @@ require(['jquery', 'vue', 'utils', 'echarts', 'global'], function ($, Vue, utils
}
});
// 连接SSE
this
.
currentEventSource
=
new
EventSource
(
'../../api/langchain/sse
Intelligent?chatMessage='
+
encodeURIComponent
(
chatMessage
)
+
"&dialogId="
+
this
.
sessionId
);
this
.
currentEventSource
=
new
EventSource
(
'../../api/langchain/sse
BigTwo'
);
let
responseText
=
''
;
var
md
=
new
markdown
({
html
:
true
,
// 允许解析 HTML 标签
linkify
:
true
,
// 自动识别链接
typographer
:
true
,
// 启用排版优化
breaks
:
true
,
// 将单个换行符视为换行
});
this
.
currentEventSource
.
onmessage
=
(
event
)
=>
{
const
data
=
JSON
.
parse
(
event
.
data
);
const
content
=
data
.
dataToSend
[
1
].
data
;
if
(
content
!==
"stop"
)
{
const
match
=
content
.
match
(
/SUGGEST#
\[(
.*
?)\]
#SUGGEST/
);
if
(
match
)
{
const
suggestionsJson
=
`[
${
match
[
1
]}
]`
;
const
suggestions
=
JSON
.
parse
(
suggestionsJson
);
if
(
Array
.
isArray
(
suggestions
))
{
this
.
questions
=
suggestions
;
}
}
else
{
//提示词不展示
responseText
+=
content
;
this
.
messages
[
aiMessageIndex
].
content
=
responseText
;
}
this
.
messages
[
aiMessageIndex
].
content
=
md
.
render
(
responseText
);
}
else
{
// 移除输入指示器
this
.
messages
[
aiMessageIndex
].
typing
=
false
;
...
...
@@ -302,7 +295,7 @@ require(['jquery', 'vue', 'utils', 'echarts', 'global'], function ($, Vue, utils
this
.
currentEventSource
=
null
;
this
.
isResponding
=
false
;
}
this
.
questions
=
[];
// 获取新的会话ID并清空对话
this
.
getSessionId
().
then
(()
=>
{
this
.
clearChat
();
...
...
@@ -410,6 +403,18 @@ require(['jquery', 'vue', 'utils', 'echarts', 'global'], function ($, Vue, utils
this
.
selectedExpert
=
expert
;
// 立即关闭下拉框
this
.
showExpertDropdown
=
false
;
},
//组织内外选择相关方法
toggleOrgDropdown
()
{
this
.
showOrgDropdown
=
!
this
.
showOrgDropdown
;
},
closeOrgDropdown
()
{
this
.
showOrgDropdown
=
false
;
},
selectOrg
(
org
)
{
this
.
selectedOrg
=
org
;
// 立即关闭下拉框
this
.
showOrgDropdown
=
false
;
}
}
});
...
...
src/main/resources/static/pages/langchain/style/ai-chat.css
View file @
6bf7630a
...
...
@@ -27,6 +27,7 @@
--scrollbar-thumb
:
#c1c1c1
;
--scrollbar-hover
:
#a8a8a8
;
--box-shadow
:
rgba
(
0
,
0
,
0
,
0.08
);
--question-text
:
#676c90
;
}
.dark-theme
{
...
...
@@ -50,6 +51,7 @@
--scrollbar-thumb
:
#555555
;
--scrollbar-hover
:
#777777
;
--box-shadow
:
rgba
(
0
,
0
,
0
,
0.2
);
--question-text
:
#dbdef8
;
}
body
{
...
...
@@ -214,6 +216,39 @@ body {
}
/* 右侧聊天区域 */
.fist-loading
{
width
:
900px
;
margin
:
8%
auto
10px
;
}
.fist-loading
>
p
{
color
:
var
(
--primary-color
);
font-weight
:
bold
;
font-size
:
20px
;
width
:
500px
;
margin
:
0
auto
;
}
.fist-loading
div
{
width
:
500px
;
max-height
:
72px
;
line-height
:
72px
;
border-radius
:
16px
;
padding
:
0
16px
;
background-color
:
var
(
--sidebar-bg
);
border
:
1px
solid
var
(
--box-shadow
);
box-shadow
:
0
1px
8px
var
(
--box-shadow
);
box-sizing
:
border-box
;
align-items
:
center
;
margin
:
10px
auto
;
font-size
:
16px
;
cursor
:
pointer
;
color
:
var
(
--question-text
);
}
.fist-loading
div
:hover
{
background-color
:
#e5eaf6
;
color
:
#475ada
;
}
.chat-area
{
flex
:
1
;
display
:
flex
;
...
...
@@ -229,15 +264,14 @@ body {
width
:
100%
;
overflow-y
:
auto
;
/* 让内容继续流动 */
height
:
calc
(
100vh
-
150px
);
/* 确保占据足够高度 */
overflow-x
:
hidden
;
padding-top
:
20px
;
}
/* 聊天消息区域 */
.chat-messages
{
width
:
900px
;
max-width
:
95%
;
margin
:
0
auto
;
padding
:
20px
;
margin-bottom
:
10px
;
margin
:
0
auto
10px
;
}
/* 输入区域 */
...
...
@@ -271,6 +305,44 @@ body {
}
/* 用户消息 */
.question
{
display
:
flex
;
justify-content
:
flex-start
;
}
.question_left
{
width
:
40px
;
margin
:
0
10px
;
}
.question_right
{
padding
:
0
6px
;
}
.question_right
>
p
{
font-size
:
14px
;
color
:
var
(
--question-text
);
}
.questionContent
{
font-size
:
13px
;
color
:
var
(
--question-text
);
background-color
:
var
(
--input-area-bg
);
border-radius
:
6px
;
box-shadow
:
0
16px
20px
0
rgba
(
174
,
167
,
223
,
.06
);
line-height
:
20px
;
max-width
:
800px
;
overflow
:
hidden
;
padding
:
8px
16px
;
text-overflow
:
ellipsis
;
white-space
:
nowrap
;
width
:
-moz-fit-content
;
width
:
fit-content
;
margin
:
10px
0
;
cursor
:
pointer
;
}
.questionContent
:hover
{
background-color
:
#e5eaf6
;
color
:
#475ada
;
}
.user-message
{
flex-direction
:
row-reverse
;
}
...
...
@@ -294,7 +366,7 @@ body {
}
.message
.content
{
max-width
:
70
%
;
max-width
:
87
%
;
padding
:
12px
16px
;
border-radius
:
12px
;
box-shadow
:
0
1px
2px
var
(
--box-shadow
);
...
...
@@ -315,6 +387,7 @@ body {
.content
p
{
white-space
:
pre-wrap
;
word-break
:
break-word
;
color
:
var
(
--text-color
);
}
#user-input
{
...
...
@@ -396,6 +469,22 @@ body {
66
%
{
content
:
"..."
;
}
}
@media
(
max-width
:
1190px
){
.chat-messages
{
width
:
800px
;
}
}
@media
(
max-width
:
1090px
){
.chat-messages
{
width
:
700px
;
}
}
@media
(
max-width
:
990px
){
.chat-messages
{
width
:
600px
;
}
}
/* 响应式设计 */
@media
(
max-width
:
768px
)
{
.sidebar
{
...
...
src/main/resources/static/scripts/require-config.js
View file @
6bf7630a
...
...
@@ -54,6 +54,8 @@ require.config({
'Popper'
:
[
getContextPath
()
+
'/libs/popper/popper-x'
],
'component-demo-image'
:
[
'./component-demo-image'
],
'component-demo-image-new'
:
[
'./component-demo-image-new'
],
'marked'
:[
getContextPath
()
+
'/libs/marked/marked.umd.min'
],
'markdown'
:[
getContextPath
()
+
'/libs/marked/markdown-it.min'
],
//流程引擎组件
'process-engine-toolbar-v2'
:
[
getContextPath
()
+
'/libs/process-engine/component-process-engine-toolbar-v2'
],
'process-engine-toolbar-v3'
:
[
getContextPath
()
+
'/libs/process-engine/component-process-engine-toolbar-v3'
],
...
...
@@ -188,6 +190,8 @@ define("global",
'XMLHttp-download-file'
,
//下载文件前校验
'component-demo-image'
,
//显示图片
'component-checkbox-list'
,
//复选框
'marked'
,
'markdown'
,
//'component-demo-image-new',
// 'directive-ellipsis', //显示更多文字
...
...
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