Files
AI/index.html
2025-11-21 16:10:24 +08:00

2776 lines
88 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,user-scalable=no" />
<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://res.wx.qq.com/connect/zh_CN/htmledition/js/wxopensdk.js"> -->
<!-- </script> -->
<script src="./marked.min.js"></script>
<script src="https://unpkg.com/vconsole@latest/dist/vconsole.min.js"></script>
<style>
* {
touch-action: manipulation;
/* 允许平移和捏合缩放,但禁用双击缩放和长按 */
-webkit-tap-highlight-color: transparent;
/* 移除点击时的默认高亮(可选,优化体验) */
}
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: 9999999999;
}
.toast.show {
opacity: 1;
}
.toast.success {
background-color: #09db45;
}
/* 侧边栏 */
.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;
height: 100%;
left: 0px;
}
.sidebar.collapsed {
left: -260px;
/* 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 {
display: flex;
justify-content: center;
align-items: center;
position: absolute;
top: 7px;
left: 190px;
background: #0057ff;
color: #fff;
border: none;
border-radius: 50%;
width: 28px;
height: 28px;
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);
height: 100%;
}
.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%;
left: 0px;
}
.sidebar.sidebar.collapsed>.toggle-btn {
position: fixed;
right: 10px !important;
width: 28px;
height: 28px;
display: flex;
justify-content: center;
align-items: center;
}
.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;
}
.input-group {
display: flex;
align-items: center;
padding: 0px 5px;
border: 1px solid #ccc;
border-radius: 8px;
margin-bottom: 10px;
}
.area-select {
width: 130px;
font-size: 14px;
color: #333;
cursor: pointer;
}
input {
/* flex: 1; */
border: none;
outline: none;
font-size: 14px;
}
/* 获取验证码按钮 */
.code-btn {
border: none;
/* border: 1px solid #1890ff; */
background: white;
padding: 5px 12px;
border-radius: 6px;
color: #1890ff;
cursor: pointer;
font-size: 14px;
width: 135px;
}
/* 登录按钮 */
.login-btn {
margin-top: 25px;
width: 100%;
background: #1890ff;
color: white;
padding: 12px 0;
border-radius: 10px;
border: none;
font-size: 17px;
cursor: pointer;
}
/* 协议 */
.agreement {
margin-top: 15px;
font-size: 12px;
color: #777;
display: flex;
align-items: center;
gap: 6px;
}
/* 微信登录 */
.wechat-login {
text-align: center;
font-size: 14px;
color: #555;
cursor: pointer;
}
.wechat-login>div {
display: flex;
justify-content: center;
align-items: center;
gap: 2px;
}
.wechat-login img {
width: 38px;
display: block;
margin: 0 auto 5px;
}
/* 区号选择框 */
.mask {
display: none;
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.4);
}
.area-box {
background: #fff;
position: absolute;
bottom: 0;
width: 100%;
max-height: 300px;
overflow-y: auto;
border-radius: 10px 10px 0 0;
}
.area-item {
padding: 15px;
border-bottom: 1px solid #eee;
font-size: 16px;
cursor: pointer;
}
.area-item:hover {
background: #f5f5f5;
}
.toggle-eye {
position: absolute;
right: 10px;
top: 10px;
}
.link {
color: #1677ff;
cursor: pointer;
}
.wechat-login {
display: flex;
justify-content: space-between;
font-size: 12px;
color: #666;
margin-bottom: 10px;
}
.wechat-login span:nth-child(1) {
margin-right: 8px;
}
.popbox {
position: fixed;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
background-color: rgba(0, 0, 0, 0.4);
z-index: 999999;
}
.popbox .loading-box {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
z-index: 999999999;
display: flex;
justify-content: center;
align-items: center;
background-color: #fff;
opacity: 0.7;
}
.popbox .loading-box.none {
display: none;
}
.popbox .loading-box .loading {
border: solid #e2e8f0;
/* 浅灰色边框 */
border-top-color: #165DFF;
/* 蓝色顶部边框 */
border-radius: 50%;
animation: spin 1s linear infinite;
/* 旋转动画 */
display: inline-block;
margin-bottom: 20px;
width: 30px;
height: 30px;
}
#sms-form {
background-color: #fff;
border-radius: 8px;
padding: 10px;
position: relative;
}
</style>
<style>
body {
font-family: Arial, sans-serif;
background: #f5f5f5;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
}
.popbgc {
background-color: rgba(0, 0, 0, 0.4);
position: fixed;
left: 0px;
right: 0px;
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
z-index: 999999;
transition: all 0.3s ease-in-out;
backdrop-filter: blur(2px);
position: relative;
}
#sms-button {
padding: 12px;
}
.popbgc .loading-box {
position: absolute;
top: 0px;
left: 0px;
width: 100%;
height: 100%;
z-index: 999999999;
}
.popbgc .loading-box.none {
display: none;
}
.popbgc .loading-box .loading {
border: solid #e2e8f0;
/* 浅灰色边框 */
border-top-color: #165DFF;
/* 蓝色顶部边框 */
border-radius: 50%;
animation: spin 1s linear infinite;
/* 旋转动画 */
display: inline-block;
margin-bottom: 20px;
width: 30px;
height: 30px;
}
.popup {
background: #fff;
padding: 20px;
border-radius: 8px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.2);
width: 400px;
}
.popup h2 {
margin-top: 0;
font-size: 18px;
margin-bottom: 15px;
}
input,
select,
button {
width: 100%;
padding: 8px;
margin: 5px 0;
border-radius: 4px;
border: 1px solid #ccc;
}
input {
border: none;
}
.options {
display: flex;
flex-wrap: wrap;
gap: 5px;
margin: 10px 0;
}
.option {
/* flex: 1 1 calc(50% - 5px); */
width: 28%;
text-align: center;
padding: 8px;
background: #f0f0f0;
border-radius: 4px;
cursor: pointer;
user-select: none;
}
.option.selected {
background: #007bff;
color: white;
}
button {
background: #007bff;
color: white;
border: none;
cursor: pointer;
}
.popbgc.none {
display: none;
}
.weixin-loading {
display: none;
}
@media (max-width: 768px) {
.popbgc {
width: 100%;
height: 100%;
}
.popup {
position: absolute;
top: 50px;
width: 90%;
}
.weixin-loading {
width: 100%;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
position: fixed;
z-index: 99999;
background-color: #fff;
top: 0px;
left: 0px;
opacity: 0.7;
}
.weixin-loading.none {
display: none;
}
.weixin-loading .loading {
border: solid #e2e8f0;
/* 浅灰色边框 */
border-top-color: #165DFF;
/* 蓝色顶部边框 */
border-radius: 50%;
animation: spin 1s linear infinite;
/* 旋转动画 */
display: inline-block;
margin-bottom: 20px;
width: 30px;
height: 30px;
}
@keyframes spin {
to {
transform: rotate(360deg);
}
}
}
</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 class="popbgc none">
<div class="popup">
<h2>开启光伏运维智能化!</h2>
<form id="popupForm">
<input type="text" name="company" placeholder="请输入您的公司" required>
<!-- <select name="need" required>
<option value="">您的需求是什么?</option>
<option value="杂草清理">杂草清理</option>
<option value="组件积灰清扫">组件积灰清扫</option>
<option value="EL/IR缺陷识别">EL/IR缺陷识别</option>
<option value="热斑检测">热斑检测</option>
<option value="需要运维机器人">需要运维机器人</option>
<option value="定制机器人">定制机器人</option>
</select> -->
<div>
<p>您对以下哪些场景感兴趣?</p>
<div class="options" id="scenes">
<div class="option">山地光伏</div>
<div class="option">荒漠 / 戈壁</div>
<div class="option">农光互补电站</div>
<div class="option">渔光互补电站</div>
<div class="option">滩涂 / 盐碱地</div>
<div class="option">平原 / 草原</div>
</div>
</div>
<div>
<p>您的需求?</p>
<div class="options" id="needs">
<div class="option">杂草清理</div>
<div class="option">组件积灰清扫</div>
<div class="option">EL/IR缺陷识别</div>
<div class="option">热斑检测</div>
<div class="option">需要运维机器人</div>
<div class="option">定制机器人</div>
</div>
</div>
<button type="submit">立即使用</button>
</form>
</div>
</div>
<div class="weixin-loading none ">
<div class="loading">
</div>
</div>
</div>
<script>
// 开启 vConsole 调试面板
var vConsole = new VConsole();
console.log("✅ vConsole 已经启动");
</script>
<script>
// document.addEventListener('DOMContentLoaded', function () {
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 openId = '';
let isStopped = false;//是否停止输出
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");
if (code) {
console.log(code, "code")
// console.log(window.location);
fetch(`/api/wechat/oauth/access-token?code=${code}`).then(res => res.json()).then(res => {
console.log(res, '获取accesstoken')
})
}
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 解码
let userId;
if (from == "weixin") {
userId = encodedUserId;
openId = encodedUserId;
setCookie('openId', openId)
document.querySelector('.userinfo-panel .logout').style.display = 'none';
} else {
userId = decodeURIComponent(Number(escape(atob(encodedUserId))));
}
if (userId && userId != "NaN") {
setCookie("userId", userId);
}
getUserName(userId);
// const userId = encodedUserId;
let sendParam = {
imageBase64: '',
userId: userId,
sessionId: '',
userPrompt: '',
requestId: ''
}
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") && getCookie("userId") != "NaN") {
if (from == "weixin") {
getUserByOpenId(userId)
} else {
getSessionList();
}
} else {
// deleteCookie('userId');
if (from == "weixin") {
} else {
// getUserName(userId);
getSessionList();
// window.location.href = 'http://ri.satabot.com/index.html#/login?goRemote=1&clear=1';
// window.location.href = 'http://ri.satabot.com/#/login?goRemote=1&clear=1';
}
}
function handleLogin(data) {
console.log("开始登录")
const loginParams = data;
return new Promise((resolve, reject) => {
fetch("https://serviceri.satabot.com/login", {
method: "POST", // 必选:指定请求方法为 POST
headers: {
"Content-Type": "application/json", // 声明请求体是 JSON 格式
// 可选:如果接口需要 Token 前置验证,可添加 Authorization 头
// "Authorization": "Bearer 你的前置 Token"
},
// 必选:请求体,将参数对象转为 JSON 字符串(不能直接传对象)
body: JSON.stringify(loginParams)
})
.then(res => {
console.log(res, 987)
if (!res.ok) {
throw new Error(`请求失败:${res.status} ${res.statusText}`);
}
return res.json(); // 只有 2xx 状态才解析 JSON
})
.then(data => {
console.log("登录回复")
resolve(data);
})
.catch(err => {
reject(err);
});
});
}
// 发送验证码
function handleBind(num) {
const bindParams = {
phoneNumbers: num,
}
return new Promise((resolve, reject) => {
fetch("https://serviceri.satabot.com/external/sms/sendAliSmsCode", {
method: "POST", // 必选:指定请求方法为 POST
headers: {
"Content-Type": "application/json", // 声明请求体是 JSON 格式
// 可选:如果接口需要 Token 前置验证,可添加 Authorization 头
// "Authorization": "Bearer 你的前置 Token"
},
// 必选:请求体,将参数对象转为 JSON 字符串(不能直接传对象)
body: JSON.stringify(bindParams)
})
.then(res => {
if (!res.ok) {
throw new Error(`请求失败:${res.status} ${res.statusText}`);
}
return res.json(); // 只有 2xx 状态才解析 JSON
})
.then(data => {
resolve(data);
})
.catch(err => {
reject(err);
});
});
}
function bindToLogin() {
document.querySelector('.popbox .loading-box').classList.remove('none');
const data = {
sourceType: 5,
openId: openId,
phonenumber: document.getElementById('sys-phone').value,
smsCode: document.getElementById('code').value
}
retryFetch("https://serviceri.satabot.com/login", {
// fetch("/api/remote/login", {
method: "POST", // 必选:指定请求方法为 POST
headers: {
"Content-Type": "application/json", // 声明请求体是 JSON 格式
// 可选:如果接口需要 Token 前置验证,可添加 Authorization 头
// "Authorization": "Bearer 你的前置 Token"
},
// 必选:请求体,将参数对象转为 JSON 字符串(不能直接传对象)
body: JSON.stringify(data)
}).then(res => {
console.log(res, "绑定信息")
if (res.code == 200) {
if (document.querySelector('.popbox .loading-box')) {
document.querySelector('.popbox .loading-box').classList.add('none');
}
userId = res.userId;
setCookie('token', res.token);
unshowpop();
getSessionList();
}
// } else {
// showToast(res.msg + "denglucuowu");
// setTimeout(() => {
// // window.location.reload();
// }, 2000);
// return;
// }
}).catch(err => {
// 捕获异步异常(如网络错误、接口报错、代码报错)
console.error("请求异常:", err);
showToast("登录失败,请重试" + err); // 给用户友好提示
})
.finally(() => {
unshowpop();
if (document.querySelector('.popbox .loading-box')) {
document.querySelector('.popbox .loading-box').classList.add('none');
}
});
// try {
// handleLogin(data).then(res => {
// if (res.code == 200) {
// if (document.querySelector('.popbox .loading-box')) {
// document.querySelector('.popbox .loading-box').classList.add('none');
// }
// userId = res.userId;
// setCookie('token', res.token);
// unshowpop();
// getSessionList();
// } else {
// showToast(res.msg + "denglucuowu");
// window.location.reload();
// return;
// }
// }).catch(err => {
// // 捕获异步异常(如网络错误、接口报错、代码报错)
// console.error("请求异常:", err);
// showToast("登录失败,请重试" + err); // 给用户友好提示
// })
// .finally(() => {
// unshowpop();
// if (document.querySelector('.popbox .loading-box')) {
// document.querySelector('.popbox .loading-box').classList.add('none');
// }
// });
// } catch (err) {
// console.error("请求异常:", err);
// }
}
async function retryFetch(url, options, maxRetry = 10) {
let lastError;
for (let i = 0; i < maxRetry; i++) {
try {
console.log(`登录请求第 ${i + 1} 次尝试...`);
const response = await fetch(url, options);
return await response.json();
} catch (err) {
lastError = err;
console.warn(`第 ${i + 1} 次尝试失败:`, err);
// 最后一次重试失败 → 抛出错误
if (i === maxRetry - 1) throw err;
// 重试前延迟 300ms
await new Promise(res => setTimeout(res, 1000));
}
}
throw lastError;
}
function getUserByOpenId(id) {
const data = {
sourceType: 5,
openId: id,
phonenumber: '',
smsCode: '',
}
retryFetch("https://serviceri.satabot.com/login", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify(data)
})
.then(res => {
if (res.code == 300) {
showpop(); // 弹出验证码
} else if (res.code == 200) {
setCookie('token', res.token);
userId = res.userId;
}
})
.catch(err => {
console.error("登录请求失败(重试10次后):", err);
showToast("网络异常,请稍后再试");
})
.finally(() => {
console.log("登录请求结束");
});
// fetch("https://ai.satabot.com/api/remote/login", {
// method: "POST", // 必选:指定请求方法为 POST
// headers: {
// "Content-Type": "application/json", // 声明请求体是 JSON 格式
// // 可选:如果接口需要 Token 前置验证,可添加 Authorization 头
// // "Authorization": "Bearer 你的前置 Token"
// },
// // 必选:请求体,将参数对象转为 JSON 字符串(不能直接传对象)
// body: JSON.stringify(data)
// }).then(res => res.json()).then(res => {
// if (res.code == 300) {
// showpop();//出现验证码弹窗
// } else if (res.code == 200) {
// setCookie('token', res.token);
// userId = res.userId;
// }
// }).catch(err => {
// console.error("请求失败:", err);
// showToast("网络异常,请稍后再试" + err);
// })
// .finally(() => {
// console.log("登录请求结束");
// });
// try {
// handleLogin(data).then(res => {
// if (res.code == 300) {
// showpop();//出现验证码弹窗
// } else if (res.code == 200) {
// setCookie('token', res.token);
// userId = res.userId;
// }
// })
// retryFetch("https://serviceai.satabot.com/getuserByopenid", {
// method: "POST",
// headers: {
// "Content-Type": "application/json"
// },
// body: JSON.stringify({
// userId: id,
// })
// }).then(data => {
// if (data.code == 0) {
// userId = data.userId;
// sendParam.userId = userId;
// getSessionList();
// } else {
// console.error("未找到该用户:",);
// }
// })
// } catch (err) {
// console.error("请求异常:", err);
// }
}
const questions = document.querySelectorAll(".question");
// 获取到用户名称
function getUserName(userId) {
if (from == "weixin") {
document.querySelectorAll('.username').forEach((e) => {
e.textContent = userId;
})
} else {
try {
fetch("https://serviceai.satabot.com/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 {
window.location.href = data.error;
return;
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;//s是否预设题目
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("https://serviceai.satabot.com/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, type = '') {
// 创建元素
const toast = document.createElement("div");
toast.className = `toast${type == "success" ? " success" : ""}`;
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) => {
console.log(toggleButton.contains(e.target), panel.contains(e.target))
if (!toggleButton.contains(e.target) && !panel.contains(e.target)) {
panel.classList.remove('show');
}
if (!sidebar.contains(e.target)) {
if (!sidebar.classList.contains("collapsed")) {
sidebar.classList.add("collapsed");
toggleBtn.style.left = "10px"; // 收起后
}
}
});
// 阻止点击面板本身关闭
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 setcookie(name, value) {
const d = new Date();
d.setFullYear(d.getFullYear() + 100); // +100 年
document.cookie = `${name}=${value};expires=${d.toUTCString()};path=/`;
}
function getcookie(name) {
const cookies = document.cookie.split("; ");
for (let c of cookies) {
const [key, val] = c.split("=");
if (key === name) return val;
}
return null;
}
function showPop(userId) {
if (!userId) {
console.error("userId 缺失,无法记录点击次数");
return;
}
const cookieName = `popupClickCount_${userId}`;
let clickCount = parseInt(getcookie(cookieName) || "0", 10);
clickCount++;
setcookie(cookieName, clickCount, 7);
console.log(`用户 ${userId} 当前点击次数:`, clickCount);
if (clickCount == 10) {
document.querySelector(".popbgc").classList.remove("none");
}
}
// 内容 角色 图片 创造时间
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");
if (controller) {
showToast("请先停止对话");
return;
}
isStopped = false;
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 (!sendBtnImg.classList.contains('none')) {
sendBtnImg.classList.add('none');
stopBtn.classList.remove('none')
}
getAiResponse(imgUrl);
}
let reader;
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);
}, 200);
msgDiv.className = "message assistant thinking ";
chatMessages.appendChild(msgDiv);
chatMessages.scrollTop = chatMessages.scrollHeight;
sessId = sendParam.sessionId;
sendParam.imageBase64 = img;
// sendParam.userId= encodedUserId;
if (from != "weixin") {
sendParam.userId = decodeURIComponent(Number(escape(atob(encodedUserId))));
} else {
sendParam.userId = userId;
}
// ✅ 创建 AbortController
controller = new AbortController();
sendParam.requestId = crypto.randomUUID();
const res = await fetch("https://serviceai.satabot.com/analyze", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(sendParam),
signal: controller.signal, // ✅ 加上 signal
}).catch(err => {
console.log("✅ 流被中断:", err.name);
clearInterval(interval);
controller.abort();
controller = null;
const sendBtnImg = document.querySelector('#sendBtn img');
const stopBtn = document.querySelector('#stopBtn');
sendBtnImg.classList.remove('none');
stopBtn.classList.add('none');
return null;
});
if (!res) {
const sendBtnImg = document.querySelector('#sendBtn img');
const stopBtn = document.querySelector('#stopBtn');
if (sendBtnImg.classList.contains("none")) {
sendBtnImg.classList.remove('none');
}
if (!stopBtn.classList.contains("none")) {
stopBtn.classList.add('none');
}
return null;
}
sendParam.imageBase64 = '';
reader = res.body.getReader();
const decoder = new TextDecoder("utf-8");
let buffer = "";
msgDiv.classList.remove("thinking");
showPop(userId);
clearInterval(interval);
console.log(isStopped, '是否点击停止')
while (true) {
console.log("回答")
let result;
try {
result = await reader.read();
} catch (e) {
console.log("流已中断:", e);
break; // ← 必须退出循环
}
const { done, value } = result;
if (done) {
controller = null;
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();
if (data == "[DONE]") {
console.log("完成回答");
controller = null;
// 更新聊天滚动条
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, 1)); // 控制字速
if (isStopped) return;
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');
}
let countTimer;
function getSms() {
document.getElementById('send-code').textContent = "60s";
let remainSeconds = 60;
countTimer = setInterval(() => {
if (remainSeconds > 0) {
remainSeconds--;
document.getElementById('send-code').textContent = `${remainSeconds}s`;
document.getElementById('send-code').disabled = true;
} else {
clearInterval(countTimer);
countTimer = null;
document.getElementById('send-code').textContent = "获取验证码";
document.getElementById('send-code').disabled = false;
}
}, 1000)
const phonenumber = document.getElementById('sys-phone').value;
const bindParams = {
phoneNumbers: phonenumber,
}
retryFetch("https://serviceri.satabot.com/external/sms/sendAliSmsCode", {
method: "POST", // 必选:指定请求方法为 POST
headers: {
"Content-Type": "application/json", // 声明请求体是 JSON 格式
// 可选:如果接口需要 Token 前置验证,可添加 Authorization 头
// "Authorization": "Bearer 你的前置 Token"
},
// 必选:请求体,将参数对象转为 JSON 字符串(不能直接传对象)
body: JSON.stringify(bindParams)
}).then(res => {
showToast("验证码发送成功");
if (res.code == 200) {
// showToast("验证码发送成功");
} else {
// showToast(res.msg + "yanzssahnegma");
// clearInterval(countTimer);
// countTimer = null;
// document.getElementById('send-code').textContent = "获取验证码";
// document.getElementById('send-code').disabled = false;
// return;
}
console.log(res);
}).catch(err => {
showToast(err + "验证码发送失败");
clearInterval(countTimer);
countTimer = null;
document.getElementById('send-code').textContent = "获取验证码";
document.getElementById('send-code').disabled = false;
return;
})
// handleBind(phonenumber).then(res => {
// }).catch(err => {
// showToast(err + "yanzhnegma");
// clearInterval(countTimer);
// countTimer = null;
// document.getElementById('send-code').textContent = "获取验证码";
// document.getElementById('send-code').disabled = false;
// return;
// })
}
function unshowpop() {
if (countTimer) {
clearInterval(countTimer);
}
const popbox = document.querySelector('.popbox');
if (popbox) {
popbox.remove();
}
}
function showpop() {
let html = `<div class='popbox'>
<div class="form sms-form" id="sms-form">
<div class="loading-box none">
<div class="loading"></div>
</div>
<h2 style='margin:0px'>欢迎使用光伏运维智能体</h2>
<p style='color:#666;margin:13px 0px;'>请先绑定手机号</p>
<div class="input-group">
<div class="area-select" onclick="showAreaSelect()">
<span id="areaText">中国大陆 +86</span>
</div>
<input id="sys-phone" type="text" placeholder="请输入手机号">
</div>
<!-- 验证码登录 -->
<div id="codeLogin" class="input-group">
<input id="code" type="text" placeholder="请输入验证码">
<button class="code-btn" id="send-code" onclick="getSms()">获取验证码</button>
</div>
<div class="form-group">
<button type="button" class="button button-primary" id="sms-button" onclick="bindToLogin()">
<!-- <i class="fas fa-user-plus mr-2"></i> -->
绑定
</button>
</div>
</div></div>`;
document.body.insertAdjacentHTML('beforeend', html);
}
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(`https://serviceai.satabot.com/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(`https://serviceai.satabot.com/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();
if (controller) return;
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;
}
// loginWithWeChat();
// function loginWithWeChat() {
// const appId = "wx364b8ebf17fe45fe";
// const appSecret = "9b7476020991a2c71f1416da7f011d31";
// const redirectUri = encodeURIComponent("https://www.example.com/wechat/callback");
// const state = "login_state";
// const scope = "snsapi_userinfo";
// const url = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${appId}&secret=${appSecret}&redirect_uri=${redirectUri}&response_type=code&scope=${scope}&state=${state}#wechat_redirect`;
// window.location.href = url; // 跳转到微信授权页面
// }
// simulateWechatLogin()
// 触发登录时执行:跳转微信授权页
function simulateWechatLogin() {
const APP_ID = 'wx0eeb7556d60121db';
const REDIRECT_URI = encodeURIComponent("https://www.satabot.com");
const STATE = Math.random().toString(36).substring(2, 12);
const SCOPE = 'snsapi_base';
// const authUrl = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=wx807d86fb6b3d4fd2&redirect_uri=http%3A%2F%2Fdevelopers.weixin.qq.com&response_type=code&scope=snsapi_userinfo&state=STATE&forcePopup=true#wechat_redirect";
const authUrl = `https://open.weixin.qq.com/connect/oauth2/authorize?appid=${APP_ID}&redirect_uri=${REDIRECT_URI}&response_type=code&scope=${SCOPE}&state=${STATE}&forcePopup=true#wechat_redirect`;
// window.location.href = authUrl; // 跳转后页面刷新,后续代码不执行
}
// if(from=="weixin"){
// gotoWeiXin();
// }else{
// }
// if (!isWeChat()) {
// } else {
// gotoWeiXin();
// }
// let isSuccess = false;
// function getSessionList() {
// console.log(userId, "huoqu")
// sendParam.userId = userId;
// fetch(`/api/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 => {
// debugger;
// console.log(data, 8777, data.code)
// if (data.code == 300) {
// if (!isSuccess) {
// window.location.href = data.error;
// isSuccess = true;
// } else {
// window.location.href = "https://ai.satabot.com/404.html";
// return;
// }
// // window.location.href='http://localhost/#/login?goRemote=1 '
// return;
// } else {
// sessIdList = data.sessions;
// renderChatList();
// console.log(data, 8777)
// }
// // chats = data.messages;
// // renderChatList();
// // renderMessages(data.messages)
// })
// }
function getSessionList() {
console.log(userId, "huoqu");
sendParam.userId = userId;
fetch(`https://serviceai.satabot.com/sessions?userId=${sendParam.userId}`, {
method: "GET",
// 确保 credentials 正确设置,以便传递 Cookie
// credentials: 'include',
})
.then(res => {
// debugger;
// 首先检查 HTTP 响应状态码
if (res.status === 401) {
// 401 表示未授权,用户需要登录
// 从后端响应头中获取登录页地址(后端需设置 Location 头)
const loginUrl = res.headers.get('Location') || '/login'; // 降级默认地址
window.location.href = loginUrl; // 跳转到后端指定的登录页
return Promise.reject('用户未授权');
}
else if (!res.ok) {
// 处理其他 HTTP 错误,如 404, 500 等
console.error('请求失败,状态码:', res.status);
// 可以在这里统一跳转到 404 页面
// window.location.href = "https://ai.satabot.com/404.html";
return Promise.reject(`HTTP error! status: ${res.status}`);
}
// 如果状态码是 200 OK,则继续解析 JSON
return res.json();
})
.then(data => {
// 请求成功,渲染页面
console.log('获取会话列表成功');
sessIdList = data.sessions;
renderChatList();
})
.catch(error => {
// 捕获网络错误或我们手动抛出的错误
console.error('获取会话列表时发生错误:', error);
// 这里可以不做任何事,因为特定的错误已经被处理了
});
}
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');
console.log(controller, '暂停消息');
isStopped = true;
if (reader) {
reader.cancel("stop");
reader = null;
}
if (controller) {
controller.abort();
controller = null;
if (sendBtnImg.classList.contains('none')) {
sendBtnImg.classList.remove('none');
stopBtn.classList.add('none')
}
}
});
// DOM 加载完成后执行
console.log('DOM Ready');
// });
</script>
<script>
// 1. 分别获取「场景选项」和「需求选项」(通过父容器ID精准筛选,避免混淆)
const sceneOptions = document.querySelectorAll('#scenes .option'); // 场景类选项(父容器#scenes)
const needOptions = document.querySelectorAll('#needs .option'); // 需求类选项(父容器#needs)
// 2. 创建两个隐藏输入框,分别存「场景」和「需求」数据
const scenesInput = document.createElement('input');
scenesInput.type = 'hidden';
scenesInput.name = 'scenes'; // 后端接收「场景」的字段名
const needsInput = document.createElement('input');
needsInput.type = 'hidden';
needsInput.name = 'needs'; // 后端接收「需求」的字段名
// 3. 把两个隐藏框添加到表单中
const form = document.getElementById('popupForm');
form.appendChild(scenesInput);
form.appendChild(needsInput);
// 4. 场景选项的点击事件:收集选中的场景
sceneOptions.forEach(opt => {
opt.addEventListener('click', () => {
opt.classList.toggle('selected'); // 切换选中样式
// 筛选选中的场景,提取文本并拼接成字符串(如:"山地光伏,农光互补电站")
const selectedScenes = Array.from(sceneOptions)
.filter(o => o.classList.contains('selected'))
.map(o => o.textContent);
scenesInput.value = selectedScenes.join(','); // 同步到场景隐藏框
});
});
// 5. 需求选项的点击事件:收集选中的需求
needOptions.forEach(opt => {
opt.addEventListener('click', () => {
opt.classList.toggle('selected'); // 切换选中样式
// 筛选选中的需求,提取文本并拼接成字符串(如:"杂草清理,热斑检测")
const selectedNeeds = Array.from(needOptions)
.filter(o => o.classList.contains('selected'))
.map(o => o.textContent);
needsInput.value = selectedNeeds.join(','); // 同步到需求隐藏框
});
});
// 6. 表单提交:异步发送所有数据(公司名 + 场景 + 需求)
form.addEventListener('submit', async (e) => {
e.preventDefault(); // 阻止表单默认刷新提交
const formData = new FormData(e.target); // 自动收集表单所有数据
const data = { ...Object.fromEntries(formData.entries()), userId: userId }; // 转成普通对象
if (!data.needs) {
showToast("请选择需求");
return;
}
if (!data.scenes) {
showToast("请选择场景");
return;
}
console.log(data, "收集信息")
// 发送POST请求到后端
const res = await fetch('https://serviceai.satabot.com/submit', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(data) // 数据格式:{ company: "xxx", scenes: "xxx,xxx", needs: "xxx,xxx" }
});
const result = await res.json();
if (result.code == 200) {
document.querySelector('.popbgc').classList.add('none');
showToast("提交成功", 'success')
}
});
</script>
</body>
</html>