Files
AI/index.html

1662 lines
52 KiB
HTML
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<meta http-equiv="Cache-Control" content="no-store, no-cache, must-revalidate">
<meta http-equiv="Pragma" content="no-cache">
<meta http-equiv="Expires" content="Thu, 01 Jan 1970 00:00:00 GMT">
<title>新能源智能体</title>
<link rel="icon" href="./img/logo.png" type="image/x-icon">
<!-- <script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script> -->
<style>
body {
margin: 0;
font-family: "Segoe UI", "PingFang SC", Arial, sans-serif;
height: 100vh;
display: flex;
background: #f0f6fb;
color: #333;
width: 100%;
}
.toast {
position: fixed;
top: 20%;
left: 50%;
transform: translateX(-50%);
background: rgba(0, 0, 0, 0.8);
color: #fff;
padding: 10px 18px;
border-radius: 8px;
font-size: 15px;
opacity: 0;
transition: opacity 0.3s ease;
z-index: 9999;
}
.toast.show {
opacity: 1;
}
/* 侧边栏 */
.sidebar {
width: 240px;
background: #ffffff;
border-right: 1px solid #e3eaf2;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
box-shadow: 2px 0 5px rgba(0, 0, 0, 0.04);
z-index: 10;
}
.sidebar.collapsed {
width: 0;
overflow: hidden;
}
.sidebar-header {
padding: 16px;
padding-left: 20px;
font-size: 16px;
font-weight: bold;
color: #0057ff;
border-bottom: 1px solid #f0f4f8;
display: flex;
justify-content: flex-start;
}
.sidebar-header img {
width: 24px;
margin-right: 10px;
}
.sidebar-actions {
padding: 12px;
border-bottom: 1px solid #f0f4f8;
}
.sidebar-actions button {
width: 100%;
padding: 10px;
border: none;
border-radius: 8px;
background: #0057ff;
color: #fff;
font-size: 14px;
cursor: pointer;
transition: background 0.2s;
}
.sidebar-actions button:hover {
background: #0046cc;
}
.chat-list {
flex: 1;
overflow-y: auto;
}
.chat-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 12px 16px;
border-bottom: 1px solid #f7f9fb;
cursor: pointer;
transition: background 0.2s;
text-align: center;
}
.chat-item:hover {
background: #f2f8fd;
}
.chat-item.active {
background: #e6f0ff;
font-weight: bold;
color: #0057ff;
}
.chat-title {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.delete-btn {
margin-left: 8px;
color: #aaa;
cursor: pointer;
font-size: 14px;
}
.delete-btn:hover {
color: #e74c3c;
}
/* 收起按钮(独立漂浮在左边) */
.toggle-btn {
position: absolute;
top: 10px;
left: 190px;
background: #0057ff;
color: #fff;
border: none;
border-radius: 50%;
width: 40px;
height: 40px;
font-size: 18px;
cursor: pointer;
box-shadow: 0 2px 6px rgba(0, 0, 0, 0.1);
z-index: 20;
transition: background 0.2s;
}
.toggle-btn:hover {
background: #0046cc;
}
/* 右侧聊天区域 */
.chat-container {
flex: 1;
display: flex;
flex-direction: column;
background: #f9fcff;
justify-content: space-between;
width: calc(100% - 240px)
}
.chat-container .content {
height: 70%;
overflow-y: auto;
scrollbar-width: none;
/* overflow-y: auto; */
}
.chat-header {
height: 50px;
padding: 1px 20px;
padding-left: 56px;
background: #fff;
border-bottom: 1px solid #e3eaf2;
font-size: 16px;
font-weight: bold;
color: #0057ff;
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.04);
display: flex;
justify-content: space-between;
}
.chat-header .userinfo {
display: flex;
justify-content: center;
align-items: center;
gap: 5px;
color: #666;
font-weight: 400;
font-family: fangsong;
}
.chat-header .userinfo .logout {
color: #666;
font-weight: 400;
font-family: fangsong;
/* text-decoration: underline; */
cursor: pointer
}
.price {
text-decoration: none;
}
.price:hover,
.logout:hover {
color: #999 !important;
}
.chat-header .back {
display: none;
}
.chat-messages {
flex: 1;
padding: 20px;
overflow-y: auto;
position: relative;
height: 400px
}
.message {
max-width: 70%;
margin-bottom: 16px;
padding: 12px 16px;
border-radius: 14px;
font-size: 16px;
line-height: 1.5;
/* white-space: pre-wrap;*/
white-space: normal;
word-wrap: break-word;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
animation: fadeIn 0.3s ease;
}
.message.user {
display: block;
background: #d4e5ff;
color: #000;
align-self: flex-end;
border-bottom-right-radius: 4px;
display: block;
width: fit-content;
}
.message.assistant {
max-width: 100%;
background: #fff;
color: #333;
align-self: flex-start;
border: 1px solid #e6eaf0;
border-bottom-left-radius: 4px;
}
.message img {
max-width: 200px;
border-radius: 8px;
margin-top: 8px;
}
.chat-input {
padding: 12px;
background: #fff;
border-top: 1px solid #e3eaf2;
display: flex;
flex-direction: column;
gap: 8px;
box-shadow: 0 -1px 3px rgba(0, 0, 0, 0.04);
}
.message.thinking {
width: auto;
/* 宽度自适应内容 */
max-width: 40%;
/* 限制最大宽度 */
}
.input-row {
display: flex;
align-items: center;
flex-direction: column;
justify-content: space-between;
gap: 8px;
}
.none {
display: none;
}
.input-row .img-row {
width: 100%;
min-width: 0px;
min-height: 0px;
box-sizing: border-box;
padding: 10px;
}
.input-row .img-box .delete-btn {
position: absolute;
right: -5px;
top: -5px;
}
.input-row .img-box {
width: 40px;
position: relative
}
.input-row img {
width: 40px;
height: 40px;
border-radius: 5px;
}
.chat-input textarea {
flex: 1;
resize: none;
padding: 10px;
border-radius: 8px;
border: 1px solid #d9e4f0;
font-size: 14px;
outline: none;
background: #f9fcff;
transition: border 0.2s;
width: 100%;
}
.chat-input textarea:focus {
/* border-color: #0057ff; */
}
.chat-input button {
padding: 10px 16px;
border: none;
border-radius: 8px;
background: #0057ff;
color: #fff;
font-size: 14px;
cursor: pointer;
transition: background 0.2s;
}
.chat-input button:hover {
background: #0046cc;
}
.options-row {
display: flex;
gap: 10px;
align-items: center;
font-size: 13px;
}
select,
input[type="file"] {
font-size: 13px;
padding: 4px 6px;
border: 1px solid #d9e4f0;
border-radius: 6px;
background: #fff;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(5px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
/* 修改后的输入框样式 - 豆包/腾讯元宝风格 */
.chat-input {
padding: 16px;
background: #fff;
border-top: 1px solid #e8e8e8;
display: flex;
flex-direction: column;
gap: 12px;
}
.input-row {
display: flex;
align-items: flex-end;
justify-content: flex-end;
/* 底部对齐 */
gap: 8px;
border: 1px solid #e5e5e5;
border-radius: 8px;
}
.input-row .button-row {
display: flex;
flex-direction: row;
align-items: flex-end;
justify-content: space-between;
gap: 12px;
width: 100%;
margin-right: 10px;
margin-bottom: 10px;
}
.input-row .button-row>div {
cursor: pointer;
width: 40px;
height: 40px;
display: flex;
justify-content: center;
align-items: center;
margin-left: 12px;
}
.chat-input textarea {
flex: 1;
resize: none;
padding: 12px 16px;
font-size: 14px;
outline: none;
background: #fff;
transition: all 0.2s;
min-height: 44px;
max-height: 200px;
box-shadow: none;
border: none !important;
box-sizing: border-box
}
.chat-input textarea:focus {
/* border-color: #0057ff; */
/* box-shadow: 0 0 0 2px rgba(0, 87, 255, 0.1); */
}
.chat-input button {
padding: 10px 16px;
height: 44px;
border: none;
border-radius: 8px;
background: #0057ff;
color: #fff;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
}
.chat-input button:hover {
background: #0046cc;
}
/* 文件选择和模型选择样式调整 */
.options-row {
display: flex;
gap: 8px;
align-items: center;
font-size: 13px;
}
select,
input[type="file"] {
font-size: 13px;
padding: 6px 8px;
border: 1px solid #e5e5e5;
border-radius: 6px;
background: #fff;
transition: all 0.2s;
}
select:focus,
input[type="file"]:focus {
border-color: #0057ff;
outline: none;
}
#fileInput {
display: none;
}
.mobilelogo {
display: none;
}
.userinfo-wrapper {
display: none;
}
/* 手机端适配 */
@media (max-width: 768px) {
.mobilelogo {
display: flex;
width: 28px;
height: 28px;
margin-right: 10px;
}
.sidebar {
/* position: absolute;
top: 10px; */
/* display: none; */
width: 240px;
background: #ffffff;
border-right: 1px solid #e3eaf2;
display: flex;
flex-direction: column;
transition: all 0.3s ease;
box-shadow: 8px 0 60px rgba(0, 0, 0, 0.04);
z-index: 1000;
position: absolute;
top: 10px;
height: 100%;
}
.sidebar.sidebar.collapsed>.toggle-btn {
position: fixed;
right: 10px !important;
}
.chat-container {
width: 100%;
height: 100vh;
}
.chat-input {
/* position: fixed; */
bottom: 0;
left: 0;
right: 0;
z-index: 100;
}
.chat-messages {
padding-bottom: 125px;
/* height: 200px */
/* 留出输入框空间 */
}
.chat-header .back {
display: flex;
justify-content: center;
align-items: center;
margin-right: 20px;
}
.userinfo-wrapper {
position: relative;
display: flex;
justify-content: center;
align-items: center;
}
.chat-container .content {
overflow-y: auto;
scrollbar-width: none;
}
/* 按钮样式 */
.userinfo-toggle {
padding: 7px 2px;
border-radius: 9px;
border: none;
background: #0057ff;
color: #fff;
font-weight: 500;
cursor: pointer;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.2);
transition: background 0.3s;
max-width: 38px;
overflow-x: hidden;
}
.userinfo-toggle:hover {
background: #0057ff;
}
/* 折叠面板 */
.userinfo-panel {
position: absolute;
top: 50px;
right: 0;
width: 100px;
background: #fff;
border-radius: 12px;
padding: 15px 20px;
box-shadow: 0 8px 20px rgba(0, 0, 0, 0.15);
display: flex;
flex-direction: column;
gap: 12px;
opacity: 0;
transform: translateY(-10px);
pointer-events: none;
transition: all 0.3s ease;
z-index: 1000;
}
/* 展开状态 */
.userinfo-panel.show {
opacity: 1;
transform: translateY(0);
pointer-events: auto;
}
/* 面板内部元素 */
.userinfo-panel span,
.userinfo-panel a {
padding: 8px 12px;
border-radius: 8px;
text-decoration: none;
color: #333;
background: #f1f1f1;
text-align: center;
transition: background 0.2s;
}
.userinfo-panel span:hover,
.userinfo-panel a:hover {
background: #e0e0e0;
}
.userinfo {
display: none !important;
}
}
.fourquestion {
display: flex;
flex-direction: row;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 10px;
margin-bottom: 20px;
}
.fourquestion>div {
display: flex;
flex-direction: column;
flex-wrap: wrap;
justify-content: center;
align-items: center;
gap: 10px;
}
.fourquestion .question {
box-sizing: border-box;
width: 300px;
display: flex;
justify-content: center;
align-items: center;
font-size: 21px;
color: #999;
border: 1px solid #e5e5e5;
border-radius: 10px;
padding: 24px 10px;
cursor: pointer;
}
.fourquestion .question:hover {
background-color: #e5e5e5;
color: #0057ff;
}
.fourquestion .question img {
width: 35px;
height: 35px;
margin-right: 5px;
}
.sidequestion {
display: flex;
flex-direction: column;
flex-wrap: wrap;
width: 100%;
display: none;
}
.sidequestion>div {
width: 100%;
}
.sidequestion>div .question {
width: 80%;
height: 2px;
display: flex;
justify-content: center;
align-items: center;
font-size: 16px;
color: #999;
border: 1px solid #e5e5e5;
border-radius: 10px;
padding: 15px -16px !important;
cursor: pointer;
}
.stopBtn {
width: 36px;
height: 36px;
}
.none {
display: none;
}
</style>
</head>
<body>
<!-- 左侧侧边栏 -->
<div class="sidebar" id="sidebar">
<button class="toggle-btn" id="toggleBtn" onclick="toggleSidebar()">☰</button>
<div class="sidebar-header"><img src="./img/logo.png">会话列表</div>
<div class="sidebar-actions">
<button onclick="newChat()">+ 新建会话</button>
</div>
<div class="chat-list" id="chatList"></div>
<div class="fourquestion sidequestion">
<div>
<div class='question' data-value='分析这个场站' data-pre-value="详细分析这个光伏场站,根据图片给出详细的运维方案"><img src="./img/1.png"
alt="">分析这个场站</div>
<div class='question' data-value='详细的清洗计划' data-pre-value="如果这个光伏场站需要用无人机清洗怎么做,给出一份详细的计划"><img
src="./img/4.png" alt="">详细的清洗计划</div>
</div>
<div>
<div class='question' data-value='详细的巡检计划' data-pre-value="如果这个光伏场站需要巡检怎么做,给出一份详细的计划"><img src="./img/2.png"
alt="">详细的巡检计划</div>
<div class='question' data-value='详细的割草计划' data-pre-value="如果这个光伏场站需要割草怎么做,给出一份详细的计划"><img src="./img/3.png"
alt="">详细的割草计划</div>
</div>
</div>
</div>
<!-- 收起按钮 -->
<!-- 右侧聊天区 -->
<div class="chat-container">
<div class="chat-header">
<!-- <div class='back' onclick="handleBack()">
<svg t="1748925372585" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"
p-id="4688" width="16" height="16">
<path
d="M395.21518 513.604544l323.135538-312.373427c19.052938-18.416442 19.052938-48.273447 0-66.660212-19.053961-18.416442-49.910737-18.416442-68.964698 0L291.75176 480.290811c-19.052938 18.416442-19.052938 48.273447 0 66.660212l357.633237 345.688183c9.525957 9.207709 22.01234 13.796214 34.497699 13.796214 12.485359 0 24.971741-4.588505 34.466999-13.82896 19.052938-18.416442 19.052938-48.242747 0-66.660212L395.21518 513.604544z"
fill="#999" p-id="4689"></path>
</svg>
</div> -->
<div style="
display: flex;
justify-content: flex-start;
align-items: center;
">
<img src="./img/logo.png" class="mobilelogo" alt="">
新能源智能体<span style="font-size:14px">(sataBotAgent)</span>
</div>
<div class="userinfo">
<span class="username"></span>
<span class="logout" onClick="logout()">退出</span>
<a class="logout price" href="./modal.html">服务价格</a>
</div>
<div class="userinfo-wrapper">
<!-- 初始按钮 -->
<button class="userinfo-toggle username" onclick="toggleUserInfo()">用户</button>
<!-- 折叠面板 -->
<div class="userinfo-panel">
<!-- <span class="username">用户名</span> -->
<span class="logout" onclick="logout()">退出</span>
<a class="logout price" href="./modal.html">服务价格</a>
</div>
</div>
</div>
<div class="content">
<div id="chatMessages" class="chat-messages"></div>
<div class="fourquestion fourquestionmain">
<div>
<div class='question mainauestion' data-value='分析这个场站' data-pre-value="详细分析这个光伏场站,根据图片给出详细的运维方案"><img
src="./img/1.png" alt="">分析这个场站</div>
<div class='question mainauestion' data-value='详细的清洗计划' data-pre-value="如果这个光伏场站需要用无人机清洗怎么做,给出一份详细的计划"><img
src="./img/4.png" alt="">详细的清洗计划</div>
</div>
<div>
<div class='question mainauestion' data-value='详细的巡检计划' data-pre-value="如果这个光伏场站需要巡检怎么做,给出一份详细的计划"><img
src="./img/2.png" alt="">详细的巡检计划</div>
<div class='question mainauestion' data-value='详细的割草计划' data-pre-value="如果这个光伏场站需要割草怎么做,给出一份详细的计划"><img
src="./img/3.png" alt="">详细的割草计划</div>
</div>
</div>
</div>
<div class="chat-input">
<!-- <div class="options-row">
<select id="mmModel">
<option value="hunyuan-vision">hunyuan-vision</option>
</select>
<select id="llModel">
<option value="hunyuan-api">hunyuan-api</option>
</select>
</div> -->
<div class="input-row">
<div class="img-row none">
<div class="img-box">
<img src="" alt="">
<span class="delete-btn" onclick="deleteImg()"><svg t="1757836617046" class="icon" viewBox="0 0 1024 1024"
version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="16945" width="16" height="16">
<path d="M512 512m-512 0a512 512 0 1 0 1024 0 512 512 0 1 0-1024 0Z" fill="#EF4631" p-id="16946">
</path>
<path
d="M733.6 733.6c-16.7 16.7-43.7 16.6-60.2 0.1l-151-151-151 151c-16.6 16.6-43.4 16.6-60.2-0.1-16.7-16.7-16.6-43.7-0.1-60.2l151-151-151-151c-16.6-16.6-16.6-43.4 0.1-60.2 16.7-16.7 43.7-16.6 60.2-0.1l151 151 151-151c16.6-16.6 43.4-16.6 60.2 0.1 16.7 16.7 16.6 43.7 0.1 60.2l-151 151 151 151c16.6 16.6 16.7 43.4-0.1 60.2z"
fill="#FFFFFF" p-id="16947"></path>
</svg></span>
</div>
</div>
<textarea id="chatInput" rows="1" placeholder="询问任何问题..."></textarea>
<div class="button-row">
<!-- <div id="sendFileBtn">
<input type="file" id="fileInput" /><svg t="1757833755414" class="icon" viewBox="0 0 1024 1024"
version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="14318" width="24" height="24">
<path
d="M576 928H192c-52.992 0-96-43.093333-96-96V192c0-52.992 43.093333-96 96-96h640c52.992 0 96 43.093333 96 96v384.064c0 17.706667-14.293333 32-32 32s-32-14.293333-32-32V192.021333c0-17.706667-14.4-32-32-32H192c-17.706667 0-32 14.378667-32 32v639.957334c0 17.706667 14.4 32 32 32h384c17.706667 0 32 14.314667 32 32 0 17.706667-14.293333 32.021333-32 32.021333zM128 693.312a32.064 32.064 0 0 1-22.613333-54.698667l159.402666-159.338666a95.786667 95.786667 0 0 1 110.72-17.984l173.589334 86.805333c12.309333 6.186667 27.093333 3.797333 36.8-5.994667l287.402666-287.445333a32.042667 32.042667 0 0 1 45.290667 45.312L631.210667 587.392a95.786667 95.786667 0 0 1-110.72 18.005333l-173.589334-86.826666a31.616 31.616 0 0 0-36.8 6.016l-159.509333 159.317333c-6.186667 6.314667-14.4 9.386667-22.592 9.386667z m320-277.376c-52.906667 0-96-43.093333-96-96s43.093333-96.021333 96-96.021333 96 43.093333 96 96-43.093333 96.021333-96 96.021333z m0-128c-17.6 0-32 14.378667-32 32 0 17.6 14.4 32 32 32s32-14.4 32-32c0-17.621333-14.4-32-32-32zM768 928c-17.706667 0-32-14.293333-32-32v-192.021333c0-17.706667 14.293333-32 32-32s32 14.293333 32 32v192c0 17.706667-14.293333 32.021333-32 32.021333z m128-128c-7.893333 0-15.701333-2.922667-21.909333-8.725333L768 691.669333l-106.090667 99.712a31.936 31.936 0 0 1-45.226666-1.408 31.957333 31.957333 0 0 1 1.408-45.205333l112.213333-105.386667A48.554667 48.554667 0 0 1 768 621.44c14.72 0 28.501333 6.613333 37.696 17.92l112.213333 105.386667A31.957333 31.957333 0 0 1 896 800z"
fill="#666666" p-id="14319"></path>
</svg>
</div> -->
<div id="sendFileBtn" style="position: relative; width: 40px; height: 40px; cursor: pointer;">
<input type="file" id="fileInput" style="position: absolute;
left: 0; top: 0; width: 100%; height: 100%;
opacity: 0; cursor: pointer;" capture="camera" />
<img src="./img/link.jpg" alt="" style="width:24px;height:24px">
<path
d="M576 928H192c-52.992 0-96-43.093333-96-96V192c0-52.992 43.093333-96 96-96h640c52.992 0 96 43.093333 96 96v384.064c0 17.706667-14.293333 32-32 32s-32-14.293333-32-32V192.021333c0-17.706667-14.4-32-32-32H192c-17.706667 0-32 14.378667-32 32v639.957334c0 17.706667 14.4 32 32 32h384c17.706667 0 32 14.314667 32 32 0 17.706667-14.293333 32.021333-32 32.021333zM128 693.312a32.064 32.064 0 0 1-22.613333-54.698667l159.402666-159.338666a95.786667 95.786667 0 0 1 110.72-17.984l173.589334 86.805333c12.309333 6.186667 27.093333 3.797333 36.8-5.994667l287.402666-287.445333a32.042667 32.042667 0 0 1 45.290667 45.312L631.210667 587.392a95.786667 95.786667 0 0 1-110.72 18.005333l-173.589334-86.826666a31.616 31.616 0 0 0-36.8 6.016l-159.509333 159.317333c-6.186667 6.314667-14.4 9.386667-22.592 9.386667z m320-277.376c-52.906667 0-96-43.093333-96-96s43.093333-96.021333 96-96.021333 96 43.093333 96 96-43.093333 96.021333-96 96.021333z m0-128c-17.6 0-32 14.378667-32 32 0 17.6 14.4 32 32 32s32-14.4 32-32c0-17.621333-14.4-32-32-32zM768 928c-17.706667 0-32-14.293333-32-32v-192.021333c0-17.706667 14.293333-32 32-32s32 14.293333 32 32v192c0 17.706667-14.293333 32.021333-32 32.021333z m128-128c-7.893333 0-15.701333-2.922667-21.909333-8.725333L768 691.669333l-106.090667 99.712a31.936 31.936 0 0 1-45.226666-1.408 31.957333 31.957333 0 0 1 1.408-45.205333l112.213333-105.386667A48.554667 48.554667 0 0 1 768 621.44c14.72 0 28.501333 6.613333 37.696 17.92l112.213333 105.386667A31.957333 31.957333 0 0 1 896 800z"
fill="#666666" p-id="14319"></path>
</svg>
</div>
<div id="sendBtn">
<img src="./img/upload.png" alt="" style="width:36px;height:36px">
<div class="stopBtn none" id="stopBtn">
<svg t="1762735469119" class="icon" viewBox="0 0 1024 1024" version="1.1"
xmlns="http://www.w3.org/2000/svg" p-id="4603" width="36" height="36">
<path
d="M512 947.456A435.456 435.456 0 1 1 947.456 512 435.456 435.456 0 0 1 512 947.456z m0.4096-81.3056A353.8432 353.8432 0 1 0 158.72 512.3072a353.8944 353.8944 0 0 0 353.6896 353.8432zM407.7568 386.8672h208.4864a20.8384 20.8384 0 0 1 20.8384 20.7872v208.5888a20.8384 20.8384 0 0 1-20.8384 20.7872H407.7568a20.8384 20.8384 0 0 1-20.8384-20.7872V407.7568a20.8896 20.8896 0 0 1 20.8384-20.8896z"
p-id="4604"></path>
</svg>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="./marked.min.js"></script>
<!-- <script>
// 开启 vConsole 调试面板
var vConsole = new VConsole();
console.log("✅ vConsole 已经启动");
</script> -->
<script>
const sidebar = document.getElementById("sidebar");
const chatMessages = document.getElementById("chatMessages");
const chatInput = document.getElementById("chatInput");
const fileInput = document.getElementById("fileInput");
const chatList = document.getElementById("chatList");
let controller = null;
let sessIdList = [];
let isUpload = {};
let hasImage = false;
let isPre = false;//是否点击了预设问题
// let sessIdList = JSON.parse(localStorage.getItem("sessId")) || [];
let sessId;
let activeChatId = '';
const params = new URLSearchParams(window.location.search);
console.log(params.get("userId"), 644);
const encodedUserId = params.get("userId");
const from = params.get("from");
const code = params.get("code");
const appid = "wx364b8ebf17fe45fe";
// 回调地址(必须和公众号后台配置的网页授权域名一致)
const redirect_uri = encodeURIComponent("http://ai.satabot.com");
// 授权方式:snsapi_base(静默) 或 snsapi_userinfo(需要用户确认)
const scope = "snsapi_userinfo";
// 自定义参数,授权后会原样返回
const state = "12399";
// ✅ 先 URL 解码,再 Base64 解码,再 UTF-8 解码
const userId = decodeURIComponent(Number(escape(atob(encodedUserId))));
setCookie("userId", userId);
getUserName(userId);
// const userId = encodedUserId;
let sendParam = {
imageBase64: '',
userId: userId,
sessionId: '',
userPrompt: ''
}
if (isMobile()) {
toggleSidebar();
}
console.log(isWeChat(), 999, code)
// if (isWeChat()) {
// if (code) {
// // gotoWeiXin();
// // 用 code 换取 openid 和 access_token
// fetch(`https://api.weixin.qq.com/sns/oauth2/access_token?appid=${appid}&secret=SECRET&code=CODE&grant_type=authorization_code?code=${code}`)
// .then(res => res.json())
// .then(data => {
// // data 里返回 userId / openid
// setCookie("userId", data.userId);
// window.location.href = "/"; // 回到首页
// });
// } else {
// // 没拿到 code,再次走 gotoWeiXin()
// gotoWeiXin();
// }
// } else {
if (getCookie("userId")) {
getSessionList();
} else {
deleteCookie('userId');
window.location.href = 'http://ri.satabot.com/#/login?goRemote=1&clear=1';
}
// }
const questions = document.querySelectorAll(".question");
// 获取到用户名称
function getUserName(userId) {
try {
fetch("http://1.95.137.212:30009/user", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
userId,
})
}).then(res => res.json()).then(data => {
if (data.code === 0) {
document.querySelectorAll('.username').forEach((e) => {
e.textContent = data.userName;
})
console.log("成功找到该用户:", data.userName, data.userId);
} else {
console.error("未找到该用户:",);
}
})
} catch (err) {
console.error("请求异常:", err);
}
}
questions.forEach(q => {
q.addEventListener("click", () => {
if (q.classList.contains("mainauestion")) {
// q.style.display = 'none';
// if (!isMobile()) {
document.querySelector('.fourquestionmain').style.display = 'none';
document.querySelector('.sidequestion').style.display = 'flex';
document.querySelector('.chat-messages').style.height = '100%';
// }
}
isPre = true;
console.log(isUpload, 88)
if (!sendParam.imageBase64 && !fileInput.files.length) {
// if (!isUpload[activeChatId]) {
if (!hasImage) {
showToast("请先上传图片");
// alert("请先上传图片");
return;
}
}
const value = q.getAttribute("data-value");
const preValue = q.getAttribute("data-pre-value");
sendParam.userPrompt = preValue;
chatInput.value = value; // ✅ 把预设问题填入输入框
chatInput.focus(); // 可选:让光标聚焦输入框
sendMessage(true);
});
});
function isMobile() {
return /Android|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
}
// getHistory();
function setCookie(name, value, days = 7) {
const d = new Date();
d.setTime(d.getTime() + days * 24 * 60 * 60 * 1000);
const expires = "expires=" + d.toUTCString();
document.cookie = `${name}=${encodeURIComponent(value)}; ${expires}; path=/`;
}
function getCookie(name) {
const match = document.cookie.match(new RegExp('(^| )' + name + '=([^;]+)'));
if (match) return decodeURIComponent(match[2]);
return null;
}
function deleteCookie(name) {
document.cookie = `${name}=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;`;
}
function handleBack() {
RPJavaFragmentInterface.navigateTo("StatusFragment");
}
function generateUniqueId16() {
// 当前时间戳(毫秒,取后几位)
const timestamp = Date.now().toString().slice(-10);
// 生成 6 位随机数
const random = Math.floor(Math.random() * 1e6).toString().padStart(6, "0");
return timestamp + random; // 一共 16 位
}
function logout() {
deleteCookie('userId');
window.location.href = 'http://ri.satabot.com/#/login?goRemote=1&clear=1';
}
function createSession(userId, sessionId) {
try {
fetch("http://1.95.137.212:30009/session", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
userId,
sessionId,
})
}).then(res => res.json()).then(data => {
if (data.code === 0) {
hasImage = false;
console.log("会话创建成功:", data.message);
} else {
console.error("创建失败:", data.error);
}
})
} catch (err) {
console.error("请求异常:", err);
}
}
function showToast(message) {
// 创建元素
const toast = document.createElement("div");
toast.className = "toast";
toast.innerText = message;
document.body.appendChild(toast);
// 显示
setTimeout(() => {
toast.classList.add("show");
}, 10);
// 2 秒后自动消失
setTimeout(() => {
toast.classList.remove("show");
setTimeout(() => toast.remove(), 300); // 动画结束后移除
}, 2000);
}
function scrollToBottom() {
const chatMessages = document.getElementById("chatMessages");
if (!chatMessages) return;
requestAnimationFrame(() => {
chatMessages.scrollTop = chatMessages.scrollHeight;
});
}
// function scrollToBottom() {
// const chatMessages = document.getElementById("chatMessages");
// setTimeout(() => {
// chatMessages.scrollTop = chatMessages.scrollHeight;
// chatMessages.bottom = '0px';
// }, 0);
// }
const panel = document.querySelector('.userinfo-panel');
const toggleButton = document.querySelector('.userinfo-toggle');
function toggleUserInfo() {
panel.classList.toggle('show');
}
document.addEventListener('click', (e) => {
if (!toggleButton.contains(e.target) && !panel.contains(e.target)) {
panel.classList.remove('show');
}
});
// 阻止点击面板本身关闭
panel.addEventListener('click', (e) => {
e.stopPropagation();
});
function renderChatList(isNew) {
chatList.innerHTML = '';
if (!sessIdList || sessIdList.length == 0) {
chatMessages.innerHTML = "";
return;
}
// if (isNew == "new") {
activeChatId = sessIdList[sessIdList.length - 1].sessId;
// } else {
// activeChatId = sessIdList[0];
// }
sendParam.sessionId = activeChatId;
getHistory();
sessIdList.forEach((item, index) => {
console.log(item, activeChatId);
const item1 = document.createElement("div");
item1.setAttribute("data-sessionid", item.sessId);
item1.className = "chat-item" + (activeChatId == item.sessId ? " active" : "");
const title = document.createElement("div");
title.className = "chat-title";
if (item.title) {
title.textContent = item.title;
} else {
title.textContent = `新对话${index + 1}`;
}
item1.onclick = () => switchChat(item.sessId);
const del = document.createElement("span");
del.className = "delete-btn";
del.textContent = "✕";
del.onclick = (e) => {
e.stopPropagation();
deleteChat(item.sessId);
};
item1.appendChild(title);
item1.appendChild(del);
chatList.appendChild(item1);
})
}
// bool 是否新创建的
function renderMessages(data, bool) {
chatMessages.innerHTML = "";
if (bool || !data || !data.length) {
return;
}
data.forEach(msg => {
if (msg.image_base64) {
hasImage = true; // 只要有一个不为空,就置为 true
}
addMessage(msg.content, msg.role, msg.image_base64);
});
setTimeout(scrollToBottom, 0);
}
// 内容 角色 图片 创造时间
function addMessage(content = '', role = "assistant", imgUrl = null, time = null) {
const msg = document.createElement("div");
msg.className = `message ${role}`;
if (time) {
const timeNode = document.createElement("div");
timeNode.textContent = time;
timeNode.style.fontSize = "12px";
timeNode.style.color = "#999";
timeNode.style.marginTop = "4px";
// msg.appendChild(timeNode);
}
// 如果有文字内容,先创建文字节点
if (content) {
const textNode = document.createElement("div");
textNode.innerHTML = marked.parse(content);
msg.appendChild(textNode);
}
// 如果有图片,创建图片节点
if (imgUrl) {
const img = document.createElement("img");
img.src = imgUrl;
img.style.marginTop = content ? "8px" : "0"; // 如果有文字,图片加一点间距
msg.appendChild(img);
}
// 如果有时间,可以显示
chatMessages.appendChild(msg);
chatMessages.scrollTop = chatMessages.scrollHeight;
}
function sendMessage(isPre) {
console.log("发送消息1122");
const sendBtnImg = document.querySelector('#sendBtn img');
const stopBtn = document.querySelector('#stopBtn');
// 2. 先判断 stopBtn 的状态,再控制 sendBtn img(逻辑更清晰)
if (!activeChatId) {
newChat();
}
// if (sessIdList.length == 0) {
// return;
// }
ifSessionId = true;
const text = chatInput.value.trim();
if (!text && !fileInput.files.length && !sendParam.imageBase64) return;
if (text) addMessage(text, "user");
let imgUrl = '';
if (sendParam.imageBase64) {
imgUrl = sendParam.imageBase64;
hasImage = true;
if (fileInput.files[0]) {
const file = fileInput.files[0];
const url = URL.createObjectURL(file);
addMessage('', "user", url);
} else {
addMessage('', "user", sendParam.imageBase64);
sendParam.imageBase64 = '';
}
fileInput.value = "";
}
if (!isPre) {
sendParam.userPrompt = text;
} else {
}
chatInput.value = "";
if (controller) {
controller.abort();
controller = null;
}
if (!sendBtnImg.classList.contains('none')) {
sendBtnImg.classList.add('none');
stopBtn.classList.remove('none')
}
getAiResponse(imgUrl);
}
const getAiResponse = async (img) => {
deleteImg();
const aiMsg = { role: "assistant", content: "", isImage: false };
const msgDiv = document.createElement("div");
msgDiv.className = "message assistant";
msgDiv.textContent = '思考中';
let dots = 0;
const interval = setInterval(() => {
dots = (dots + 1) % 4; // 0,1,2,3
msgDiv.textContent = '思考中' + '.'.repeat(dots);
}, 500);
msgDiv.className = "message assistant thinking ";
chatMessages.appendChild(msgDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
sessId = sendParam.sessionId;
sendParam.imageBase64 = img;
// sendParam.userId= encodedUserId;
sendParam.userId = decodeURIComponent(Number(escape(atob(encodedUserId))));
// ✅ 创建 AbortController
controller = new AbortController();
const res = await fetch("http://1.95.137.212:30009/analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(sendParam),
signal: controller.signal, // ✅ 加上 signal
}).catch(err => {
console.log("✅ 流被中断:", err.name);
return null;
});
if (!res) return; // 流已中断则直接退出
sendParam.imageBase64 = '';
const reader = res.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = "";
msgDiv.classList.remove("thinking");
clearInterval(interval);
while (true) {
const { done, value } = await reader.read();
console.log(done, value, 'done')
if (done) {
const sendBtnImg = document.querySelector('#sendBtn img');
const stopBtn = document.querySelector('#stopBtn');
sendBtnImg.classList.add('none');
stopBtn.classList.remove('none');
break;
};
buffer += decoder.decode(value, { stream: true });
let lines = buffer.split("\n");
buffer = lines.pop();
for (let line of lines) {
line = line.trim();
if (!line.startsWith("data:")) continue;
const data = line.slice(5).trim();
console.log("完成回答", data)
if (data == "[DONE]") {
console.log("完成回答")
// 更新聊天滚动条
const sendBtnImg = document.querySelector('#sendBtn img');
const stopBtn = document.querySelector('#stopBtn');
sendBtnImg.classList.remove('none');
stopBtn.classList.add('none');
setTimeout(scrollToBottom, 0); // chatMessages.scrollTop = chatMessages.scrollHeight;
return;
}
try {
const obj = JSON.parse(data);
// if (obj.content && obj.type != 'title') {
// // 每次追加内容到 AI 消息
// aiMsg.content += obj.content;
// msgDiv.innerHTML = marked.parse(aiMsg.content);
// // msgDiv.textContent = aiMsg.content;
// chatMessages.scrollTop = chatMessages.scrollHeight;
// }
if (obj.content && obj.type !== "title") {
for (const char of obj.content) {
await new Promise(r => setTimeout(r, 5)); // 控制字速
aiMsg.content += char;
msgDiv.innerHTML = marked.parse(aiMsg.content);
// chatMessages.scrollTop = chatMessages.scrollHeight;
setTimeout(scrollToBottom, 0);
}
}
if (obj.content && obj.type == 'title') {
const chatItem = document.querySelector(`.chat-item[data-sessionid="${sessId}"]`);
if (chatItem) {
const chatTitle = chatItem.querySelector('.chat-title');
if (chatTitle) {
chatTitle.textContent = obj.content; // 设置标题
}
}
}
} catch (err) {
console.error("JSON parse error", data);
}
}
}
};
function newChat() {
const id = generateUniqueId16();
sessIdList.push({ sessId: id, title: "新对话" });
createSession(userId, id)
const chat = [{
id,
title: "新对话 " + (sessIdList.length),
messages: { role: "assistant", content: "👋 新会话已创建,请输入问题。", image_base64: '' }
}];
activeChatId = id;
ifSessionId = true;
sessId = id;
sendParam.sessionId = sessId;
renderMessages([chat[0].messages], true);
renderChatList('new');
}
function switchChat(id) {
activeChatId = id;
sendParam.sessionId = id;
const items = document.querySelectorAll(".chat-item");
items.forEach(item => {
if (item.dataset.sessionid === id) {
item.classList.add("active");
} else {
item.classList.remove("active");
}
});
getHistory();
// renderChatList();
}
const deleteSession = (sessionId, userId) => {
try {
fetch(`http://1.95.137.212:30009/session?sessionId=${sessionId}&userId=${userId}`, {
method: "DELETE"
}).then(res => res.json()).then(data => {
if (data.code === 0) {
getSessionList();
renderChatList();
console.log("删除成功:", data.message);
} else {
console.error("删除失败:", data.error);
}
})
} catch (err) {
console.error("请求异常:", err);
}
}
function deleteChat(id) {
// sessIdList = sessIdList.filter(item => item !== id);
// localStorage.setItem("sessId", JSON.stringify(sessIdList));
deleteSession(id, sendParam.userId)
}
function handleSendFile() {
console.log("选择文件");
// 触发隐藏的 input 点击
document.getElementById("fileInput").click();
}
// 监听文件选择
document.getElementById("fileInput").addEventListener("change", function (e) {
const file = e.target.files[0];
if (file) {
console.log("选择的文件:", file.name);
// 这里可以直接上传
uploadFile(file);
}
});
// 示例上传函数(fetch 到后端接口)
function uploadFile(file) {
if (file) {
console.log("上传文件:", file.name, URL.createObjectURL(file));
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = async function () {
console.log(reader.result)
sendParam.imageBase64 = reader.result;
document.querySelector(".img-row").classList.remove("none");
document.querySelector(".img-row img").src = sendParam.imageBase64;
isUpload[activeChatId] = true;
}
// document.getElementById("rawImg").src = URL.createObjectURL(file);
}
}
function deleteImg() {
document.querySelector(".img-row").classList.add("none");
sendParam.imageBase64 = '';
document.querySelector(".img-row img").src = '';
}
chatInput.addEventListener("keydown", function (e) {
if (e.key === "Enter" && !e.shiftKey) { // 按下回车且未按Shift
e.preventDefault(); // 阻止换行
sendMessage(); // 调用发送函数
}
});
document.querySelector("#sendBtn img").addEventListener("click", function () {
console.log("发送信息11")
sendMessage();
});
document.getElementById("sendFileBtn").addEventListener("click", function () {
handleSendFile()
});
// getHistory();
// renderChatList("new");
// sendParam.sessionId = "7906661519458367";
// 调用获取历史记录的接口
function getHistory() {
// sendParam.userId = encodedUserId;
fetch(`http://1.95.137.212:30009/history?sessionId=${sendParam.sessionId}`, {
// fetch(`http://1.95.137.212:30008/history?sessionId=${sendParam.sessionId}`, {
method: "GET",
// headers: { "Content-Type": "application/json" },
// credentials: 'include', // 发送 cookie
// body: JSON.stringify(sendParam),
}).then(res => res.json()).then(data => {
// chats = data.messages;
// renderChatList();
renderMessages(data.messages)
})
}
// sessIdList
function isWeChat() {
return /MicroMessenger/i.test(navigator.userAgent);
}
// function getCookie(name) {
// const value = `; ${document.cookie}`;
// const parts = value.split(`; ${name}=`);
// if (parts.length === 2) return parts.pop().split(';').shift();
// return null;
// }
// gotoWeiXin();
function gotoWeiXin() {
console.log(isWeChat(), 8888, navigator.userAgent);
const authUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`;
// 拼接完整的授权链接
// const authUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appid}&redirect_uri=${redirect_uri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`;
// 跳转到微信授权
console.log("跳转链接:", authUrl);
window.location.href = authUrl;
}
// if(from=="weixin"){
// gotoWeiXin();
// }else{
// }
// if (!isWeChat()) {
// } else {
// gotoWeiXin();
// }
function getSessionList() {
sendParam.userId = userId;
fetch(`http://1.95.137.212:30009/sessions?userId=${sendParam.userId}`, {
// fetch(`http://1.95.137.212:30008/history?sessionId=${sendParam.sessionId}`, {
method: "GET",
// headers: { "Content-Type": "application/json" },
// credentials: 'include', // 发送 cookie
// body: JSON.stringify(sendParam),
}).then(res => res.json()).then(data => {
console.log(data, 8777)
if (data.code == 300) {
// window.location.href='http://localhost/#/login?goRemote=1 '
window.location.href = data.error;
return;
}
sessIdList = data.sessions;
renderChatList();
console.log(data, 8777)
// chats = data.messages;
// renderChatList();
// renderMessages(data.messages)
})
}
function toggleSidebar() {
sidebar.classList.toggle("collapsed");
const toggleBtn = document.getElementById("toggleBtn");
if (sidebar.classList.contains("collapsed")) {
toggleBtn.style.left = "10px"; // 收起后
} else {
toggleBtn.style.left = "190px"; // 展开后
}
}
document.getElementById("stopBtn").addEventListener("click", () => {
const sendBtnImg = document.querySelector('#sendBtn img');
const stopBtn = document.querySelector('#stopBtn');
if (controller) {
controller.abort();
controller = null;
if (sendBtnImg.classList.contains('none')) {
sendBtnImg.classList.remove('none');
stopBtn.classList.add('none')
}
}
});
</script>
</body>
</html>