Commit 9a0d119d authored by 赵灿灿's avatar 赵灿灿

新增融合页面

parent ab5f9df9
Pipeline #22119 failed with stages
in 3 minutes and 54 seconds
...@@ -877,4 +877,114 @@ public class LangChainController { ...@@ -877,4 +877,114 @@ public class LangChainController {
.body(emitter); .body(emitter);
} }
//内部专家外部专家融合智能体
@GetMapping("/sseFusionIntelligent")
public ResponseEntity<SseEmitter> sseFusionIntelligent(@RequestParam String chatMessage,@RequestParam String dialogId,
@RequestParam String selectedExpert ,@RequestParam String selectedOrg) {
String condition="";
String regionName= chatService.getAuthRegionName();
// if("内部专家".equals(selectedExpert)) {
// if ("组织内".equals(selectedOrg) && StringUtils.isNotEmpty(regionName)) {
// condition = ",只能查找" + regionName;
// } else if ("组织外".equals(selectedOrg) && StringUtils.isNotEmpty(regionName)) {
// condition = ",排除" + regionName;
// }
// }
String urlAddr = "http://10.32.41.228:44501/scene_gateway/agent/open/5386f9144ee042578d1c0f66c2598d06";
//String urlAddr = chatService.getUrl(selectedExpert);
//String urlAddr = "http://10.32.41.35:40517/scene_gateway/agent/83f77143b09c461993dd9a7db403eb94";
SseEmitter emitter = new SseEmitter(0L);
QuestionRequest questionRequest=new QuestionRequest();
questionRequest.setKeyword(chatMessage+condition);
questionRequest.setRequestId(getRequestId());
questionRequest.setDialogId(dialogId);
Conversations conversations=chatService.saveConversations(dialogId,chatMessage,selectedExpert);
Messages messagesQusetion=new Messages();
messagesQusetion.setRequestId(dialogId);
messagesQusetion.setContent(chatMessage);
messagesQusetion.setRole("user");
messagesQusetion.setModelName("qwen2.5-72b");
messagesQusetion=chatService.insertQuestionMessage(conversations,messagesQusetion);
Messages messagesContent=new Messages();
messagesContent.setRole("assistant");
messagesContent.setModelName("qwen2.5-72b");
messagesContent.setParentMsgId(messagesQusetion.getId());
messagesContent.setSort(messagesQusetion.getSort()+1);
chatService.insertMessage(conversations,messagesContent);
String params = JsonUtils.objectToJson(questionRequest);
StringBuffer content =new StringBuffer();
StringBuffer lineCotent =new StringBuffer();
new Thread(() -> {
HttpURLConnection connection = null;
try {
URL url = new URL(urlAddr);
// 建立链接
connection = (HttpURLConnection) url.openConnection();
connection.setRequestMethod("GET");
connection.setRequestProperty("AuthToken", "4009fe23e6b648539792330c14f5ed8e");
// connection.setRequestProperty("AuthToken", "fc40db5b7abe47dabfe1899e61fde2d7");
// 允许输入和输出
connection.setDoInput(true);
connection.setDoOutput(true);
// 设置超时为0,表示无限制
connection.setConnectTimeout(0);
connection.setReadTimeout(0);
try (OutputStream os = connection.getOutputStream()) {
os.write(params.getBytes(StandardCharsets.UTF_8));
os.flush();
}
// 检查响应码
int responseCode = connection.getResponseCode();
if (responseCode != HttpURLConnection.HTTP_OK) {
emitter.completeWithError(new RuntimeException("SSE 连接失败: " + responseCode));
return;
}
// 持续读取 SSE 数据流
try (BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
logger.info(line);
if (!line.startsWith("data:CALLBACK#")) {
if (line.startsWith("data:")) {
String data = line.substring(5).trim();
if("stop".equals(data))
{
emitter.send(SseEmitter.event().data("stop"),MediaType.parseMediaType("application/json; charset=UTF-8"));
}else
{
lineCotent.append(data);
String sendData=data.replace("attachment#[]#attachment","").replace("source#[]#source","")
.replace("#","").replace("*","");
emitter.send(SseEmitter.event().data(sendData), MediaType.parseMediaType("application/json; charset=UTF-8"));
if(!sendData.startsWith("SUGGEST["))
content.append(sendData);
}
}
}
}
}
emitter.complete(); // 流结束
} catch (Exception e) {
emitter.completeWithError(e);
logger.info(e.getMessage());
} finally {
messagesContent.setContent(content.toString());
chatService.updateMessage(messagesContent);
if (connection != null) {
connection.disconnect();
}
logger.info(lineCotent.toString());
}
}).start();
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_TYPE, MediaType.TEXT_EVENT_STREAM_VALUE)
.body(emitter);
}
} }
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>智能推荐专家</title>
<link rel="stylesheet" href="style/ai-chat.css">
<style>
[v-cloak] {
display: none;
}
</style>
</head>
<body>
<div id="app" class="main-container" :class="{'dark-theme': isDarkTheme}" v-cloak>
<!-- 左侧历史对话列表 -->
<div class="sidebar">
<div class="sidebar-header">
<h1>智能推荐专家</h1>
<!-- 添加主题切换按钮 -->
<div id="theme-toggle" class="theme-toggle" title="切换主题" @click="toggleTheme">
<svg id="light-icon" viewBox="0 0 24 24" :style="{display: isDarkTheme ? 'block' : 'none'}">
<path d="M12 7c-2.76 0-5 2.24-5 5s2.24 5 5 5 5-2.24 5-5-2.24-5-5-5zM2 13h2c.55 0 1-.45 1-1s-.45-1-1-1H2c-.55 0-1 .45-1 1s.45 1 1 1zm18 0h2c.55 0 1-.45 1-1s-.45-1-1-1h-2c-.55 0-1 .45-1 1s.45 1 1 1zM11 2v2c0 .55.45 1 1 1s1-.45 1-1V2c0-.55-.45-1-1-1s-1 .45-1 1zm0 18v2c0 .55.45 1 1 1s1-.45 1-1v-2c0-.55-.45-1-1-1s-1 .45-1 1zM5.99 4.58c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0s.39-1.03 0-1.41L5.99 4.58zm12.37 12.37c-.39-.39-1.03-.39-1.41 0-.39.39-.39 1.03 0 1.41l1.06 1.06c.39.39 1.03.39 1.41 0 .39-.39.39-1.03 0-1.41l-1.06-1.06zm1.06-10.96c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06zM7.05 18.36c.39-.39.39-1.03 0-1.41-.39-.39-1.03-.39-1.41 0l-1.06 1.06c-.39.39-.39 1.03 0 1.41s1.03.39 1.41 0l1.06-1.06z"></path>
</svg>
<svg id="dark-icon" viewBox="0 0 24 24" :style="{display: isDarkTheme ? 'none' : 'block'}">
<path d="M9.37,5.51C9.19,6.15,9.1,6.82,9.1,7.5c0,4.08,3.32,7.4,7.4,7.4c0.68,0,1.35-0.09,1.99-0.27C17.45,17.19,14.93,19,12,19 c-3.86,0-7-3.14-7-7C5,9.07,6.81,6.55,9.37,5.51z M12,3c-4.97,0-9,4.03-9,9s4.03,9,9,9s9-4.03,9-9c0-0.46-0.04-0.92-0.1-1.36 c-0.98,1.37-2.58,2.26-4.4,2.26c-2.98,0-5.4-2.42-5.4-5.4c0-1.81,0.89-3.42,2.26-4.4C12.92,3.04,12.46,3,12,3L12,3z"></path>
</svg>
</div>
<button id="new-chat-btn" class="new-chat-btn" @click="newChat">
<span class="plus-icon">+</span> 新对话
</button>
</div>
<div class="history-list">
<div v-for="(section, sectionIndex) in historySections" :key="sectionIndex" class="history-section">
<div class="time-label">{{ section.label }}</div>
<div v-for="(item, itemIndex) in section.items"
:key="item.id"
class="history-item"
:class="{ active: item.active }"
:data-id="item.id" @click="loadChatHistory(item)">
<div class="history-title" >{{ item.title }}</div>
<button class="delete-btn" @click.stop="deleteChat(item.id)">
<svg viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path d="M3 6h18"></path>
<path d="M19 6v14a2 2 0 0 1-2 2H7a2 2 0 0 1-2-2V6"></path>
<path d="M8 6V4a2 2 0 0 1 2-2h4a2 2 0 0 1 2 2v2"></path>
</svg>
</button>
</div>
</div>
</div>
</div>
<!-- 右侧聊天区域 -->
<div class="chat-area">
<!-- 消息历史区域 -->
<div id="scrollContainer" class="scrollContainer">
<div class="fist-loading" v-if="fistLoading">
<p>快速查找专家</p>
<div class="expert-guides-container">
<div class="expert-guides-scroll" :style="scrollStyle">
<div v-for="(item, index) in extendedGuides" :key="index" class="expert-guide-item" @click="questionClick(item)">
<p>{{ item }}</p>
</div>
</div>
</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 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>
<!-- 输入区域 -->
<div class="input-area">
<textarea id="user-input" v-model="userInput" placeholder="请输入您的问题 shift+enter换行" rows="3"></textarea>
<div class="input-area-content">
<div style="display: flex;align-items: center;justify-content: flex-start;">
<div class="custom-select" v-click-outside="closeExpertDropdown" style="display: none">
<div class="selected-option" @click.stop="selectExpert('内部专家')"
:class="{ 'active': selectedExpert === '内部专家' }">
<span>内部专家</span>
<!-- <svg class="dropdown-icon" :class="{ 'rotated': showExpertDropdown }" 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="showExpertDropdown">-->
<!-- <div class="dropdown-item" @click.stop="selectExpert('内部专家')" :class="{ 'active': selectedExpert === '内部专家' }">-->
<!-- <span>内部专家</span>-->
<!-- </div>-->
<!--&lt;!&ndash; <div class="dropdown-item" @click.stop="selectExpert('外部专家')" :class="{ 'active': selectedExpert === '外部专家' }">&ndash;&gt;-->
<!--&lt;!&ndash; <span>外部专家</span>&ndash;&gt;-->
<!--&lt;!&ndash; </div>&ndash;&gt;-->
<!-- </div>-->
</div>
<div class="custom-select" v-click-outside="closeExpertDropdown" style="display: none">
<div class="selected-option" @click.stop="selectExpert('外部专家')"
:class="{ 'active': selectedExpert === '外部专家' }">
<span> 外部专家 </span>
</div>
</div>
<div class="custom-select" v-click-outside="closeOrgDropdown" v-show="showOrgSelection" style="display: none">
<!-- <div class="selected-option" @click.stop="toggleOrgDropdown" style="background-color: rgba(230, 220, 250, 0.5);color: #6633ff;">-->
<!-- <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">
<svg class="send-icon" viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round">
<line x1="12" y1="19" x2="12" y2="5"></line>
<polyline points="5 12 12 5 19 12"></polyline>
</svg>
</button>
<button id="stop-btn" class="stop-btn" @click="stopResponse" v-show="isResponding">
<svg viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2" fill="none"
stroke-linecap="round" stroke-linejoin="round" class="stop-icon">
<rect x="6" y="6" width="12" height="12" rx="2" ry="2"></rect>
</svg>
</button>
</div>
</div>
</div>
</div>
</div>
<script type="text/javascript" src="../../libs/require.js/require.min.js"></script>
<script type="text/javascript" src="../../scripts/require-config.js"></script>
<!-- 引入Vue相关JS -->
<script type="text/javascript" src="js/ai-chat-vue.js"></script>
</body>
</html>
\ No newline at end of file
This diff is collapsed.
This diff is collapsed.
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