<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<title>云智计算 - ONES模型在线对话</title>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/styles/atom-one-dark.min.css">
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/highlight.min.js"></script>
<style>
:root {
--primary-color: #3b82f6;
--primary-light: rgba(59, 130, 246, 0.1);
--user-bubble: #e3f2fd;
--btn-gradient: linear-gradient(135deg, #6da6ff 0%, #3b82f6 100%);
--stop-gradient: linear-gradient(135deg, #ff6b6b 0%, #ef4444 100%);
--bg-gradient: linear-gradient(135deg, #f8fafc 0%, #e2e8f0 100%);
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.12);
--shadow-md: 0 4px 6px -1px rgba(0, 0, 0, 0.1);
--code-bg: #282c34;
}
body, html {
height: 100%;
margin: 0;
font-family: 'Helvetica Neue', Arial, 'PingFang SC', 'Microsoft YaHei', sans-serif;
background: var(--bg-gradient);
display: flex;
flex-direction: column;
}
.title-bar {
width: 100%;
max-width: 1200px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.95);
backdrop-filter: blur(5px);
box-shadow: var(--shadow-md);
padding: 12px 20px;
display: flex;
align-items: center;
gap: 15px;
border-radius: 16px 16px 0 0;
box-sizing: border-box;
border-bottom: 2px solid rgba(0, 0, 0, 0.1);
}
.title-bar h1 {
flex: 1;
text-align: center;
margin: 0;
font-size: 18px;
font-weight: 600;
color: #1e293b;
letter-spacing: 0.5px;
}
.title-bar button {
background: none;
border: none;
min-width: 40px;
height: 40px;
border-radius: 50%;
padding: 8px;
transition: all 0.2s ease;
flex-shrink: 0;
color: #1e293b;
font-size: 14px;
font-weight: 600;
letter-spacing: 0.5px;
}
.title-bar button:hover {
background: rgba(0, 0, 0, 0.05);
}
.chat-container {
flex: 1;
width: 100%;
max-width: 1200px;
margin: 0 auto;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(5px);
box-shadow: var(--shadow-md);
border-radius: 0 0 16px 16px;
overflow: hidden;
display: flex;
flex-direction: column;
box-sizing: border-box;
position: relative;
}
.mode-selector {
display: flex;
gap: 8px;
padding: 8px 16px;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(10px);
border-bottom: 1px solid #f1f5f9;
z-index: 10;
overflow-x: auto;
white-space: nowrap;
scrollbar-width: none;
-ms-overflow-style: none;
justify-content: center;
}
.mode-selector::-webkit-scrollbar {
display: none;
}
.mode-btn {
padding: 8px 16px;
border: 1px solid #e2e8f0;
border-radius: 24px;
background: rgba(255, 255, 255, 0.7);
color: #64748b;
font-size: 14px;
cursor: pointer;
transition: all 0.2s;
display: flex;
align-items: center;
justify-content: center;
gap: 6px;
white-space: nowrap;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
flex-shrink: 0;
}
.mode-btn:hover {
background: rgba(255, 255, 255, 0.9);
transform: translateY(-1px);
}
.mode-btn.active {
background: var(--primary-color);
color: white;
border-color: var(--primary-color);
}
.mode-btn:active {
transform: scale(0.98);
}
.chat-input-container {
padding: 8px 16px;
background: white;
border-top: 1px solid #f1f5f9;
display: flex;
gap: 12px;
align-items: flex-end;
position: relative;
}
.input-wrapper {
flex: 1;
position: relative;
display: flex;
align-items: center;
}
#user-input {
flex: 1;
padding: 10px 14px 10px 40px;
border: 1px solid #e2e8f0;
border-radius: 24px;
font-size: 14px;
transition: all 0.2s ease;
min-height: 40px;
max-height: 200px;
resize: none;
white-space: pre-wrap;
box-sizing: border-box;
line-height: 1.5;
}
#user-input:focus, #user-input:not(:placeholder-shown) {
padding-left: 14px;
}
#user-input:focus + .ocr-btn, #user-input:not(:placeholder-shown) + .ocr-btn {
display: none;
}
.ocr-btn {
position: absolute;
left: 10px;
background: none;
border: none;
cursor: pointer;
opacity: 0.5;
transition: opacity 0.2s;
z-index: 2;
width: 30px;
height: 30px;
}
.ocr-btn:hover {
opacity: 0.8;
}
.ocr-btn img {
width: 100%;
height: 100%;
pointer-events: none;
}
.submit-btn, .stop-btn {
padding: 10px 20px;
border: none;
border-radius: 24px;
color: white;
font-weight: 500;
cursor: pointer;
transition: all 0.2s ease;
height: 40px;
box-sizing: border-box;
font-size: 14px;
flex-shrink: 0;
}
.submit-btn {
background: var(--btn-gradient);
}
.stop-btn {
background: var(--stop-gradient);
display: none;
}
.stop-btn:hover {
opacity: 0.9;
}
.chat-message {
display: flex;
gap: 12px;
margin-bottom: 16px;
animation: fadeIn 0.3s ease;
position: relative;
}
.user-message {
justify-content: flex-end;
}
.user-message .avatar {
order: 1;
}
.user-message .message-content {
order: 0;
border-radius: 18px 18px 4px 18px !important;
background: var(--user-bubble) !important;
color: #2d3748 !important;
}
.avatar {
width: 40px;
height: 40px;
border-radius: 50%;
object-fit: cover;
box-shadow: var(--shadow-sm);
border: 2px solid white;
}
.message-content {
max-width: 70%;
padding: 12px 16px;
border-radius: 18px;
line-height: 1.6;
word-break: break-word;
transition: transform 0.2s ease;
position: relative;
}
.ai-message .message-content {
background: white;
color: #1e293b;
border: 1px solid #e2e8f0;
border-radius: 8px 18px 18px 18px;
box-shadow: var(--shadow-sm);
}
.copy-content {
padding-bottom: 4px;
}
.message-footer {
display: flex;
justify-content: space-between;
align-items: center;
padding: 4px 8px;
border-top: 1px solid #e2e8f0;
margin-top: 4px;
background: white;
height: 24px;
}
.message-time {
font-size: 11px;
color: #64748b;
}
.action-buttons {
display: flex;
gap: 4px;
}
.action-button {
font-size: 12px;
padding: 2px 6px;
background: rgba(0, 0, 0, 0.05);
border: none;
border-radius: 4px;
cursor: pointer;
transition: all 0.2s;
height: 20px;
line-height: 16px;
}
.action-button:hover {
background: rgba(0, 0, 0, 0.1);
}
.chat-messages {
flex: 1;
padding: 16px;
overflow-y: auto;
scroll-behavior: smooth;
}
.code-block {
margin: 10px 0;
position: relative;
border-radius: 8px;
overflow: hidden;
background: var(--code-bg);
color: #abb2bf;
}
.code-header {
display: flex;
justify-content: space-between;
align-items: center;
background: #21252b;
color: #abb2bf;
padding: 8px 12px;
font-size: 13px;
border-bottom: 1px solid #181a1f;
}
.code-copy-btn {
background: #31363f;
border: none;
border-radius: 4px;
color: #abb2bf;
padding: 4px 8px;
font-size: 12px;
cursor: pointer;
transition: all 0.2s;
}
.code-copy-btn:hover {
background: #3e4451;
color: white;
}
.code-content {
padding: 12px;
overflow-x: auto;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 14px;
line-height: 1.5;
background: var(--code-bg);
}
.code-content pre {
margin: 0;
padding: 0;
white-space: pre;
}
.generated-image {
max-width: 100%;
height: auto;
border-radius: 8px;
margin: 8px 0;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: block;
}
.image-container {
text-align: center;
margin: 12px 0;
}
.image-prompt {
font-size: 12px;
color: #64748b;
margin-top: 4px;
font-style: italic;
}
@keyframes fadeIn {
from {
opacity: 0;
transform: translateY(10px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.thinking-indicator {
color: #64748b;
font-style: italic;
padding: 8px 16px;
border-radius: 8px;
background: #f1f5f9;
display: flex;
align-items: center;
gap: 8px;
animation: pulse 1.5s infinite;
margin: 8px 0;
}
@keyframes pulse {
0% {
opacity: 0.6;
}
50% {
opacity: 1;
}
100% {
opacity: 0.6;
}
}
.loader {
border: 2px solid #64748b;
border-top: 2px solid transparent;
border-radius: 50%;
width: 16px;
height: 16px;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% {
transform: rotate(0deg);
}
100% {
transform: rotate(360deg);
}
}
.scroll-to-bottom {
position: fixed;
bottom: 120px;
right: 20px;
width: 40px;
height: 40px;
border-radius: 50%;
background: rgba(255, 255, 255, 0.9);
backdrop-filter: blur(5px);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
z-index: 100;
opacity: 0;
transition: opacity 0.3s;
}
.scroll-to-bottom.visible {
opacity: 1;
}
.scroll-to-bottom svg {
width: 20px;
height: 20px;
fill: #64748b;
}
.modal-overlay {
position: fixed;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: rgba(0, 0, 0, 0.5);
display: flex;
justify-content: center;
align-items: center;
z-index: 1000;
backdrop-filter: blur(5px);
animation: fadeIn 0.3s ease;
}
.modal {
background: white;
border-radius: 12px;
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.2);
max-width: 90%;
width: 500px;
max-height: 80vh;
overflow-y: auto;
position: relative;
}
.modal-header {
padding: 16px 20px;
border-bottom: 1px solid #e2e8f0;
display: flex;
justify-content: space-between;
align-items: center;
}
.modal-title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #1e293b;
}
.modal-body {
padding: 20px;
line-height: 1.6;
color: #334155;
}
.modal-footer {
padding: 16px 20px;
border-top: 1px solid #e2e8f0;
display: flex;
justify-content: center;
gap: 12px;
}
.modal-btn {
padding: 10px 20px;
border-radius: 6px;
font-weight: 500;
cursor: pointer;
transition: all 0.2s;
border: none;
}
.modal-confirm {
background: #3b82f6;
color: white;
}
.modal-confirm:hover {
background: #2563eb;
}
.modal-cancel {
background: #f1f5f9;
color: #64748b;
}
.modal-cancel:hover {
background: #e2e8f0;
}
.ocr-progress {
margin-top: 16px;
display: flex;
flex-direction: column;
align-items: center;
gap: 12px;
}
.ocr-progress-bar {
width: 100%;
height: 6px;
background: #f1f5f9;
border-radius: 3px;
overflow: hidden;
}
.ocr-progress-bar-fill {
height: 100%;
background: #3b82f6;
width: 0%;
transition: width 0.3s ease;
}
.ocr-file-input {
display: none;
}
.force-modal {
background: rgba(0, 0, 0, 0.7);
}
.force-modal .modal {
box-shadow: 0 10px 25px rgba(0, 0, 0, 0.3);
}
@media (max-width: 768px) {
.title-bar {
display: none;
}
.chat-container {
width: 100%;
max-width: 100%;
margin: 0;
border-radius: 0;
}
.message-content {
max-width: 78% !important;
}
.mode-selector {
padding: 8px 12px !important;
justify-content: flex-start;
}
.mode-btn {
padding: 6px 12px !important;
font-size: 13px !important;
}
#user-input {
padding: 10px 14px !important;
font-size: 14px !important;
}
.chat-input-container {
padding: 12px 16px !important;
gap: 8px !important;
}
.submit-btn, .stop-btn {
padding: 10px 16px !important;
font-size: 14px !important;
}
.mode-btn.active {
background: var(--primary-color) !important;
color: white !important;
border-color: var(--primary-color) !important;
}
.ocr-btn {
left: 10px;
bottom: 8px;
}
#user-input {
padding-left: 40px !important;
}
#user-input:focus, #user-input:not(:placeholder-shown) {
padding-left: 14px !important;
}
.message-time {
display: block;
margin-right: auto;
}
.action-buttons {
gap: 4px;
margin-left: auto;
}
.action-button {
padding: 2px 6px;
}
.modal {
width: 90%;
max-height: 85vh;
}
.modal-body {
padding: 16px;
font-size: 14px;
}
.modal-footer {
padding: 12px 16px;
}
.modal-btn {
padding: 8px 12px;
font-size: 14px;
}
}
</style>
</head>
<body>
<!-- 强制公告弹窗 -->
<div id="force-disclaimer-modal" class="modal-overlay force-modal" style="display: none;">
<div class="modal">
<div class="modal-header">
<h3 class="modal-title">【必读】用户使用协议</h3>
</div>
<div class="modal-body" id="force-disclaimer-content">
<p>1. 本平台提供的所有内容仅供参考和学习使用,回答内容均由 AI 生成,请仔细甄别。</p>
<p>2. 用户在使用本平台服务时,应自行承担所有风险,包括但不限于数据丢失、信息泄露等。</p>
<p>3. 本平台不对用户生成的内容负责,用户应确保其内容的合法性和合规性。</p>
<p>4. 禁止使用本平台进行任何违法、违规或侵犯他人权益的行为。</p>
<p>5. 我们保留随时修改或终止服务的权利,恕不另行通知。</p>
<p>6. 继续使用本服务即表示您已阅读并同意以上条款。</p>
</div>
<div class="modal-footer">
<button class="modal-btn modal-confirm" id="force-disclaimer-confirm">已知晓并遵守(每小时弹出)</button>
</div>
</div>
</div>
<!-- 免责声明弹窗 -->
<div id="disclaimer-modal" class="modal-overlay" style="display: none;" onclick="closeModal('disclaimer-modal')">
<div class="modal" onclick="event.stopPropagation()">
<div class="modal-header">
<h3 class="modal-title">【必读】用户使用协议</h3>
</div>
<div class="modal-body" id="disclaimer-content">
<p>1. 本平台提供的所有内容仅供参考和学习使用,回答内容均由 AI 生成,请仔细甄别。</p>
<p>2. 根据《湖南省生成式人工智能专项通知》规定,本站原则上只提供在校大学生论文优化等服务,后续毕业将会终止开发和运行此类项目,维护和运营期间将会尽可能避免出现舆论属性和社会动员能力,本站不会保存您的隐私信息。</p>
<p>3. 禁止向AI模型提问任何违法、违规或侵犯他人权益的行为和方法,本站已接入OpanAI和讯飞星火的文本内容审核。一经发现相关内容,本站有权对您的IP进行封禁和拉黑处理。</p>
<p>4. 云智计算保留对本站随时修改或终止服务的权利,运营期间不会存在任何的资金来往。</p>
<p>继续使用即代表您已阅读并同意上述协议⬆</p>
</div>
<div class="modal-footer">
<span class="close-hint">点击任意处关闭</span>
</div>
</div>
</div>
<!-- OCR上传弹窗 -->
<div id="ocr-upload-modal" class="modal-overlay" style="display: none;" onclick="closeModal('ocr-upload-modal')">
<div class="modal" onclick="event.stopPropagation()">
<div class="modal-header">
<h3 class="modal-title">上传图片【内测版】</h3>
</div>
<div class="modal-body">
<div id="ocr-upload-initial">
<p style="font-size: 17px;">请选择有效的图片进行识别,获取内容将会直接显示在输入框内(5MB)</p>
<div class="ocr-progress">
<button class="modal-btn modal-confirm" onclick="document.getElementById('ocr-file-input').click()">选择图片(相册/截屏)</button>
<input type="file" id="ocr-file-input" class="ocr-file-input" accept="image/*">
</div>
</div>
<div id="ocr-upload-progress" style="display: none;">
<div class="ocr-progress">
<div class="loader"></div>
<div class="ocr-progress-text" id="ocr-progress-text">正在上传图片...</div>
<div class="ocr-progress-bar">
<div class="ocr-progress-bar-fill" id="ocr-progress-bar"></div>
</div>
</div>
</div>
</div>
<div class="modal-footer">
<button class="modal-btn modal-cancel" onclick="closeModal('ocr-upload-modal')">取消</button>
</div>
</div>
</div>
<div class="title-bar">
<button onclick="window.location.href='https://api.jkyai.top/?action=doc&id=77'">🤖 对接文档</button>
<h1>我是 云智ONES,很高兴见到你!</h1>
<button onclick="showModal('disclaimer-modal')">📜 用户协议</button>
</div>
<div class="chat-container">
<div class="chat-messages" id="chat-messages"></div>
<div class="mode-selector">
<button class="mode-btn active" id="search-mode" onclick="setMode('search')">
<span class="mode-icon">🔍</span>
<span class="mode-text">联网搜索</span>
</button>
<button class="mode-btn" id="image-mode" onclick="setMode('image')">
<span class="mode-icon">🎨</span>
<span class="mode-text">图像生成</span>
</button>
<button class="mode-btn" id="writing-mode" onclick="setMode('writing')">
<span class="mode-icon">✍️</span>
<span class="mode-text">帮我写作</span>
</button>
<button class="mode-btn" id="answer-mode" onclick="setMode('answer')">
<span class="mode-icon">📚</span>
<span class="mode-text">搜题解答</span>
</button>
</div>
<div class="chat-input-container">
<div class="input-wrapper">
<button class="ocr-btn" id="ocr-btn" title="OCR图像识别" onclick="showModal('ocr-upload-modal')">
<img src="data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgc3Ryb2tlPSIjNjQ3NDhCIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgY2xhc3M9Imx1Y2lkZSBsdWNpZGUtY2FtZXJhIj48cGF0aCBkPSJNMTQgOEgxNmEyIDIgMCAwIDEgMiAydjhhMiAyIDAgMCAxLTIgMkg4YTIgMiAwIDAgMS0yLTJWOGEyIDIgMCAwIDEgMi0yaDIiLz48Y2lyY2xlIGN4PSIxMiIgY3k9IjEzIiByPSIzIi8+PHBhdGggZD0iTTQgMTVMMyA2YTIgMiAwIDAgMSAyLTJoMiIvPjwvc3ZnPg==" alt="OCR识别">
</button>
<textarea id="user-input" placeholder="请输入您的问题或需求" rows="1"></textarea>
</div>
<input type="submit" class="submit-btn" id="submit-btn" value="发送" onclick="sendMessage()">
<input type="button" class="stop-btn" id="stop-btn" value="停止" onclick="stopAIResponse()">
</div>
</div>
<div class="scroll-to-bottom" id="scroll-to-bottom" onclick="scrollToBottom()">
<svg viewBox="0 0 24 24">
<path d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"></path>
</svg>
</div>
<script>
// 常量定义
const MODES = {
search: { icon: '🔍', text: '联网搜索', placeholder: '请输入您的问题或需求' },
image: { icon: '🎨', text: '图像生成', placeholder: '请输入您的绘画提示词' },
writing: { icon: '✍️', text: '帮我写作', placeholder: '请输入详细的写作要求' },
answer: { icon: '📚', text: '搜题解答', placeholder: '请输入需要解答的问题' }
};
const API_ENDPOINTS = {
search: 'https://api.jkyai.top/API/AI/api.php?question={query}&type=text&key=000000&search=yes&uid=123456',
image: 'https://api.jkyai.top/API/AI/draw.php?prompt={query}',
writing: 'https://api.jkyai.top/API/rsxzzs.php?question={query}',
answer: 'https://api.jkyai.top/API/wnjtzs.php?question={query}&type=text'
};
const DISCLAIMER_CONTENT = `
<p>1. 本平台提供的所有内容仅供参考和学习使用,回答内容均由AI模型生成,请仔细甄别。</p>
<p>2. 根据《湖南省生成式人工智能专项通知》规定,本站原则上只提供在校大学生论文优化等服务,后续毕业将会终止开发和运行此类项目,维护和运营期间将会尽可能避免出现舆论属性和社会动员能力,本站不会保存您的隐私信息。</p>
<p>3. 禁止向AI模型提问任何违法、违规或侵犯他人权益的行为和方法,本站已接入OpanAI和讯飞星火的文本内容审核。一经发现相关内容,本站有权对您的IP进行封禁和拉黑处理。</p>
<p>4. 云智计算保留对本站随时修改或终止服务的权利,运营期间不会存在任何的资金来往。</p>
<p>继续使用即代表您已阅读并同意上述协议⬆</p>
`;
// DOM元素
const chatMessages = document.getElementById('chat-messages');
const submitBtn = document.getElementById('submit-btn');
const stopBtn = document.getElementById('stop-btn');
const scrollToBottomBtn = document.getElementById('scroll-to-bottom');
const userInput = document.getElementById('user-input');
const ocrBtn = document.getElementById('ocr-btn');
const ocrFileInput = document.getElementById('ocr-file-input');
// 全局变量
let aiAvatar = 'https://api.jkyai.top/chat/chat.jpg';
let userAvatar = 'https://api.jkyai.top/chat/user.jpg';
let typingEffectActive = false;
let controller = new AbortController();
let conversationHistory = [];
let thinkingInterval = null;
let thinkingDiv = null;
let startTime = 0;
let currentMode = 'search';
// 初始化函数
function init() {
setupEventListeners();
loadHighlightJs();
showWelcomeMessage();
checkForceDisclaimer();
}
// 事件监听设置
function setupEventListeners() {
// 输入框事件
userInput.addEventListener('input', handleInputChange);
userInput.addEventListener('keydown', handleKeyDown);
userInput.addEventListener('focus', () => ocrBtn.style.display = 'none');
userInput.addEventListener('blur', () => {
if (!userInput.value.trim()) ocrBtn.style.display = 'block';
});
// 聊天区域滚动事件
chatMessages.addEventListener('scroll', handleChatScroll);
// 强制公告确认按钮
document.getElementById('force-disclaimer-confirm').addEventListener('click', () => {
closeModal('force-disclaimer-modal');
localStorage.setItem('lastDisclaimerAcceptTime', Date.now());
});
// OCR文件上传
ocrFileInput.addEventListener('change', handleOCRImageUpload);
}
// 模态框控制
function showModal(modalId) {
const modal = document.getElementById(modalId);
modal.style.display = 'flex';
document.body.style.overflow = 'hidden';
if (modalId === 'disclaimer-modal') {
document.getElementById('disclaimer-content').innerHTML = DISCLAIMER_CONTENT;
} else if (modalId === 'ocr-upload-modal') {
resetOCRModal();
}
}
function closeModal(modalId) {
const modal = document.getElementById(modalId);
modal.style.display = 'none';
document.body.style.overflow = '';
}
function resetOCRModal() {
document.getElementById('ocr-upload-initial').style.display = 'block';
document.getElementById('ocr-upload-progress').style.display = 'none';
document.getElementById('ocr-progress-bar').style.width = '0%';
ocrFileInput.value = '';
}
// 输入处理
function handleInputChange() {
this.style.height = 'auto';
this.style.height = (this.scrollHeight) + 'px';
ocrBtn.style.display = this.value.trim() ? 'none' : 'block';
}
function handleKeyDown(e) {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
}
// 聊天滚动处理
function handleChatScroll() {
const { scrollTop, scrollHeight, clientHeight } = chatMessages;
scrollToBottomBtn.classList.toggle('visible', scrollHeight - (scrollTop + clientHeight) > 100);
}
function scrollToBottom() {
chatMessages.scrollTop = chatMessages.scrollHeight;
}
// 模式设置
function setMode(mode) {
currentMode = mode;
document.querySelectorAll('.mode-btn').forEach(btn => btn.classList.remove('active'));
document.getElementById(`${mode}-mode`).classList.add('active');
userInput.placeholder = MODES[mode].placeholder;
}
// 消息处理
function createMessageElement(side, text, avatar) {
const messageDiv = document.createElement('div');
messageDiv.className = `chat-message ${side}-message`;
const contentDiv = document.createElement('div');
contentDiv.className = 'message-content';
const copyContentDiv = document.createElement('div');
copyContentDiv.className = 'copy-content';
copyContentDiv.innerHTML = side === 'user' ? formatUserInput(text) : text;
contentDiv.appendChild(copyContentDiv);
const avatarImg = document.createElement('img');
avatarImg.src = avatar;
avatarImg.className = 'avatar';
if (side === 'user') {
messageDiv.appendChild(contentDiv);
messageDiv.appendChild(avatarImg);
} else {
messageDiv.appendChild(avatarImg);
messageDiv.appendChild(contentDiv);
}
return messageDiv;
}
function formatUserInput(text) {
const escapedText = escapeHtml(text);
const codeBlockRegex = /```([a-zA-Z]*)\s*([\s\S]*?)```/g;
const formattedText = escapedText.replace(codeBlockRegex, (match, language, code) => {
return `<div class="user-code-block">${escapeHtml(code.trim())}</div>`;
});
return formattedText.replace(/\n/g, '<br>');
}
function appendMessage(side, text, avatar) {
const messageDiv = createMessageElement(side, text, avatar);
chatMessages.appendChild(messageDiv);
scrollToBottom();
return messageDiv;
}
function addActionButtons(messageDiv, content, isInitial = false) {
const footerDiv = document.createElement('div');
footerDiv.className = 'message-footer';
const timeSpan = document.createElement('span');
timeSpan.className = 'message-time';
const now = new Date();
timeSpan.textContent = `➣ ${now.getHours().toString().padStart(2, '0')}:${now.getMinutes().toString().padStart(2, '0')}:${now.getSeconds().toString().padStart(2, '0')}`;
const buttonsDiv = document.createElement('div');
buttonsDiv.className = 'action-buttons';
const copyBtn = document.createElement('button');
copyBtn.className = 'action-button';
copyBtn.innerHTML = '❏';
copyBtn.onclick = () => {
const textToCopy = currentMode === 'image' ? content : messageDiv.querySelector('.message-content').textContent.trim();
navigator.clipboard.writeText(textToCopy).then(() => {
copyBtn.textContent = '✓';
setTimeout(() => copyBtn.textContent = '❏', 1000);
});
};
buttonsDiv.appendChild(copyBtn);
if (!isInitial) {
const regenerateBtn = document.createElement('button');
regenerateBtn.className = 'action-button';
regenerateBtn.innerHTML = '↻';
regenerateBtn.onclick = () => regenerateResponse(conversationHistory.length - 1);
buttonsDiv.appendChild(regenerateBtn);
}
footerDiv.appendChild(timeSpan);
footerDiv.appendChild(buttonsDiv);
messageDiv.querySelector('.message-content').appendChild(footerDiv);
}
// 代码处理
function escapeHtml(unsafe) {
return unsafe
.replace(/&/g, "&")
.replace(/</g, "<")
.replace(/>/g, ">")
.replace(/"/g, """)
.replace(/'/g, "'");
}
function detectAndFormatCode(text) {
const codeBlockRegex = /```([a-zA-Z]*)\s*([\s\S]*?)```/g;
const languageMap = {
'js': 'javascript', 'py': 'python', 'java': 'java', 'c': 'c', 'cpp': 'c++',
'cs': 'csharp', 'go': 'golang', 'rb': 'ruby', 'php': 'php', 'swift': 'swift',
'kt': 'kotlin', 'rs': 'rust', 'scala': 'scala', 'pl': 'perl', 'sh': 'bash',
'bash': 'bash', 'shell': 'shell', 'sql': 'sql', 'html': 'html', 'css': 'css',
'xml': 'xml', 'json': 'json'
};
return text.replace(codeBlockRegex, (match, language, code) => {
language = (languageMap[language.trim().toLowerCase()] || 'plaintext').toLowerCase();
const displayLanguage = language.charAt(0).toUpperCase() + language.slice(1);
const escapedCode = escapeHtml(code.trim());
return `
<div class="code-block">
<div class="code-header">
<span class="code-language">${displayLanguage}</span>
<button class="code-copy-btn" onclick="copyCodeBlock(this)">复制代码</button>
</div>
<div class="code-content">
<pre><code class="hljs language-${language}">${escapedCode}</code></pre>
</div>
</div>`;
});
}
function copyCodeBlock(button) {
const code = button.closest('.code-block').querySelector('code').textContent;
navigator.clipboard.writeText(code);
button.textContent = '复制成功√';
button.style.background = '#10b981';
button.style.color = 'white';
setTimeout(() => {
button.textContent = '复制代码';
button.style.background = '';
button.style.color = '';
}, 2000);
}
// 打字效果
function typeWriterEffect(element, text, callback) {
const parts = parseTextParts(text);
element.innerHTML = '';
typingEffectActive = true;
submitBtn.style.display = 'none';
stopBtn.style.display = 'block';
let partIndex = 0;
let charIndex = 0;
function typeNextPart() {
if (partIndex >= parts.length || !typingEffectActive) {
typingEffectActive = false;
submitBtn.style.display = 'block';
stopBtn.style.display = 'none';
callback?.();
return;
}
const currentPart = parts[partIndex];
if (currentPart.type === 'text') {
typeText(currentPart.content);
} else if (currentPart.type === 'code') {
typeCode(currentPart);
}
}
function typeText(textContent) {
if (charIndex < textContent.length && typingEffectActive) {
let fullText = parts.slice(0, partIndex)
.filter(p => p.type === 'text')
.map(p => p.content)
.join('') + textContent.substring(0, charIndex + 1);
let displayHTML = fullText.replace(/\n/g, '<br>');
element.innerHTML = displayHTML;
scrollToBottom();
charIndex++;
setTimeout(typeText, 30, textContent);
} else {
partIndex++;
charIndex = 0;
typeNextPart();
}
}
function typeCode({ language, content }) {
const displayLanguage = getDisplayLanguage(language);
const codeContainer = createCodeBlock(displayLanguage);
element.appendChild(codeContainer);
function typeCodeContent() {
if (charIndex < content.length && typingEffectActive) {
codeContainer.querySelector('code').textContent = content.substring(0, charIndex + 1);
hljs.highlightElement(codeContainer.querySelector('code'));
scrollToBottom();
charIndex++;
setTimeout(typeCodeContent, 10);
} else {
partIndex++;
charIndex = 0;
typeNextPart();
}
}
typeCodeContent();
}
typeNextPart();
}
function parseTextParts(text) {
const parts = [];
const codeBlockRegex = /```([a-zA-Z]*)\s*([\s\S]*?)```/g;
let lastIndex = 0;
let match;
while ((match = codeBlockRegex.exec(text)) !== null) {
if (match.index > lastIndex) {
parts.push({ type: 'text', content: text.substring(lastIndex, match.index) });
}
parts.push({
type: 'code',
language: match[1] || 'plaintext',
content: match[2].trim()
});
lastIndex = match.index + match[0].length;
}
if (lastIndex < text.length) {
parts.push({ type: 'text', content: text.substring(lastIndex) });
}
return parts.length ? parts : [{ type: 'text', content: text }];
}
function getDisplayLanguage(language) {
const languageMap = {
'js': 'javascript', 'py': 'python', 'java': 'java', 'c': 'c', 'cpp': 'c++',
'cs': 'csharp', 'go': 'golang', 'rb': 'ruby', 'php': 'php', 'swift': 'swift',
'kt': 'kotlin', 'rs': 'rust', 'scala': 'scala', 'pl': 'perl', 'sh': 'bash',
'bash': 'bash', 'shell': 'shell', 'sql': 'sql', 'html': 'html', 'css': 'css',
'xml': 'xml', 'json': 'json'
};
return languageMap[language.toLowerCase()] || language;
}
function createCodeBlock(language) {
const codeContainer = document.createElement('div');
codeContainer.className = 'code-block';
const codeHeader = document.createElement('div');
codeHeader.className = 'code-header';
const codeLanguage = document.createElement('span');
codeLanguage.className = 'code-language';
codeLanguage.textContent = language.charAt(0).toUpperCase() + language.slice(1);
const copyButton = document.createElement('button');
copyButton.className = 'code-copy-btn';
copyButton.textContent = '复制代码';
copyButton.onclick = () => copyCodeBlock(copyButton);
codeHeader.appendChild(codeLanguage);
codeHeader.appendChild(copyButton);
const codeContent = document.createElement('div');
codeContent.className = 'code-content';
const preElement = document.createElement('pre');
const codeElement = document.createElement('code');
codeElement.className = `hljs language-${language.toLowerCase()}`;
preElement.appendChild(codeElement);
codeContent.appendChild(preElement);
codeContainer.appendChild(codeHeader);
codeContainer.appendChild(codeContent);
return codeContainer;
}
// AI响应处理
async function getAIResponse(userText, messageIndex = null) {
try {
controller = new AbortController();
showThinkingIndicator();
const apiUrl = API_ENDPOINTS[currentMode].replace('{query}', encodeURIComponent(userText));
const response = await fetch(apiUrl, { signal: controller.signal });
clearInterval(thinkingInterval);
chatMessages.removeChild(thinkingDiv);
thinkingDiv = null;
if (!response.ok) throw new Error('请求失败');
let aiText = await response.text();
const messageDiv = appendMessage('ai', '', aiAvatar);
const contentDiv = messageDiv.querySelector('.message-content');
if (messageIndex !== null) {
conversationHistory[messageIndex].answer = aiText;
} else {
conversationHistory.push({ question: userText, answer: aiText });
}
if (currentMode === 'image') {
handleImageResponse(aiText, messageDiv, contentDiv);
} else {
typeWriterEffect(contentDiv, aiText, () => addActionButtons(messageDiv, aiText));
}
} catch (error) {
handleAIError(error);
}
}
function showThinkingIndicator() {
thinkingDiv = document.createElement('div');
thinkingDiv.className = 'thinking-indicator';
const spinner = document.createElement('span');
spinner.className = 'loader';
const timerSpan = document.createElement('span');
const thinkingText = {
search: '数据分析中,请稍作等待...',
image: '图文绘画中,请耐心等待...',
writing: '文章撰写中,请耐心等待...',
answer: '逐步解题中,请稍作等待...'
}[currentMode];
timerSpan.textContent = thinkingText;
thinkingDiv.appendChild(spinner);
thinkingDiv.appendChild(timerSpan);
chatMessages.appendChild(thinkingDiv);
startTime = Date.now();
thinkingInterval = setInterval(() => {
const elapsed = Date.now() - startTime;
const hours = String(Math.floor(elapsed / 3600000)).padStart(2, '0');
const minutes = String(Math.floor((elapsed % 3600000) / 60000)).padStart(2, '0');
const seconds = String(Math.floor((elapsed % 60000) / 1000)).padStart(2, '0');
timerSpan.textContent = `${thinkingText} ${hours}:${minutes}:${seconds}`;
}, 1000);
}
function handleImageResponse(imageUrl, messageDiv, contentDiv) {
if (imageUrl.startsWith('http')) {
contentDiv.innerHTML = `
<div class="image-container">
<img src="${imageUrl}" class="generated-image" alt="生成的图像">
<div class="image-prompt">图像由 云智AI 生成,请仔细甄别!</div>
</div>
`;
addActionButtons(messageDiv, imageUrl);
scrollToBottom();
} else {
contentDiv.textContent = imageUrl;
addActionButtons(messageDiv, imageUrl);
}
}
function handleAIError(error) {
clearInterval(thinkingInterval);
if (thinkingDiv) chatMessages.removeChild(thinkingDiv);
thinkingDiv = null;
submitBtn.style.display = 'block';
stopBtn.style.display = 'none';
if (error.name !== 'AbortError') {
appendMessage('ai', '网络不稳定,请稍后重试', aiAvatar);
}
}
function regenerateResponse(messageIndex) {
if (messageIndex >= 0 && messageIndex < conversationHistory.length) {
const question = conversationHistory[messageIndex].question;
controller = new AbortController();
const loadingDiv = appendMessage('ai', '重新生成中...', aiAvatar);
getAIResponse(question, messageIndex);
setTimeout(() => chatMessages.removeChild(loadingDiv), 500);
}
}
function sendMessage() {
const text = userInput.value.trim();
if (!text) return;
controller = new AbortController();
appendMessage('user', text, userAvatar);
userInput.value = '';
userInput.style.height = 'auto';
getAIResponse(text);
ocrBtn.style.display = 'block';
}
function stopAIResponse() {
controller.abort();
typingEffectActive = false;
submitBtn.style.display = 'block';
stopBtn.style.display = 'none';
clearInterval(thinkingInterval);
if (thinkingDiv) chatMessages.removeChild(thinkingDiv);
}
// OCR处理
async function handleOCRImageUpload(event) {
const file = event.target.files[0];
if (!file || !file.type.match('image.*')) {
alert('请选择有效的图片文件');
return;
}
document.getElementById('ocr-upload-initial').style.display = 'none';
document.getElementById('ocr-upload-progress').style.display = 'block';
document.getElementById('ocr-progress-text').textContent = '正在解析图片内容...';
const randomName = `${Math.floor(100000 + Math.random() * 900000)}.jpg`;
const formData = new FormData();
formData.append('image', file);
formData.append('filename', randomName);
// 模拟上传进度
let progress = 0;
const progressInterval = setInterval(() => {
progress += 5;
if (progress > 90) clearInterval(progressInterval);
document.getElementById('ocr-progress-bar').style.width = `${progress}%`;
}, 200);
try {
const response = await fetch('https://api.jkyai.top/chat/upload.php', {
method: 'POST',
body: formData
});
if (!response.ok) throw new Error('上传失败: ' + response.status);
const data = await response.json();
if (!data.success) throw new Error(data.message || '上传失败');
clearInterval(progressInterval);
document.getElementById('ocr-progress-bar').style.width = '100%';
document.getElementById('ocr-progress-text').textContent = '图片解析成功,正在识别中...';
const imageUrl = `https://api.jkyai.top/chat/file/${randomName}`;
const ocrResponse = await fetch(`https://api.jkyai.top/chat/ocr.php?url=${encodeURIComponent(imageUrl)}&type=text`);
if (!ocrResponse.ok) throw new Error('识别失败: ' + ocrResponse.status);
const text = await ocrResponse.text();
userInput.value = text.replace(/\n+/g, '\n').trim();
userInput.focus();
userInput.style.height = 'auto';
userInput.style.height = `${userInput.scrollHeight}px`;
closeModal('ocr-upload-modal');
} catch (error) {
clearInterval(progressInterval);
document.getElementById('ocr-progress-text').textContent = error.message || '处理失败';
setTimeout(() => closeModal('ocr-upload-modal'), 1500);
}
}
// 免责声明检查
function checkForceDisclaimer() {
const lastAcceptTime = localStorage.getItem('lastDisclaimerAcceptTime');
const oneHour = 60 * 60 * 1000;
if (!lastAcceptTime || (Date.now() - parseInt(lastAcceptTime)) > oneHour) {
showModal('force-disclaimer-modal');
}
}
// 欢迎消息
function showWelcomeMessage() {
const initialMessage = '您好!我是云智计算研发的语言大模型,为您提供写作润色,代码优化,论文编辑,数据分析等一系列服务。请问有什么能够帮助您的吗?【支持记忆对话/深度思考】';
const messageDiv = appendMessage('ai', '', aiAvatar);
const contentDiv = messageDiv.querySelector('.message-content');
conversationHistory.push({ question: "", answer: initialMessage });
typeWriterEffect(contentDiv, initialMessage, () => {
addActionButtons(messageDiv, initialMessage, true);
});
}
// 高亮JS加载
function loadHighlightJs() {
hljs.configure({
languages: [
'javascript', 'python', 'java', 'c', 'cpp', 'csharp', 'go',
'ruby', 'php', 'swift', 'kotlin', 'rust', 'scala', 'perl',
'bash', 'shell', 'sql', 'html', 'css', 'xml', 'json'
]
});
const requiredLanguages = [
'php', 'javascript', 'python', 'java', 'c', 'cpp', 'csharp', 'go',
'ruby', 'swift', 'kotlin', 'rust', 'scala', 'perl', 'bash', 'shell', 'sql', 'html', 'css', 'xml', 'json'
];
requiredLanguages.forEach(lang => {
if (!hljs.getLanguage(lang)) {
const script = document.createElement('script');
script.src = `https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.7.0/languages/${lang}.min.js`;
document.head.appendChild(script);
}
});
}
// 安全措施
function setupSecurity() {
// 防止控制台访问
Object.defineProperty(window, 'conversationHistory', {
configurable: false,
writable: false,
value: conversationHistory
});
// 禁用右键菜单
document.addEventListener('contextmenu', e => e.preventDefault());
// 禁用开发者工具
document.addEventListener('keydown', e => {
if (e.key === 'F12' || e.keyCode === 123 ||
(e.ctrlKey && e.shiftKey && e.keyCode === 73) ||
(e.ctrlKey && e.keyCode === 85) ||
(e.ctrlKey && e.keyCode === 83)) {
e.preventDefault();
}
});
// 禁用iframe嵌套
if (window.top !== window.self) {
window.top.location = window.self.location;
}
// 禁用缓存
window.onpageshow = event => {
if (event.persisted) window.location.reload();
};
}
// 初始化应用
setupSecurity();
init();
</script>
</body>
</html>