Files
ai-chat-system/templates/index.html

722 lines
33 KiB
HTML
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-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI 对话系统 v2.0</title>
<link href="https://cdn.jsdelivr.net/npm/remixicon@3.5.0/fonts/remixicon.css" rel="stylesheet">
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; background: #f5f5f5; height: 100vh; display: flex; }
.sidebar { width: 260px; background: #202123; color: #fff; display: flex; flex-direction: column; }
.sidebar-header { padding: 16px; border-bottom: 1px solid #4d4d4f; }
.new-chat-btn { width: 100%; padding: 12px 16px; background: transparent; border: 1px solid #4d4d4f; border-radius: 6px; color: #fff; cursor: pointer; display: flex; align-items: center; gap: 8px; font-size: 14px; transition: background 0.2s; }
.new-chat-btn:hover { background: #2a2b32; }
.conversation-list { flex: 1; overflow-y: auto; padding: 8px; }
.conversation-item { padding: 12px; border-radius: 6px; cursor: pointer; display: flex; align-items: center; justify-content: space-between; margin-bottom: 4px; color: #ececf1; }
.conversation-item:hover { background: #2a2b32; }
.conversation-item.active { background: #343541; }
.conversation-item .title { flex: 1; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; font-size: 14px; }
.conversation-item .delete-btn { opacity: 0; background: none; border: none; color: #999; cursor: pointer; padding: 4px; }
.conversation-item:hover .delete-btn { opacity: 1; }
.main-content { flex: 1; display: flex; flex-direction: column; background: #fff; }
.chat-header { padding: 16px 24px; border-bottom: 1px solid #e5e5e5; display: flex; align-items: center; justify-content: space-between; }
.chat-header h1 { font-size: 18px; font-weight: 600; }
.header-controls { display: flex; align-items: center; gap: 16px; }
/* Agent选择器 */
.agent-selector { display: flex; align-items: center; gap: 8px; }
.agent-selector label { font-size: 14px; color: #666; }
.agent-selector select { padding: 8px 12px; border: 1px solid #ddd; border-radius: 8px; font-size: 14px; background: #fff; cursor: pointer; min-width: 150px; }
.agent-selector select:focus { border-color: #10a37f; outline: none; }
.messages-container { flex: 1; overflow-y: auto; padding: 24px; }
.message { max-width: 800px; margin: 0 auto 24px; display: flex; gap: 16px; }
.message-avatar { width: 36px; height: 36px; border-radius: 4px; display: flex; align-items: center; justify-content: center; flex-shrink: 0; font-size: 18px; }
.message.user .message-avatar { background: #5436da; color: #fff; }
.message.assistant .message-avatar { background: #19c37d; color: #fff; }
.message-content { flex: 1; line-height: 1.6; }
.message-content pre { background: #1e1e1e; color: #d4d4d4; padding: 16px; border-radius: 8px; overflow-x: auto; margin: 12px 0; }
.message-content code { background: #f0f0f0; padding: 2px 6px; border-radius: 4px; font-family: monospace; }
.message-content pre code { background: transparent; padding: 0; }
/* 思考内容样式 */
.thinking-content { background: #f8f9fa; border-left: 3px solid #667eea; padding: 12px 16px; margin: 8px 0; font-size: 14px; color: #666; border-radius: 4px; }
.thinking-toggle { cursor: pointer; color: #667eea; font-size: 12px; margin-top: 4px; }
.input-container { padding: 24px; border-top: 1px solid #e5e5e5; }
.input-wrapper { max-width: 800px; margin: 0 auto; display: flex; gap: 12px; align-items: flex-end; }
.input-wrapper textarea { flex: 1; padding: 12px 16px; border: 1px solid #ddd; border-radius: 12px; font-size: 16px; resize: none; outline: none; font-family: inherit; max-height: 200px; }
.input-wrapper textarea:focus { border-color: #10a37f; }
.send-btn { width: 48px; height: 48px; border-radius: 12px; background: #10a37f; border: none; color: #fff; cursor: pointer; font-size: 20px; display: flex; align-items: center; justify-content: center; transition: background 0.2s; }
.send-btn:hover { background: #0d8c6d; }
.send-btn:disabled { background: #ccc; cursor: not-allowed; }
/* 快捷语句 */
.quick-phrases { max-width: 800px; margin: 0 auto; padding-top: 12px; }
.quick-phrases-header { display: flex; align-items: center; justify-content: space-between; margin-bottom: 8px; }
.quick-phrases-header span { font-size: 12px; color: #999; }
.quick-phrases-header button { font-size: 12px; background: none; border: 1px solid #ddd; padding: 4px 8px; border-radius: 4px; cursor: pointer; color: #666; }
.quick-phrases-header button:hover { background: #f5f5f5; }
.quick-phrases-list { display: flex; flex-wrap: wrap; gap: 8px; }
.quick-phrase-item { display: flex; align-items: center; gap: 4px; padding: 6px 12px; background: #f0f0f0; border-radius: 16px; cursor: pointer; font-size: 13px; color: #333; transition: all 0.2s; border: 1px solid transparent; }
.quick-phrase-item:hover { background: #e8e8e8; border-color: #10a37f; }
.quick-phrase-item .delete-btn { opacity: 0; font-size: 12px; color: #999; margin-left: 4px; }
.quick-phrase-item:hover .delete-btn { opacity: 1; }
/* 快捷语句管理弹窗 */
.phrase-modal { position: fixed; top: 0; left: 0; right: 0; bottom: 0; background: rgba(0,0,0,0.5); display: none; align-items: center; justify-content: center; z-index: 1000; }
.phrase-modal.show { display: flex; }
.phrase-modal-content { background: #fff; padding: 24px; border-radius: 12px; max-width: 400px; width: 90%; }
.phrase-modal-content h3 { margin-bottom: 16px; }
.phrase-modal-content input { width: 100%; padding: 12px; border: 1px solid #ddd; border-radius: 8px; margin-bottom: 16px; }
.phrase-modal-buttons { display: flex; gap: 12px; justify-content: flex-end; }
.phrase-modal-buttons button { padding: 8px 16px; border-radius: 8px; cursor: pointer; }
.phrase-modal-buttons .cancel { background: #f5f5f5; border: 1px solid #ddd; }
.phrase-modal-buttons .save { background: #10a37f; color: #fff; border: none; }
.welcome { display: flex; flex-direction: column; align-items: center; justify-content: center; height: 100%; color: #666; }
.welcome h2 { font-size: 28px; margin-bottom: 16px; color: #333; }
.welcome p { font-size: 16px; }
.loading::after { content: ''; animation: dots 1.5s infinite; }
@keyframes dots { 0%, 20% { content: '.'; } 40% { content: '..'; } 60%, 100% { content: '...'; } }
::-webkit-scrollbar { width: 8px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb { background: #c1c1c1; border-radius: 4px; }
::-webkit-scrollbar-thumb:hover { background: #a1a1a1; }
</style>
</head>
<body>
<div class="sidebar">
<div class="sidebar-header">
<button class="new-chat-btn" onclick="createNewConversation()">
<i class="ri-add-line"></i> 新对话
</button>
</div>
<div class="conversation-list" id="conversationList">
<div class="empty-state" style="text-align:center;color:#999;padding:40px;">暂无对话</div>
</div>
</div>
<div class="main-content">
<div class="chat-header">
<h1>AI 对话 v2.0</h1>
<div class="header-controls">
<div class="agent-selector">
<label><i class="ri-robot-line"></i> Agent:</label>
<select id="agentSelect" onchange="switchAgent()">
<option value="">加载中...</option>
</select>
</div>
<div style="font-size:12px;color:#666;background:#f0f0f0;padding:4px 8px;border-radius:4px;">
<i class="ri-user-line"></i> 主用户 <span id="wsStatus" style="color:#999;">连接中...</span>
</div>
</div>
</div>
<div class="messages-container" id="messagesContainer">
<div class="welcome">
<h2>👋 欢迎使用 AI 对话系统</h2>
<p>选择Agent开始对话吧</p>
</div>
</div>
<div class="input-container">
<div class="input-wrapper">
<textarea id="messageInput" placeholder="输入消息... (Shift+Enter换行)" rows="1"></textarea>
<button class="send-btn" id="sendBtn" onclick="sendMessage()">
<i class="ri-send-plane-fill"></i>
</button>
</div>
<!-- 快捷语句 -->
<div class="quick-phrases">
<div class="quick-phrases-header">
<span><i class="ri-flashlight-line"></i> 快捷语句</span>
<button onclick="showAddPhraseModal()"><i class="ri-add-line"></i> 添加</button>
</div>
<div class="quick-phrases-list" id="quickPhrasesList"></div>
</div>
</div>
</div>
<!-- 添加快捷语句弹窗 -->
<div class="phrase-modal" id="phraseModal">
<div class="phrase-modal-content">
<h3><i class="ri-add-line"></i> 添加快捷语句</h3>
<input type="text" id="newPhraseInput" placeholder="输入快捷语句内容..." maxlength="100">
<div class="phrase-modal-buttons">
<button class="cancel" onclick="hidePhraseModal()">取消</button>
<button class="save" onclick="addPhrase()">添加</button>
</div>
</div>
</div>
<script>
// 全局变量
let ws = null;
let userId = 'main_user';
let currentConversationId = null;
let currentAgentId = null;
let conversations = [];
let agents = [];
let quickPhrases = [];
// 默认快捷语句
const DEFAULT_phrases = [
'请帮我总结一下',
'帮我分析这个问题',
'用简单的话解释一下',
'给我举个例子',
'有什么建议吗?'
];
// 初始化
document.addEventListener('DOMContentLoaded', () => {
loadAgents();
loadQuickPhrases();
connectWebSocket();
loadConversations();
setupTextarea();
setInterval(checkLatestConversation, 3000);
});
// 加载Agent列表
async function loadAgents() {
try {
const res = await fetch('/api/v2/agents');
const data = await res.json();
agents = data.agents || [];
renderAgentSelect();
// 设置当前Agent
const defaultAgent = agents.find(a => a.is_default) || agents[0];
if (defaultAgent) currentAgentId = defaultAgent.id;
} catch (e) {
console.error('加载Agent失败:', e);
document.getElementById('agentSelect').innerHTML = '<option value="">加载失败</option>';
}
}
// 渲染Agent下拉框
function renderAgentSelect(selectedId = null) {
const select = document.getElementById('agentSelect');
const selected = selectedId || currentAgentId;
select.innerHTML = agents.filter(a => a.is_active).map(a =>
`<option value="${a.id}" ${a.id === selected ? 'selected' : ''}>${a.display_name || a.name}</option>`
).join('');
}
// 更新Agent选择框显示
function updateAgentSelect(agentId) {
const select = document.getElementById('agentSelect');
select.value = agentId;
currentAgentId = agentId;
}
// 切换Agent - 自动创建新对话
async function switchAgent() {
const newAgentId = document.getElementById('agentSelect').value;
if (newAgentId && parseInt(newAgentId) !== currentAgentId) {
const oldAgentId = currentAgentId;
currentAgentId = parseInt(newAgentId);
console.log('切换Agent:', currentAgentId);
// 发送切换消息到WebSocket
if (ws && ws.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
action: 'switch_agent',
agent_id: currentAgentId
}));
}
// 自动创建新对话
await createNewConversation();
// 显示切换提示
const agent = agents.find(a => a.id === currentAgentId);
if (agent) {
showAgentSwitchNotice(agent.display_name || agent.name);
}
}
}
// 显示Agent切换提示
function showAgentSwitchNotice(agentName) {
const container = document.getElementById('messagesContainer');
// 清空现有内容,显示切换提示
container.innerHTML = `
<div class="welcome">
<h2>🔄 已切换 Agent</h2>
<p style="color:#667eea;font-size:18px;"><strong>${agentName}</strong></p>
<p style="color:#999;font-size:14px;">已为您创建新对话,可直接开始聊天</p>
</div>
`;
}
// 加载快捷语句
function loadQuickPhrases() {
const saved = localStorage.getItem('quickPhrases');
quickPhrases = saved ? JSON.parse(saved) : DEFAULT_phrases;
renderQuickPhrases();
}
// 渲染快捷语句
function renderQuickPhrases() {
const container = document.getElementById('quickPhrasesList');
if (quickPhrases.length === 0) {
container.innerHTML = '<span style="color:#999;font-size:12px;">点击"添加"创建快捷语句</span>';
return;
}
container.innerHTML = quickPhrases.map((p, i) => `
<div class="quick-phrase-item" onclick="usePhrase(${i})">
<span>${p}</span>
<span class="delete-btn" onclick="deletePhrase(${i}, event)"><i class="ri-close-line"></i></span>
</div>
`).join('');
}
// 使用快捷语句
function usePhrase(index) {
const input = document.getElementById('messageInput');
input.value = quickPhrases[index];
input.focus();
}
// 删除快捷语句
function deletePhrase(index, event) {
event.stopPropagation();
quickPhrases.splice(index, 1);
localStorage.setItem('quickPhrases', JSON.stringify(quickPhrases));
renderQuickPhrases();
}
// 显示添加弹窗
function showAddPhraseModal() {
document.getElementById('phraseModal').classList.add('show');
document.getElementById('newPhraseInput').value = '';
document.getElementById('newPhraseInput').focus();
}
// 隐藏弹窗
function hidePhraseModal() {
document.getElementById('phraseModal').classList.remove('show');
}
// 添加快捷语句
function addPhrase() {
const input = document.getElementById('newPhraseInput');
const phrase = input.value.trim();
if (phrase) {
quickPhrases.push(phrase);
localStorage.setItem('quickPhrases', JSON.stringify(quickPhrases));
renderQuickPhrases();
hidePhraseModal();
}
}
// 检查最新会话
async function checkLatestConversation() {
try {
const res = await fetch('/api/conversations/latest');
if (res.ok) {
const data = await res.json();
if (data.conversation && data.conversation.id !== currentConversationId) {
selectConversation(data.conversation.id);
}
}
} catch (e) {}
}
// WebSocket连接
function connectWebSocket() {
const protocol = window.location.protocol === 'https:' ? 'wss:' : 'ws:';
const wsUrl = `${protocol}//${window.location.host}/ws/${userId}`;
console.log('WebSocket连接:', wsUrl);
ws = new WebSocket(wsUrl);
ws.onopen = () => {
console.log('WebSocket已连接');
document.getElementById('wsStatus')?.textContent = '已连接';
};
ws.onmessage = (event) => {
console.log('WebSocket收到消息:', event.data);
handleWebSocketMessage(JSON.parse(event.data));
};
ws.onclose = (e) => {
console.log('WebSocket断开:', e.code, e.reason);
document.getElementById('wsStatus')?.textContent = '断开';
setTimeout(connectWebSocket, 3000);
};
ws.onerror = (e) => {
console.error('WebSocket错误:', e);
document.getElementById('wsStatus')?.textContent = '错误';
};
}
// 处理WebSocket消息
function handleWebSocketMessage(data) {
switch (data.type) {
case 'history':
displayHistory(data.messages);
if (data.agent_id) {
currentAgentId = data.agent_id;
updateAgentSelect(data.agent_id);
}
break;
case 'conversation_created':
currentConversationId = data.conversation_id;
loadConversations();
break;
case 'new_conversation':
currentConversationId = data.conversation_id;
loadConversations();
clearMessages();
break;
case 'agent_switched':
currentAgentId = data.agent_id;
console.log('Agent已切换:', data.agent_name);
break;
// 流式输出处理
case 'thinking_start':
createStreamingMessage('thinking');
break;
case 'thinking_stream':
appendStreamText('thinking', data.text);
break;
case 'thinking_end':
collapseThinking(data.content);
createStreamingMessage('content');
break;
case 'content_stream':
appendStreamText('content', data.text);
break;
case 'stream_end':
finishStreaming();
document.getElementById('sendBtn').disabled = false;
break;
case 'user_message':
appendMessage('user', data.message.content, data.message.source);
if (data.message.source === 'matrix' && data.conversation_id) {
currentConversationId = data.conversation_id;
renderConversations();
}
break;
case 'assistant_message':
// 流式完成后更新最终消息添加Agent信息等
updateFinalMessage(data.message);
break;
case 'error':
showError(data.message);
document.getElementById('sendBtn').disabled = false;
break;
}
}
// 创建流式消息容器
function createStreamingMessage(type) {
const container = document.getElementById('messagesContainer');
container.querySelector('.welcome')?.remove();
const existingStream = document.getElementById('streaming-message');
if (existingStream && type === 'content') return; // 回答区域已存在
const div = document.createElement('div');
div.id = 'streaming-message';
div.className = 'message assistant';
if (type === 'thinking') {
div.innerHTML = `
<div class="message-avatar">🤔</div>
<div class="message-content">
<div class="thinking-stream" id="thinking-stream" style="background:#f8f9fa;border-left:3px solid #667eea;padding:12px;font-size:14px;color:#667eea;">
<strong>💭 思考过程</strong><br>
<span id="thinking-text"></span>
</div>
</div>
`;
} else {
div.innerHTML = `
<div class="message-avatar">🤖</div>
<div class="message-content">
<div id="thinking-collapsed" style="display:none;"></div>
<span id="content-text"></span>
</div>
`;
}
container.appendChild(div);
container.scrollTop = container.scrollHeight;
}
// 追加流式文本
function appendStreamText(type, text) {
const textEl = document.getElementById(type === 'thinking' ? 'thinking-text' : 'content-text');
if (textEl) {
textEl.textContent += text;
const container = document.getElementById('messagesContainer');
container.scrollTop = container.scrollHeight;
}
}
// 折叠思考内容
function collapseThinking(content) {
const streamEl = document.getElementById('thinking-stream');
if (streamEl) {
// 创建折叠版本
const collapsedDiv = document.getElementById('thinking-collapsed');
if (collapsedDiv) {
collapsedDiv.style.display = 'block';
collapsedDiv.innerHTML = `
<div class="thinking-collapsed" onclick="toggleThinkingBlock(this)" style="cursor:pointer;background:#f0f0f0;padding:8px 12px;border-radius:4px;margin-bottom:8px;font-size:13px;color:#667eea;">
<i class="ri-lightbulb-line"></i> 思考过程 <span class="toggle-hint">(点击展开)</span>
</div>
<div class="thinking-expanded" style="display:none;background:#f8f9fa;border-left:3px solid #667eea;padding:12px;font-size:14px;margin-bottom:12px;">
${formatContent(content)}
</div>
`;
}
streamEl.style.display = 'none';
}
}
// 展开/折叠思考内容
function toggleThinkingBlock(el) {
const expanded = el.parentElement.querySelector('.thinking-expanded');
const hint = el.querySelector('.toggle-hint');
if (expanded.style.display === 'none') {
expanded.style.display = 'block';
hint.textContent = '(点击折叠)';
} else {
expanded.style.display = 'none';
hint.textContent = '(点击展开)';
}
}
// 完成流式输出
function finishStreaming() {
const streamMsg = document.getElementById('streaming-message');
if (streamMsg) {
streamMsg.id = ''; // 移除临时ID
}
}
// 更新最终消息添加Agent信息
function updateFinalMessage(msg) {
const messages = document.querySelectorAll('.message.assistant');
const lastMsg = messages[messages.length - 1];
if (lastMsg && msg.agent_name) {
const contentDiv = lastMsg.querySelector('.message-content');
if (contentDiv && !contentDiv.querySelector('.agent-info')) {
const agentInfo = document.createElement('div');
agentInfo.className = 'agent-info';
agentInfo.style.cssText = 'font-size:12px;color:#999;margin-top:8px;';
agentInfo.innerHTML = `<i class="ri-robot-line"></i> ${msg.agent_name}`;
contentDiv.appendChild(agentInfo);
}
}
}
// 显示错误
function showError(msg) {
const container = document.getElementById('messagesContainer');
const div = document.createElement('div');
div.className = 'message assistant';
div.innerHTML = `
<div class="message-avatar">❌</div>
<div class="message-content" style="color:#dc3545;">${msg}</div>
`;
container.appendChild(div);
}
// 清空消息
function clearMessages() {
document.getElementById('messagesContainer').innerHTML = `
<div class="welcome">
<h2>👋 开始新对话</h2>
<p>当前Agent: ${getAgentName()}</p>
</div>
`;
}
// 获取当前Agent名称
function getAgentName() {
const agent = agents.find(a => a.id === currentAgentId);
return agent ? (agent.display_name || agent.name) : '默认助手';
}
// 显示历史消息
function displayHistory(messages) {
const container = document.getElementById('messagesContainer');
container.innerHTML = '';
messages.forEach(msg => {
appendMessage(msg.role, msg.content, msg.source, msg.thinking_content);
});
}
// 添加消息
function appendMessage(role, content, source = 'web', thinking = null, agentName = null) {
const container = document.getElementById('messagesContainer');
container.querySelector('.welcome')?.remove();
document.getElementById('thinkingIndicator')?.remove();
const div = document.createElement('div');
div.className = `message ${role}`;
const avatar = role === 'user' ? '👤' : '🤖';
let html = `<div class="message-avatar">${avatar}</div><div class="message-content">`;
// 思考内容
if (thinking) {
html += `<div class="thinking-content" id="thinking-${Date.now()}">
<strong>💭 思考过程:</strong><br>${formatContent(thinking)}
<div class="thinking-toggle" onclick="toggleThinking(this)">收起</div>
</div>`;
}
// 主要内容
html += formatContent(content);
// Agent信息
if (role === 'assistant' && agentName) {
html += `<div style="font-size:12px;color:#999;margin-top:8px;"><i class="ri-robot-line"></i> ${agentName}</div>`;
}
html += '</div>';
div.innerHTML = html;
container.appendChild(div);
container.scrollTop = container.scrollHeight;
}
// 切换思考内容显示
function toggleThinking(btn) {
const content = btn.parentElement;
const isCollapsed = content.style.maxHeight;
if (isCollapsed) {
content.style.maxHeight = '';
content.style.overflow = '';
btn.textContent = '收起';
} else {
content.style.maxHeight = '60px';
content.style.overflow = 'hidden';
btn.textContent = '展开';
}
}
// 格式化内容
function formatContent(content) {
return content
.replace(/```(\w*)\n([\s\S]*?)```/g, '<pre><code>$2</code></pre>')
.replace(/`([^`]+)`/g, '<code>$1</code>')
.replace(/\n/g, '<br>');
}
// 加载会话列表
async function loadConversations() {
try {
const res = await fetch('/api/conversations');
const data = await res.json();
conversations = data.conversations || [];
renderConversations();
if (!currentConversationId && conversations.length > 0) {
selectConversation(conversations[0].id);
}
} catch (e) { console.error('加载会话失败:', e); }
}
// 渲染会话列表
function renderConversations() {
const container = document.getElementById('conversationList');
if (conversations.length === 0) {
container.innerHTML = '<div style="text-align:center;color:#999;padding:40px;">暂无对话</div>';
return;
}
container.innerHTML = conversations.map(c => `
<div class="conversation-item ${c.id === currentConversationId ? 'active' : ''}" onclick="selectConversation('${c.id}')">
<span class="title">${c.title}</span>
<button class="delete-btn" onclick="deleteConversation('${c.id}', event)"><i class="ri-delete-bin-line"></i></button>
</div>
`).join('');
}
// 选择会话
function selectConversation(id) {
currentConversationId = id;
renderConversations();
if (ws?.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({ action: 'select_conversation', conversation_id: id }));
}
}
// 创建新会话
async function createNewConversation() {
try {
const res = await fetch('/api/conversations', { method: 'POST' });
const data = await res.json();
currentConversationId = data.id;
clearMessages();
loadConversations();
} catch (e) { console.error('创建会话失败:', e); }
}
// 删除会话
async function deleteConversation(id, event) {
event.stopPropagation();
if (!confirm('确定删除?')) return;
try {
await fetch(`/api/conversations/${id}`, { method: 'DELETE' });
if (id === currentConversationId) {
currentConversationId = null;
clearMessages();
}
loadConversations();
} catch (e) { console.error('删除失败:', e); }
}
// 发送消息
function sendMessage() {
const input = document.getElementById('messageInput');
const msg = input.value.trim();
if (!msg) return;
document.getElementById('sendBtn').disabled = true;
input.value = '';
input.style.height = 'auto';
if (ws?.readyState === WebSocket.OPEN) {
ws.send(JSON.stringify({
action: 'chat',
message: msg,
conversation_id: currentConversationId,
agent_id: currentAgentId
}));
}
}
// 设置文本框
function setupTextarea() {
const textarea = document.getElementById('messageInput');
textarea.addEventListener('keydown', (e) => {
if (e.key === 'Enter' && !e.shiftKey) {
e.preventDefault();
sendMessage();
}
});
textarea.addEventListener('input', () => {
textarea.style.height = 'auto';
textarea.style.height = Math.min(textarea.scrollHeight, 200) + 'px';
});
}
// 弹窗键盘事件
document.getElementById('newPhraseInput').addEventListener('keydown', (e) => {
if (e.key === 'Enter') addPhrase();
if (e.key === 'Escape') hidePhraseModal();
});
</script>
</body>
</html>