|
|
|
|
|
|
| class ChatInterface {
|
| constructor() {
|
| this.isVisible = false;
|
| this.messages = [];
|
| this.maxMessages = 50;
|
| this.chatContainer = null;
|
| this.messageContainer = null;
|
| this.inputContainer = null;
|
| this.messageInput = null;
|
| this.sendButton = null;
|
| this.toggleButton = null;
|
| this.settingsPanel = null;
|
| this.isSettingsVisible = false;
|
|
|
| this.init();
|
| }
|
|
|
|
|
| init() {
|
| this.createChatContainer();
|
| this.createToggleButton();
|
| this.createSettingsPanel();
|
| this.bindEvents();
|
| this.addWelcomeMessage();
|
| }
|
|
|
|
|
| createChatContainer() {
|
|
|
| this.chatContainer = document.createElement('div');
|
| this.chatContainer.className = 'bella-chat-container';
|
| this.chatContainer.innerHTML = `
|
| <div class="bella-chat-header">
|
| <div class="bella-chat-title">
|
| <div class="bella-avatar">💝</div>
|
| <div class="bella-title-text">
|
| <h3>贝拉</h3>
|
| <span class="bella-status">在线</span>
|
| </div>
|
| </div>
|
| <div class="bella-chat-controls">
|
| <button class="bella-settings-btn" title="设置">
|
| <i class="fas fa-cog"></i>
|
| </button>
|
| <button class="bella-minimize-btn" title="最小化">
|
| <i class="fas fa-minus"></i>
|
| </button>
|
| </div>
|
| </div>
|
| <div class="bella-chat-messages"></div>
|
| <div class="bella-chat-input-container">
|
| <div class="bella-input-wrapper">
|
| <input type="text" class="bella-message-input" placeholder="和贝拉聊聊天..." maxlength="500">
|
| <button class="bella-send-btn" title="发送">
|
| <i class="fas fa-paper-plane"></i>
|
| </button>
|
| </div>
|
| <div class="bella-input-hint">
|
| 按 Enter 发送,Shift + Enter 换行
|
| </div>
|
| </div>
|
| `;
|
|
|
|
|
| this.messageContainer = this.chatContainer.querySelector('.bella-chat-messages');
|
| this.inputContainer = this.chatContainer.querySelector('.bella-chat-input-container');
|
| this.messageInput = this.chatContainer.querySelector('.bella-message-input');
|
| this.sendButton = this.chatContainer.querySelector('.bella-send-btn');
|
|
|
| document.body.appendChild(this.chatContainer);
|
| }
|
|
|
|
|
| createToggleButton() {
|
| this.toggleButton = document.createElement('button');
|
| this.toggleButton.className = 'bella-chat-toggle';
|
| this.toggleButton.innerHTML = `
|
| <div class="bella-toggle-icon">
|
| <i class="fas fa-comments"></i>
|
| </div>
|
| <div class="bella-toggle-text">与贝拉聊天</div>
|
| `;
|
| this.toggleButton.title = '打开聊天窗口';
|
|
|
| document.body.appendChild(this.toggleButton);
|
| }
|
|
|
|
|
| createSettingsPanel() {
|
| this.settingsPanel = document.createElement('div');
|
| this.settingsPanel.className = 'bella-settings-panel';
|
| this.settingsPanel.innerHTML = `
|
| <div class="bella-settings-header">
|
| <h4>聊天设置</h4>
|
| <button class="bella-settings-close">
|
| <i class="fas fa-times"></i>
|
| </button>
|
| </div>
|
| <div class="bella-settings-content">
|
| <div class="bella-setting-group">
|
| <label>AI服务提供商</label>
|
| <select class="bella-provider-select">
|
| <option value="local">本地模型</option>
|
| <option value="openai">OpenAI GPT</option>
|
| <option value="qwen">通义千问</option>
|
| <option value="ernie">文心一言</option>
|
| <option value="glm">智谱AI</option>
|
| </select>
|
| </div>
|
| <div class="bella-setting-group bella-api-key-group" style="display: none;">
|
| <label>API密钥</label>
|
| <input type="password" class="bella-api-key-input" placeholder="请输入API密钥">
|
| <button class="bella-api-key-save">保存</button>
|
| </div>
|
| <div class="bella-setting-group">
|
| <label>聊天模式</label>
|
| <select class="bella-mode-select">
|
| <option value="casual">轻松聊天</option>
|
| <option value="assistant">智能助手</option>
|
| <option value="creative">创意伙伴</option>
|
| </select>
|
| </div>
|
| <div class="bella-setting-group">
|
| <button class="bella-clear-history">清除聊天记录</button>
|
| </div>
|
| </div>
|
| `;
|
|
|
| document.body.appendChild(this.settingsPanel);
|
| }
|
|
|
|
|
| bindEvents() {
|
|
|
| this.toggleButton.addEventListener('click', () => {
|
| this.toggle();
|
| });
|
|
|
|
|
| this.chatContainer.querySelector('.bella-minimize-btn').addEventListener('click', () => {
|
| this.hide();
|
| });
|
|
|
|
|
| this.chatContainer.querySelector('.bella-settings-btn').addEventListener('click', () => {
|
| this.toggleSettings();
|
| });
|
|
|
|
|
| this.sendButton.addEventListener('click', () => {
|
| this.sendMessage();
|
| });
|
|
|
|
|
| this.messageInput.addEventListener('keydown', (e) => {
|
| if (e.key === 'Enter' && !e.shiftKey) {
|
| e.preventDefault();
|
| this.sendMessage();
|
| }
|
| });
|
|
|
|
|
| this.messageInput.addEventListener('input', () => {
|
| this.adjustInputHeight();
|
| });
|
|
|
|
|
| this.bindSettingsEvents();
|
| }
|
|
|
|
|
| bindSettingsEvents() {
|
|
|
| this.settingsPanel.querySelector('.bella-settings-close').addEventListener('click', () => {
|
| this.hideSettings();
|
| });
|
|
|
|
|
| const providerSelect = this.settingsPanel.querySelector('.bella-provider-select');
|
| const apiKeyGroup = this.settingsPanel.querySelector('.bella-api-key-group');
|
|
|
| providerSelect.addEventListener('change', (e) => {
|
| const provider = e.target.value;
|
| if (provider === 'local') {
|
| apiKeyGroup.style.display = 'none';
|
| } else {
|
| apiKeyGroup.style.display = 'block';
|
| }
|
|
|
|
|
| this.onProviderChange?.(provider);
|
| });
|
|
|
|
|
| this.settingsPanel.querySelector('.bella-api-key-save').addEventListener('click', () => {
|
| const provider = providerSelect.value;
|
| const apiKey = this.settingsPanel.querySelector('.bella-api-key-input').value;
|
|
|
| if (apiKey.trim()) {
|
| this.onAPIKeySave?.(provider, apiKey.trim());
|
| this.showNotification('API密钥已保存', 'success');
|
| }
|
| });
|
|
|
|
|
| this.settingsPanel.querySelector('.bella-clear-history').addEventListener('click', () => {
|
| this.clearMessages();
|
| this.onClearHistory?.();
|
| this.hideSettings();
|
| });
|
| }
|
|
|
|
|
| addWelcomeMessage() {
|
| this.addMessage('assistant', '你好!我是贝拉,你的AI伙伴。很高兴见到你!有什么想聊的吗?', true);
|
| }
|
|
|
|
|
| toggle() {
|
| console.log('ChatInterface.toggle() 被调用');
|
| console.log('切换前 isVisible:', this.isVisible);
|
|
|
| if (this.isVisible) {
|
| this.hide();
|
| } else {
|
| this.show();
|
| }
|
|
|
| console.log('切换后 isVisible:', this.isVisible);
|
| }
|
|
|
|
|
| show() {
|
| console.log('ChatInterface.show() 被调用');
|
| console.log('显示前 isVisible:', this.isVisible);
|
| console.log('显示前 chatContainer.className:', this.chatContainer.className);
|
|
|
| this.isVisible = true;
|
| this.chatContainer.classList.add('visible');
|
|
|
| console.log('显示后 isVisible:', this.isVisible);
|
| console.log('显示后 chatContainer.className:', this.chatContainer.className);
|
| console.log('chatContainer 计算样式 opacity:', window.getComputedStyle(this.chatContainer).opacity);
|
| console.log('chatContainer 计算样式 transform:', window.getComputedStyle(this.chatContainer).transform);
|
|
|
| this.toggleButton.classList.add('active');
|
| this.messageInput.focus();
|
| this.scrollToBottom();
|
| }
|
|
|
|
|
| hide() {
|
| this.isVisible = false;
|
| this.chatContainer.classList.remove('visible');
|
| this.toggleButton.classList.remove('active');
|
| this.hideSettings();
|
| }
|
|
|
|
|
| toggleSettings() {
|
| if (this.isSettingsVisible) {
|
| this.hideSettings();
|
| } else {
|
| this.showSettings();
|
| }
|
| }
|
|
|
|
|
| showSettings() {
|
| this.isSettingsVisible = true;
|
| this.settingsPanel.classList.add('visible');
|
| }
|
|
|
|
|
| hideSettings() {
|
| this.isSettingsVisible = false;
|
| this.settingsPanel.classList.remove('visible');
|
| }
|
|
|
|
|
| sendMessage() {
|
| const text = this.messageInput.value.trim();
|
| if (!text) return;
|
|
|
|
|
| this.addMessage('user', text);
|
|
|
|
|
| this.messageInput.value = '';
|
| this.adjustInputHeight();
|
|
|
|
|
| this.onMessageSend?.(text);
|
| }
|
|
|
|
|
| addMessage(role, content, isWelcome = false) {
|
| const messageElement = document.createElement('div');
|
| messageElement.className = `bella-message bella-message-${role}`;
|
|
|
| if (isWelcome) {
|
| messageElement.classList.add('bella-welcome-message');
|
| }
|
|
|
| const timestamp = new Date().toLocaleTimeString('zh-CN', {
|
| hour: '2-digit',
|
| minute: '2-digit'
|
| });
|
|
|
| messageElement.innerHTML = `
|
| <div class="bella-message-avatar">
|
| ${role === 'user' ? '👤' : '💝'}
|
| </div>
|
| <div class="bella-message-content">
|
| <div class="bella-message-text">${this.formatMessage(content)}</div>
|
| <div class="bella-message-time">${timestamp}</div>
|
| </div>
|
| `;
|
|
|
| this.messageContainer.appendChild(messageElement);
|
| this.messages.push({ role, content, timestamp: Date.now() });
|
|
|
|
|
| if (this.messages.length > this.maxMessages) {
|
| const oldMessage = this.messageContainer.firstChild;
|
| if (oldMessage) {
|
| this.messageContainer.removeChild(oldMessage);
|
| }
|
| this.messages.shift();
|
| }
|
|
|
|
|
| this.scrollToBottom();
|
|
|
|
|
| setTimeout(() => {
|
| messageElement.classList.add('bella-message-appear');
|
| }, 10);
|
| }
|
|
|
|
|
| formatMessage(content) {
|
|
|
| return content
|
| .replace(/\n/g, '<br>')
|
| .replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>')
|
| .replace(/\*(.*?)\*/g, '<em>$1</em>');
|
| }
|
|
|
|
|
| showTypingIndicator() {
|
| const existingIndicator = this.messageContainer.querySelector('.bella-typing-indicator');
|
| if (existingIndicator) return;
|
|
|
| const typingElement = document.createElement('div');
|
| typingElement.className = 'bella-typing-indicator';
|
| typingElement.innerHTML = `
|
| <div class="bella-message-avatar">💝</div>
|
| <div class="bella-message-content">
|
| <div class="bella-typing-dots">
|
| <span class="bella-typing-dot"></span>
|
| <span class="bella-typing-dot"></span>
|
| <span class="bella-typing-dot"></span>
|
| </div>
|
| </div>
|
| `;
|
|
|
| this.messageContainer.appendChild(typingElement);
|
| this.scrollToBottom();
|
|
|
|
|
| setTimeout(() => {
|
| typingElement.classList.add('bella-typing-show');
|
| }, 10);
|
| }
|
|
|
|
|
| hideTypingIndicator() {
|
| const indicator = this.messageContainer.querySelector('.bella-typing-indicator');
|
| if (indicator) {
|
| this.messageContainer.removeChild(indicator);
|
| }
|
| }
|
|
|
|
|
| clearMessages() {
|
| this.messageContainer.innerHTML = '';
|
| this.messages = [];
|
| this.addWelcomeMessage();
|
| }
|
|
|
|
|
| scrollToBottom() {
|
| setTimeout(() => {
|
| this.messageContainer.scrollTop = this.messageContainer.scrollHeight;
|
| }, 10);
|
| }
|
|
|
|
|
| adjustInputHeight() {
|
| this.messageInput.style.height = 'auto';
|
| this.messageInput.style.height = Math.min(this.messageInput.scrollHeight, 120) + 'px';
|
| }
|
|
|
|
|
| showNotification(message, type = 'info') {
|
| const notification = document.createElement('div');
|
| notification.className = `bella-notification bella-notification-${type}`;
|
| notification.textContent = message;
|
|
|
| document.body.appendChild(notification);
|
|
|
| setTimeout(() => {
|
| notification.classList.add('bella-notification-show');
|
| }, 10);
|
|
|
| setTimeout(() => {
|
| notification.classList.remove('bella-notification-show');
|
| setTimeout(() => {
|
| document.body.removeChild(notification);
|
| }, 300);
|
| }, 3000);
|
| }
|
|
|
|
|
| getVisibility() {
|
| return this.isVisible;
|
| }
|
|
|
|
|
| onMessageSend = null;
|
| onProviderChange = null;
|
| onAPIKeySave = null;
|
| onClearHistory = null;
|
| }
|
|
|
|
|
| export { ChatInterface }; |