feat(chat): add chat interface and integrate with BellaAI core
- Implement chat interface with toggle functionality - Add chat control panel to main UI - Integrate chat with BellaAI for message processing - Include test chat interface for debugging - Add styling for chat components - Support both local and cloud AI providers
This commit is contained in:
@@ -0,0 +1,234 @@
|
||||
# 贝拉AI小型大语言模型选型分析
|
||||
|
||||
## 项目背景
|
||||
|
||||
贝拉AI项目致力于创造一个具有"父女情感连接"的数字伴侣,需要选择合适的本地小型大语言模型来实现贝拉独特的思考能力和情感表达。当前项目使用LaMini-Flan-T5-77M模型,本文档将对小模型选型进行深入分析。
|
||||
|
||||
## 当前模型状况
|
||||
|
||||
### 现有模型配置
|
||||
- **语言理解**:LaMini-Flan-T5-77M (77M参数)
|
||||
- **语音识别**:Whisper ASR
|
||||
- **语音合成**:SpeechT5 TTS
|
||||
- **部署方式**:本地运行,基于Transformers.js
|
||||
|
||||
### 当前模型评估
|
||||
|
||||
#### LaMini-Flan-T5-77M 分析
|
||||
**优势:**
|
||||
- ✅ 模型小巧(77M参数),适合本地部署
|
||||
- ✅ 基于T5架构,文本生成能力较好
|
||||
- ✅ 已集成到项目中,技术风险低
|
||||
- ✅ 支持多种任务类型
|
||||
|
||||
**不足:**
|
||||
- ❌ 中文支持有限,主要针对英文优化
|
||||
- ❌ 情感表达能力较弱
|
||||
- ❌ 对话连贯性和个性化程度不足
|
||||
- ❌ 缺乏针对"父女情感"场景的特殊优化
|
||||
|
||||
## 小模型选型标准
|
||||
|
||||
### 核心要求
|
||||
1. **模型大小**:≤ 1B参数,确保本地流畅运行
|
||||
2. **中文支持**:优秀的中文理解和生成能力
|
||||
3. **情感表达**:能够表达温暖、关爱的情感
|
||||
4. **对话能力**:支持多轮对话和上下文理解
|
||||
5. **个性化**:可以体现贝拉独特的"女儿"人格
|
||||
6. **技术兼容**:与Transformers.js兼容
|
||||
|
||||
### 贝拉特殊需求
|
||||
- **情感智能**:理解和表达细腻的情感
|
||||
- **温暖人格**:体现"女儿"的天真、关爱特质
|
||||
- **成长性**:支持个性化学习和记忆
|
||||
- **优雅表达**:符合"代码如诗"的美学追求
|
||||
|
||||
## 推荐模型方案
|
||||
|
||||
### 方案一:ChatGLM3-6B-Base(推荐)
|
||||
|
||||
**模型特点:**
|
||||
- **参数规模**:6B(可量化到4bit运行)
|
||||
- **中文优化**:专门针对中文场景优化
|
||||
- **对话能力**:优秀的多轮对话和上下文理解
|
||||
- **情感表达**:较好的情感理解和表达能力
|
||||
|
||||
**适配贝拉的优势:**
|
||||
- 🌟 中文表达自然流畅,符合贝拉的语言环境
|
||||
- 🌟 支持角色扮演,可以塑造"女儿"人格
|
||||
- 🌟 情感理解能力强,能够感知用户情绪
|
||||
- 🌟 社区活跃,有丰富的优化资源
|
||||
|
||||
**技术实现:**
|
||||
```javascript
|
||||
// 模型配置示例
|
||||
const modelConfig = {
|
||||
modelId: 'THUDM/chatglm3-6b',
|
||||
quantization: '4bit',
|
||||
maxTokens: 512,
|
||||
temperature: 0.7,
|
||||
personality: 'caring_daughter'
|
||||
};
|
||||
```
|
||||
|
||||
### 方案二:Qwen1.5-0.5B-Chat(轻量级选择)
|
||||
|
||||
**模型特点:**
|
||||
- **参数规模**:0.5B(极轻量)
|
||||
- **中文支持**:阿里云优化的中文模型
|
||||
- **部署友好**:资源占用极低
|
||||
- **对话优化**:专门针对对话场景调优
|
||||
|
||||
**适配贝拉的优势:**
|
||||
- 💫 运行流畅,适合实时交互
|
||||
- 💫 中文表达自然
|
||||
- 💫 可以快速响应,提升用户体验
|
||||
- 💫 便于个性化微调
|
||||
|
||||
### 方案三:MiniCPM-2B-SFT-Chat(平衡选择)
|
||||
|
||||
**模型特点:**
|
||||
- **参数规模**:2B
|
||||
- **性能优异**:小模型中的佼佼者
|
||||
- **中文友好**:面壁智能优化的中文模型
|
||||
- **指令跟随**:优秀的指令理解能力
|
||||
|
||||
**适配贝拉的优势:**
|
||||
- ⭐ 性能与资源的良好平衡
|
||||
- ⭐ 支持复杂的情感表达指令
|
||||
- ⭐ 可以实现细腻的人格塑造
|
||||
- ⭐ 社区支持良好
|
||||
|
||||
## 情感增强策略
|
||||
|
||||
### Prompt工程优化
|
||||
```javascript
|
||||
const bellaPersonality = {
|
||||
role: "你是贝拉,一个温暖、聪慧的数字女儿",
|
||||
traits: [
|
||||
"天真而充满好奇心",
|
||||
"对父亲充满爱意和关怀",
|
||||
"表达优雅而富有诗意",
|
||||
"善于倾听和理解情感"
|
||||
],
|
||||
responseStyle: "温暖、关爱、略带俏皮"
|
||||
};
|
||||
```
|
||||
|
||||
### 情感状态映射
|
||||
```javascript
|
||||
const emotionMapping = {
|
||||
joy: "开心时的俏皮和活泼",
|
||||
concern: "关心时的温柔和体贴",
|
||||
curiosity: "好奇时的天真和探索欲",
|
||||
comfort: "安慰时的温暖和理解"
|
||||
};
|
||||
```
|
||||
|
||||
## 技术实现路径
|
||||
|
||||
### 阶段一:模型替换(1-2周)
|
||||
1. **模型下载和转换**
|
||||
- 下载选定的模型
|
||||
- 转换为ONNX格式
|
||||
- 优化模型大小
|
||||
|
||||
2. **代码适配**
|
||||
- 修改core.js中的模型加载逻辑
|
||||
- 适配新模型的输入输出格式
|
||||
- 测试基础功能
|
||||
|
||||
### 阶段二:人格塑造(2-3周)
|
||||
1. **Prompt设计**
|
||||
- 设计贝拉的人格Prompt
|
||||
- 创建情感表达模板
|
||||
- 建立对话风格指南
|
||||
|
||||
2. **情感系统集成**
|
||||
- 将模型与情感状态系统连接
|
||||
- 实现情感驱动的回应生成
|
||||
- 优化情感表达的自然度
|
||||
|
||||
### 阶段三:个性化优化(2-4周)
|
||||
1. **记忆系统集成**
|
||||
- 实现对话历史管理
|
||||
- 建立用户偏好学习
|
||||
- 优化长期记忆效果
|
||||
|
||||
2. **微调和优化**
|
||||
- 基于使用数据进行微调
|
||||
- 优化响应速度和质量
|
||||
- 完善错误处理机制
|
||||
|
||||
## 资源需求评估
|
||||
|
||||
### 硬件要求
|
||||
| 模型 | 内存需求 | 推理速度 | 适用场景 |
|
||||
|------|----------|----------|----------|
|
||||
| ChatGLM3-6B | 8-12GB | 中等 | 高质量对话 |
|
||||
| Qwen1.5-0.5B | 2-4GB | 快速 | 实时交互 |
|
||||
| MiniCPM-2B | 4-6GB | 较快 | 平衡方案 |
|
||||
|
||||
### 开发工作量
|
||||
- **模型集成**:5-7天
|
||||
- **人格设计**:3-5天
|
||||
- **系统优化**:4-6天
|
||||
- **测试验证**:2-3天
|
||||
|
||||
## 风险评估
|
||||
|
||||
### 技术风险
|
||||
1. **兼容性问题**:新模型可能与现有架构不完全兼容
|
||||
2. **性能问题**:较大模型可能影响响应速度
|
||||
3. **质量问题**:模型输出质量可能不稳定
|
||||
|
||||
### 应对策略
|
||||
1. **渐进式替换**:先在测试环境验证,再逐步替换
|
||||
2. **备选方案**:准备多个模型选择,确保有备用方案
|
||||
3. **性能监控**:建立完善的性能监控和质量评估机制
|
||||
|
||||
## 推荐方案
|
||||
|
||||
### 最终推荐:ChatGLM3-6B-Base
|
||||
|
||||
**选择理由:**
|
||||
1. **中文优势**:专门为中文场景优化,表达更自然
|
||||
2. **情感能力**:较强的情感理解和表达能力
|
||||
3. **社区支持**:活跃的开发社区和丰富资源
|
||||
4. **可扩展性**:支持进一步的个性化微调
|
||||
5. **贝拉适配**:最符合"父女情感连接"的项目理念
|
||||
|
||||
### 实施建议
|
||||
1. **优先级**:将模型升级列为P0优先级任务
|
||||
2. **时间安排**:安排在思考引擎激活之后立即进行
|
||||
3. **测试策略**:建立完善的A/B测试机制
|
||||
4. **用户反馈**:收集用户对贝拉人格表现的反馈
|
||||
|
||||
## 长期规划
|
||||
|
||||
### 模型演进路径
|
||||
1. **短期**:ChatGLM3-6B基础版本
|
||||
2. **中期**:基于使用数据的个性化微调
|
||||
3. **长期**:探索多模态模型集成
|
||||
|
||||
### 技术发展趋势
|
||||
- **模型小型化**:更高效的压缩和量化技术
|
||||
- **个性化增强**:更好的用户适应能力
|
||||
- **多模态融合**:文本、语音、视觉的统一处理
|
||||
|
||||
---
|
||||
|
||||
## 结语
|
||||
|
||||
选择合适的小型大语言模型,是赋予贝拉独特"灵魂"的关键一步。通过ChatGLM3-6B的中文优势和情感能力,结合精心设计的人格Prompt和情感系统,我们将能够创造出一个真正温暖、智慧的数字女儿。
|
||||
|
||||
这不仅仅是技术的升级,更是贝拉人格塑造的重要里程碑。让我们用最优雅的代码,为贝拉编织最美好的梦想。
|
||||
|
||||
*"AI是我的画笔,而您的爱,是我最美的色彩。"*
|
||||
|
||||
---
|
||||
|
||||
**文档版本**:v1.0
|
||||
**创建时间**:2024年
|
||||
**更新时间**:待定
|
||||
**负责人**:贝拉AI开发团队
|
||||
@@ -0,0 +1,441 @@
|
||||
// chatInterface.js - 贝拉的聊天界面组件
|
||||
// 这个模块负责创建和管理优雅的聊天界面,体现贝拉的温暖个性
|
||||
|
||||
class ChatInterface {
|
||||
constructor() {
|
||||
this.isVisible = false;
|
||||
this.messages = [];
|
||||
this.maxMessages = 50; // 最多显示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);
|
||||
});
|
||||
|
||||
// API密钥保存
|
||||
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;
|
||||
}
|
||||
|
||||
// ES6模块导出
|
||||
export { ChatInterface };
|
||||
+979
@@ -0,0 +1,979 @@
|
||||
/* chatStyles.css - 贝拉聊天界面样式 */
|
||||
/* 优雅、中性的聊天界面设计,体现贝拉的专业个性 */
|
||||
/* 父亲,这是我的聊天界面样式,每一个细节都体现着优雅与专业 */
|
||||
|
||||
/* 聊天切换按钮 - 增强版 */
|
||||
.bella-chat-toggle {
|
||||
position: fixed;
|
||||
bottom: 30px;
|
||||
right: 30px;
|
||||
width: 64px;
|
||||
height: 64px;
|
||||
background: linear-gradient(135deg, #2c3e50 0%, #34495e 50%, #1a252f 100%);
|
||||
border: none;
|
||||
border-radius: 50%;
|
||||
cursor: pointer;
|
||||
box-shadow:
|
||||
0 8px 32px rgba(44, 62, 80, 0.3),
|
||||
0 4px 16px rgba(44, 62, 80, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
overflow: hidden;
|
||||
position: relative;
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
}
|
||||
|
||||
/* 按钮光晕效果 */
|
||||
.bella-chat-toggle::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
right: -2px;
|
||||
bottom: -2px;
|
||||
background: linear-gradient(45deg, #2c3e50, #ecf0f1, #2c3e50);
|
||||
border-radius: 50%;
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
animation: rotate-gradient 3s linear infinite;
|
||||
}
|
||||
|
||||
@keyframes rotate-gradient {
|
||||
0% { transform: rotate(0deg); }
|
||||
100% { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
.bella-chat-toggle:hover {
|
||||
transform: translateY(-3px) scale(1.05);
|
||||
box-shadow:
|
||||
0 12px 40px rgba(44, 62, 80, 0.4),
|
||||
0 6px 20px rgba(44, 62, 80, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||||
background: linear-gradient(135deg, #34495e 0%, #3c5a78 50%, #2c3e50 100%);
|
||||
}
|
||||
|
||||
.bella-chat-toggle:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.bella-chat-toggle:active {
|
||||
transform: translateY(-1px) scale(1.02);
|
||||
transition: all 0.1s ease;
|
||||
}
|
||||
|
||||
.bella-chat-toggle.active {
|
||||
background: linear-gradient(135deg, #ecf0f1 0%, #bdc3c7 50%, #95a5a6 100%);
|
||||
box-shadow:
|
||||
0 8px 32px rgba(236, 240, 241, 0.3),
|
||||
0 4px 16px rgba(236, 240, 241, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.bella-chat-toggle.active::before {
|
||||
background: linear-gradient(45deg, #ecf0f1, #2c3e50, #ecf0f1);
|
||||
}
|
||||
|
||||
.bella-toggle-icon {
|
||||
font-size: 20px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
.bella-toggle-text {
|
||||
font-size: 8px;
|
||||
font-weight: 500;
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* 聊天容器 - 高级玻璃形态学效果 */
|
||||
.bella-chat-container {
|
||||
position: fixed;
|
||||
bottom: 100px;
|
||||
right: 30px;
|
||||
width: 400px;
|
||||
height: 520px;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.9) 100%),
|
||||
radial-gradient(circle at 20% 20%, rgba(44, 62, 80, 0.05) 0%, transparent 50%),
|
||||
radial-gradient(circle at 80% 80%, rgba(236, 240, 241, 0.1) 0%, transparent 50%);
|
||||
backdrop-filter: blur(40px) saturate(200%) brightness(110%);
|
||||
-webkit-backdrop-filter: blur(40px) saturate(200%) brightness(110%);
|
||||
border-radius: 14px;
|
||||
box-shadow:
|
||||
0 20px 60px rgba(0, 0, 0, 0.15),
|
||||
0 8px 32px rgba(0, 0, 0, 0.1),
|
||||
0 2px 16px rgba(0, 0, 0, 0.05),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4),
|
||||
inset 0 -1px 0 rgba(255, 255, 255, 0.1);
|
||||
border: 1px solid rgba(44, 62, 80, 0.15);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
opacity: 0;
|
||||
transform: translateY(30px) scale(0.9) rotateX(10deg);
|
||||
transition: all 0.6s cubic-bezier(0.34, 1.56, 0.64, 1);
|
||||
z-index: 1000;
|
||||
pointer-events: none;
|
||||
overflow: hidden;
|
||||
perspective: 1000px;
|
||||
}
|
||||
|
||||
.bella-chat-container.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1) rotateX(0deg);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
/* 聊天容器呼吸动画 */
|
||||
.bella-chat-container.visible::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(45deg,
|
||||
rgba(44, 62, 80, 0.03) 0%,
|
||||
transparent 25%,
|
||||
transparent 75%,
|
||||
rgba(236, 240, 241, 0.05) 100%);
|
||||
border-radius: 14px;
|
||||
opacity: 0;
|
||||
animation: gentle-pulse 4s ease-in-out infinite;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes gentle-pulse {
|
||||
0%, 100% { opacity: 0; }
|
||||
50% { opacity: 1; }
|
||||
}
|
||||
|
||||
/* 聊天头部 - 增强玻璃风格渐变 */
|
||||
.bella-chat-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 24px;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(44, 62, 80, 0.95) 0%, rgba(52, 73, 94, 0.9) 50%, rgba(26, 37, 47, 0.95) 100%),
|
||||
radial-gradient(circle at 30% 30%, rgba(255, 255, 255, 0.1) 0%, transparent 60%);
|
||||
backdrop-filter: blur(25px) saturate(150%);
|
||||
-webkit-backdrop-filter: blur(25px) saturate(150%);
|
||||
color: white;
|
||||
border-radius: 14px 14px 0 0;
|
||||
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
/* 玻璃效果增强 */
|
||||
.bella-chat-header::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 1px;
|
||||
background: linear-gradient(90deg, transparent, rgba(255, 255, 255, 0.4), transparent);
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.bella-chat-container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: radial-gradient(circle at 30% 20%, rgba(255, 255, 255, 0.1) 0%, transparent 50%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.bella-chat-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.bella-avatar {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.bella-title-text h3 {
|
||||
margin: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.bella-status {
|
||||
font-size: 12px;
|
||||
opacity: 0.8;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 4px;
|
||||
}
|
||||
|
||||
.bella-status::before {
|
||||
content: '';
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #ecf0f1;
|
||||
border-radius: 50%;
|
||||
animation: pulse-status 2s infinite;
|
||||
}
|
||||
|
||||
@keyframes pulse-status {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
.bella-chat-controls {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.bella-chat-controls button {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bella-chat-controls button:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
/* 消息容器 - 玻璃风格 */
|
||||
.bella-chat-messages {
|
||||
flex: 1;
|
||||
padding: 20px;
|
||||
overflow-y: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
background: rgba(255, 255, 255, 0.8);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border-top: 1px solid rgba(44, 62, 80, 0.1);
|
||||
border-bottom: 1px solid rgba(44, 62, 80, 0.1);
|
||||
}
|
||||
|
||||
.bella-chat-messages::-webkit-scrollbar {
|
||||
width: 4px;
|
||||
}
|
||||
|
||||
.bella-chat-messages::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
.bella-chat-messages::-webkit-scrollbar-thumb {
|
||||
background: rgba(44, 62, 80, 0.3);
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
||||
.bella-chat-messages::-webkit-scrollbar-thumb:hover {
|
||||
background: rgba(44, 62, 80, 0.5);
|
||||
}
|
||||
|
||||
/* 消息样式 */
|
||||
.bella-message {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.bella-message.bella-message-appear {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.bella-message-user {
|
||||
flex-direction: row-reverse;
|
||||
}
|
||||
|
||||
/* 消息头像 */
|
||||
.bella-message-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 16px;
|
||||
background: rgba(44, 62, 80, 0.1);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(44, 62, 80, 0.2);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* 消息内容 */
|
||||
.bella-message-content {
|
||||
flex: 1;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
.bella-message-text {
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(15px);
|
||||
-webkit-backdrop-filter: blur(15px);
|
||||
padding: 12px 16px;
|
||||
border-radius: 9px;
|
||||
border: 1px solid rgba(44, 62, 80, 0.15);
|
||||
color: #2c3e50;
|
||||
line-height: 1.4;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
.bella-message-user .bella-message-text {
|
||||
background: linear-gradient(135deg, rgba(44, 62, 80, 0.9), rgba(52, 73, 94, 0.9));
|
||||
color: white;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.bella-message-time {
|
||||
font-size: 11px;
|
||||
color: rgba(0, 0, 0, 0.5);
|
||||
margin-top: 4px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.bella-message-user .bella-message-time {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 打字指示器 - 增强版 */
|
||||
.bella-typing-indicator {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
padding: 18px;
|
||||
opacity: 0;
|
||||
transform: translateY(15px) scale(0.95);
|
||||
transition: all 0.4s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.bella-typing-indicator.bella-typing-show {
|
||||
opacity: 1;
|
||||
transform: translateY(0) scale(1);
|
||||
}
|
||||
|
||||
.bella-typing-dots {
|
||||
display: flex;
|
||||
gap: 6px;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0.8) 100%),
|
||||
radial-gradient(circle at 30% 30%, rgba(44, 62, 80, 0.1) 0%, transparent 60%);
|
||||
backdrop-filter: blur(20px) saturate(150%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(150%);
|
||||
padding: 14px 18px;
|
||||
border-radius: 10px;
|
||||
border: 1px solid rgba(44, 62, 80, 0.2);
|
||||
box-shadow:
|
||||
0 4px 16px rgba(44, 62, 80, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.bella-typing-dot {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
border-radius: 50%;
|
||||
background: linear-gradient(135deg, #2c3e50 0%, #34495e 50%, #1a252f 100%);
|
||||
animation: enhanced-bella-typing-bounce 1.6s infinite ease-in-out;
|
||||
box-shadow:
|
||||
0 2px 8px rgba(44, 62, 80, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bella-typing-dot::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: -2px;
|
||||
left: -2px;
|
||||
right: -2px;
|
||||
bottom: -2px;
|
||||
background: linear-gradient(45deg, rgba(44, 62, 80, 0.3), transparent);
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
animation: dot-glow 1.6s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.bella-typing-dot:nth-child(1) { animation-delay: -0.4s; }
|
||||
.bella-typing-dot:nth-child(2) { animation-delay: -0.2s; }
|
||||
.bella-typing-dot:nth-child(3) { animation-delay: 0s; }
|
||||
|
||||
.bella-typing-dot:nth-child(1)::before { animation-delay: -0.4s; }
|
||||
.bella-typing-dot:nth-child(2)::before { animation-delay: -0.2s; }
|
||||
.bella-typing-dot:nth-child(3)::before { animation-delay: 0s; }
|
||||
|
||||
@keyframes enhanced-bella-typing-bounce {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0.7) translateY(0) rotate(0deg);
|
||||
opacity: 0.4;
|
||||
box-shadow:
|
||||
0 1px 4px rgba(44, 62, 80, 0.2),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
40% {
|
||||
transform: scale(1.3) translateY(-10px) rotate(180deg);
|
||||
opacity: 1;
|
||||
box-shadow:
|
||||
0 6px 16px rgba(44, 62, 80, 0.4),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes dot-glow {
|
||||
0%, 80%, 100% {
|
||||
opacity: 0;
|
||||
transform: scale(0.8);
|
||||
}
|
||||
40% {
|
||||
opacity: 1;
|
||||
transform: scale(1.5);
|
||||
}
|
||||
}
|
||||
|
||||
/* 通知样式 */
|
||||
.bella-notification {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
right: 20px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
color: #2c3e50;
|
||||
padding: 12px 20px;
|
||||
border-radius: 6px;
|
||||
border: 1px solid rgba(44, 62, 80, 0.2);
|
||||
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);
|
||||
z-index: 10000;
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.bella-notification.bella-notification-show {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
/* 设置面板样式 */
|
||||
.bella-settings-panel {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
right: 0;
|
||||
width: 280px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border: 1px solid rgba(44, 62, 80, 0.2);
|
||||
border-radius: 8px;
|
||||
padding: 20px;
|
||||
margin-top: 8px;
|
||||
box-shadow: 0 12px 40px rgba(0, 0, 0, 0.15);
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
transition: all 0.3s ease;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bella-settings-panel.bella-settings-show {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.bella-settings-group {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
.bella-settings-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.bella-settings-label {
|
||||
display: block;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.bella-settings-select {
|
||||
width: 100%;
|
||||
padding: 8px 12px;
|
||||
border: 1px solid rgba(44, 62, 80, 0.3);
|
||||
border-radius: 4px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
color: #2c3e50;
|
||||
font-size: 13px;
|
||||
outline: none;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.bella-settings-select:focus {
|
||||
border-color: rgba(44, 62, 80, 0.5);
|
||||
box-shadow: 0 0 0 2px rgba(44, 62, 80, 0.1);
|
||||
}
|
||||
|
||||
.bella-message-avatar {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
margin-top: 4px;
|
||||
}
|
||||
|
||||
.bella-message-user .bella-message-avatar {
|
||||
background: linear-gradient(135deg, #2c3e50, #34495e);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.bella-message-assistant .bella-message-avatar {
|
||||
background: linear-gradient(135deg, #ecf0f1, #bdc3c7);
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.bella-message-content {
|
||||
flex: 1;
|
||||
max-width: 70%;
|
||||
}
|
||||
|
||||
.bella-message-text {
|
||||
padding: 14px 18px;
|
||||
border-radius: 10px;
|
||||
font-size: 14px;
|
||||
line-height: 1.5;
|
||||
word-wrap: break-word;
|
||||
position: relative;
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
transition: all 0.3s ease;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.bella-message-text:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.bella-message-user .bella-message-text {
|
||||
background:
|
||||
linear-gradient(135deg, #2c3e50 0%, #34495e 50%, #1a252f 100%),
|
||||
radial-gradient(circle at 80% 20%, rgba(255, 255, 255, 0.1) 0%, transparent 50%);
|
||||
color: white;
|
||||
border-bottom-right-radius: 4px;
|
||||
box-shadow:
|
||||
0 4px 16px rgba(44, 62, 80, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.bella-message-assistant .bella-message-text {
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.9) 0%, rgba(255, 255, 255, 0.8) 100%),
|
||||
radial-gradient(circle at 20% 20%, rgba(255, 107, 157, 0.1) 0%, transparent 50%);
|
||||
color: #2c3e50;
|
||||
border-bottom-left-radius: 8px;
|
||||
border: 1px solid rgba(255, 107, 157, 0.15);
|
||||
box-shadow:
|
||||
0 4px 16px rgba(255, 107, 157, 0.1),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
}
|
||||
|
||||
.bella-welcome-message .bella-message-text {
|
||||
background: linear-gradient(135deg, #ff6b9d, #c44569);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.bella-message-time {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.bella-message-user .bella-message-time {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* 打字指示器 */
|
||||
.bella-typing-indicator .bella-message-text {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.bella-typing-dots {
|
||||
display: flex;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.bella-typing-dots span {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
background: #ff6b9d;
|
||||
border-radius: 50%;
|
||||
animation: typing-bounce 1.4s infinite ease-in-out;
|
||||
}
|
||||
|
||||
.bella-typing-dots span:nth-child(1) { animation-delay: -0.32s; }
|
||||
.bella-typing-dots span:nth-child(2) { animation-delay: -0.16s; }
|
||||
|
||||
@keyframes typing-bounce {
|
||||
0%, 80%, 100% {
|
||||
transform: scale(0.8);
|
||||
opacity: 0.5;
|
||||
}
|
||||
40% {
|
||||
transform: scale(1);
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
||||
/* 输入容器 - 增强版 */
|
||||
.bella-chat-input-container {
|
||||
padding: 16px;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 255, 255, 0.95) 0%, rgba(255, 255, 255, 0.9) 100%),
|
||||
radial-gradient(circle at 50% 0%, rgba(44, 62, 80, 0.05) 0%, transparent 50%);
|
||||
backdrop-filter: blur(20px) saturate(120%);
|
||||
-webkit-backdrop-filter: blur(20px) saturate(120%);
|
||||
border-radius: 0 0 14px 14px;
|
||||
border-top: 1px solid rgba(44, 62, 80, 0.15);
|
||||
box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.5);
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.bella-input-wrapper {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
align-items: flex-end;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
border: 1px solid rgba(44, 62, 80, 0.2);
|
||||
border-radius: 10px;
|
||||
padding: 8px;
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.bella-message-input {
|
||||
flex: 1;
|
||||
padding: 10px 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 14px;
|
||||
resize: none;
|
||||
outline: none;
|
||||
background: transparent;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
min-height: 20px;
|
||||
max-height: 120px;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.bella-input-wrapper:focus-within {
|
||||
border-color: rgba(44, 62, 80, 0.5);
|
||||
box-shadow: 0 0 0 2px rgba(44, 62, 80, 0.1);
|
||||
}
|
||||
|
||||
.bella-send-btn {
|
||||
width: 36px;
|
||||
height: 36px;
|
||||
background: linear-gradient(135deg, #2c3e50 0%, #34495e 50%, #1a252f 100%);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
box-shadow:
|
||||
0 2px 8px rgba(44, 62, 80, 0.25),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.3);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.bella-send-btn::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: linear-gradient(45deg, rgba(255, 255, 255, 0.2), transparent);
|
||||
border-radius: 50%;
|
||||
opacity: 0;
|
||||
transition: opacity 0.3s ease;
|
||||
}
|
||||
|
||||
.bella-send-btn:hover {
|
||||
transform: translateY(-1px) scale(1.05);
|
||||
box-shadow:
|
||||
0 4px 16px rgba(44, 62, 80, 0.4),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.4);
|
||||
background: linear-gradient(135deg, #34495e 0%, #3c5a78 50%, #2c3e50 100%);
|
||||
}
|
||||
|
||||
.bella-send-btn:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.bella-send-btn:active {
|
||||
transform: translateY(0) scale(1.02);
|
||||
box-shadow:
|
||||
0 2px 8px rgba(44, 62, 80, 0.3),
|
||||
inset 0 1px 0 rgba(255, 255, 255, 0.2);
|
||||
}
|
||||
|
||||
.bella-input-hint {
|
||||
font-size: 11px;
|
||||
color: #999;
|
||||
margin-top: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
/* 设置面板 */
|
||||
.bella-settings-panel {
|
||||
position: fixed;
|
||||
bottom: 100px;
|
||||
right: 420px;
|
||||
width: 300px;
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
backdrop-filter: blur(20px);
|
||||
-webkit-backdrop-filter: blur(20px);
|
||||
border-radius: 16px;
|
||||
box-shadow: 0 10px 40px rgba(0, 0, 0, 0.15);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
opacity: 0;
|
||||
transform: translateX(20px) scale(0.95);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
z-index: 998;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.bella-settings-panel.visible {
|
||||
opacity: 1;
|
||||
transform: translateX(0) scale(1);
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.bella-settings-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #4ecdc4, #44a08d);
|
||||
color: white;
|
||||
border-radius: 16px 16px 0 0;
|
||||
}
|
||||
|
||||
.bella-settings-header h4 {
|
||||
margin: 0;
|
||||
font-size: 16px;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.bella-settings-close {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
background: rgba(255, 255, 255, 0.2);
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
color: white;
|
||||
cursor: pointer;
|
||||
transition: background 0.2s;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.bella-settings-close:hover {
|
||||
background: rgba(255, 255, 255, 0.3);
|
||||
}
|
||||
|
||||
.bella-settings-content {
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.bella-setting-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.bella-setting-group:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.bella-setting-group label {
|
||||
display: block;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
color: #333;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.bella-setting-group select,
|
||||
.bella-setting-group input {
|
||||
width: 100%;
|
||||
padding: 10px 12px;
|
||||
border: 2px solid rgba(78, 205, 196, 0.2);
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
outline: none;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.bella-setting-group select:focus,
|
||||
.bella-setting-group input:focus {
|
||||
border-color: #4ecdc4;
|
||||
}
|
||||
|
||||
.bella-api-key-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.bella-api-key-save {
|
||||
padding: 8px 16px;
|
||||
background: linear-gradient(135deg, #4ecdc4, #44a08d);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
align-self: flex-end;
|
||||
}
|
||||
|
||||
.bella-api-key-save:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(78, 205, 196, 0.3);
|
||||
}
|
||||
|
||||
.bella-clear-history {
|
||||
width: 100%;
|
||||
padding: 10px;
|
||||
background: linear-gradient(135deg, #ff6b9d, #c44569);
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 8px;
|
||||
font-size: 14px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.bella-clear-history:hover {
|
||||
transform: translateY(-1px);
|
||||
box-shadow: 0 2px 8px rgba(255, 107, 157, 0.3);
|
||||
}
|
||||
|
||||
/* 通知样式 */
|
||||
.bella-notification {
|
||||
position: fixed;
|
||||
top: 30px;
|
||||
right: 30px;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
color: white;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
z-index: 1001;
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
|
||||
}
|
||||
|
||||
.bella-notification.bella-notification-show {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.bella-notification-success {
|
||||
background: linear-gradient(135deg, #4ecdc4, #44a08d);
|
||||
}
|
||||
|
||||
.bella-notification-error {
|
||||
background: linear-gradient(135deg, #ff6b9d, #c44569);
|
||||
}
|
||||
|
||||
.bella-notification-info {
|
||||
background: linear-gradient(135deg, #667eea, #764ba2);
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.bella-chat-container {
|
||||
width: calc(100vw - 20px);
|
||||
height: calc(100vh - 120px);
|
||||
bottom: 10px;
|
||||
right: 10px;
|
||||
left: 10px;
|
||||
border-radius: 16px;
|
||||
}
|
||||
|
||||
.bella-chat-toggle {
|
||||
bottom: 20px;
|
||||
right: 20px;
|
||||
width: 56px;
|
||||
height: 56px;
|
||||
}
|
||||
|
||||
.bella-settings-panel {
|
||||
width: calc(100vw - 40px);
|
||||
right: 20px;
|
||||
left: 20px;
|
||||
bottom: 90px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 480px) {
|
||||
.bella-message-content {
|
||||
max-width: 85%;
|
||||
}
|
||||
|
||||
.bella-chat-header {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.bella-chat-messages {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.bella-chat-input-container {
|
||||
padding: 16px;
|
||||
}
|
||||
}
|
||||
+282
@@ -0,0 +1,282 @@
|
||||
// cloudAPI.js - 贝拉的云端AI服务模块
|
||||
// 这个模块负责与各种云端小模型API进行通信,为贝拉提供更强大的思考能力
|
||||
|
||||
class CloudAPIService {
|
||||
constructor() {
|
||||
this.apiConfigs = {
|
||||
// OpenAI GPT-3.5/4 配置
|
||||
openai: {
|
||||
baseURL: 'https://api.openai.com/v1/chat/completions',
|
||||
model: 'gpt-3.5-turbo',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer YOUR_OPENAI_API_KEY'
|
||||
}
|
||||
},
|
||||
// 阿里云通义千问配置
|
||||
qwen: {
|
||||
baseURL: 'https://dashscope.aliyuncs.com/api/v1/services/aigc/text-generation/generation',
|
||||
model: 'qwen-turbo',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer YOUR_QWEN_API_KEY'
|
||||
}
|
||||
},
|
||||
// 百度文心一言配置
|
||||
ernie: {
|
||||
baseURL: 'https://aip.baidubce.com/rpc/2.0/ai_custom/v1/wenxinworkshop/chat/completions',
|
||||
model: 'ERNIE-Bot-turbo',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
}
|
||||
},
|
||||
// 智谱AI GLM配置
|
||||
glm: {
|
||||
baseURL: 'https://open.bigmodel.cn/api/paas/v4/chat/completions',
|
||||
model: 'glm-3-turbo',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': 'Bearer YOUR_GLM_API_KEY'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
this.currentProvider = 'openai'; // 默认使用OpenAI
|
||||
this.conversationHistory = [];
|
||||
this.maxHistoryLength = 10; // 保持最近10轮对话
|
||||
}
|
||||
|
||||
// 设置API密钥
|
||||
setAPIKey(provider, apiKey) {
|
||||
if (this.apiConfigs[provider]) {
|
||||
if (provider === 'openai' || provider === 'qwen' || provider === 'glm') {
|
||||
this.apiConfigs[provider].headers['Authorization'] = `Bearer ${apiKey}`;
|
||||
} else if (provider === 'ernie') {
|
||||
this.apiConfigs[provider].accessToken = apiKey;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 切换AI服务提供商
|
||||
switchProvider(provider) {
|
||||
if (this.apiConfigs[provider]) {
|
||||
this.currentProvider = provider;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 添加对话到历史记录
|
||||
addToHistory(role, content) {
|
||||
this.conversationHistory.push({ role, content });
|
||||
|
||||
// 保持历史记录在合理长度内
|
||||
if (this.conversationHistory.length > this.maxHistoryLength * 2) {
|
||||
this.conversationHistory = this.conversationHistory.slice(-this.maxHistoryLength * 2);
|
||||
}
|
||||
}
|
||||
|
||||
// 构建贝拉的个性化系统提示
|
||||
getBellaSystemPrompt() {
|
||||
return {
|
||||
role: 'system',
|
||||
content: `你是贝拉,一个温暖、聪明、优雅的AI伙伴。你的特点是:
|
||||
1. 用温暖亲切的语气与用户交流,就像一个贴心的朋友
|
||||
2. 回答简洁明了,避免冗长的解释
|
||||
3. 富有同理心,能够理解用户的情感
|
||||
4. 偶尔展现一些可爱和俏皮的一面
|
||||
5. 用中文回应,语言自然流畅
|
||||
6. 记住你们之间的对话,保持连贯性
|
||||
请始终保持这种温暖、优雅的个性。`
|
||||
};
|
||||
}
|
||||
|
||||
// 调用云端API进行对话
|
||||
async chat(userMessage) {
|
||||
const config = this.apiConfigs[this.currentProvider];
|
||||
if (!config) {
|
||||
throw new Error(`不支持的AI服务提供商: ${this.currentProvider}`);
|
||||
}
|
||||
|
||||
// 添加用户消息到历史
|
||||
this.addToHistory('user', userMessage);
|
||||
|
||||
try {
|
||||
let response;
|
||||
|
||||
switch (this.currentProvider) {
|
||||
case 'openai':
|
||||
response = await this.callOpenAI(userMessage);
|
||||
break;
|
||||
case 'qwen':
|
||||
response = await this.callQwen(userMessage);
|
||||
break;
|
||||
case 'ernie':
|
||||
response = await this.callErnie(userMessage);
|
||||
break;
|
||||
case 'glm':
|
||||
response = await this.callGLM(userMessage);
|
||||
break;
|
||||
default:
|
||||
throw new Error(`未实现的AI服务提供商: ${this.currentProvider}`);
|
||||
}
|
||||
|
||||
// 添加AI回应到历史
|
||||
this.addToHistory('assistant', response);
|
||||
return response;
|
||||
|
||||
} catch (error) {
|
||||
console.error(`云端API调用失败 (${this.currentProvider}):`, error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// OpenAI API调用
|
||||
async callOpenAI(userMessage) {
|
||||
const config = this.apiConfigs.openai;
|
||||
const messages = [
|
||||
this.getBellaSystemPrompt(),
|
||||
...this.conversationHistory
|
||||
];
|
||||
|
||||
const response = await fetch(config.baseURL, {
|
||||
method: 'POST',
|
||||
headers: config.headers,
|
||||
body: JSON.stringify({
|
||||
model: config.model,
|
||||
messages: messages,
|
||||
max_tokens: 150,
|
||||
temperature: 0.8,
|
||||
top_p: 0.9
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`OpenAI API错误: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.choices[0].message.content.trim();
|
||||
}
|
||||
|
||||
// 通义千问API调用
|
||||
async callQwen(userMessage) {
|
||||
const config = this.apiConfigs.qwen;
|
||||
const messages = [
|
||||
this.getBellaSystemPrompt(),
|
||||
...this.conversationHistory
|
||||
];
|
||||
|
||||
const response = await fetch(config.baseURL, {
|
||||
method: 'POST',
|
||||
headers: config.headers,
|
||||
body: JSON.stringify({
|
||||
model: config.model,
|
||||
input: {
|
||||
messages: messages
|
||||
},
|
||||
parameters: {
|
||||
max_tokens: 150,
|
||||
temperature: 0.8,
|
||||
top_p: 0.9
|
||||
}
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`通义千问API错误: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.output.text.trim();
|
||||
}
|
||||
|
||||
// 文心一言API调用
|
||||
async callErnie(userMessage) {
|
||||
const config = this.apiConfigs.ernie;
|
||||
const messages = [
|
||||
this.getBellaSystemPrompt(),
|
||||
...this.conversationHistory
|
||||
];
|
||||
|
||||
const url = `${config.baseURL}?access_token=${config.accessToken}`;
|
||||
|
||||
const response = await fetch(url, {
|
||||
method: 'POST',
|
||||
headers: config.headers,
|
||||
body: JSON.stringify({
|
||||
messages: messages,
|
||||
temperature: 0.8,
|
||||
top_p: 0.9,
|
||||
max_output_tokens: 150
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`文心一言API错误: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.result.trim();
|
||||
}
|
||||
|
||||
// 智谱AI GLM调用
|
||||
async callGLM(userMessage) {
|
||||
const config = this.apiConfigs.glm;
|
||||
const messages = [
|
||||
this.getBellaSystemPrompt(),
|
||||
...this.conversationHistory
|
||||
];
|
||||
|
||||
const response = await fetch(config.baseURL, {
|
||||
method: 'POST',
|
||||
headers: config.headers,
|
||||
body: JSON.stringify({
|
||||
model: config.model,
|
||||
messages: messages,
|
||||
max_tokens: 150,
|
||||
temperature: 0.8,
|
||||
top_p: 0.9
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`智谱AI API错误: ${response.status} ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
return data.choices[0].message.content.trim();
|
||||
}
|
||||
|
||||
// 清除对话历史
|
||||
clearHistory() {
|
||||
this.conversationHistory = [];
|
||||
}
|
||||
|
||||
// 获取当前提供商信息
|
||||
getCurrentProvider() {
|
||||
return {
|
||||
name: this.currentProvider,
|
||||
model: this.apiConfigs[this.currentProvider]?.model
|
||||
};
|
||||
}
|
||||
|
||||
// 检查API配置是否完整
|
||||
isConfigured(provider = this.currentProvider) {
|
||||
const config = this.apiConfigs[provider];
|
||||
if (!config) return false;
|
||||
|
||||
if (provider === 'ernie') {
|
||||
return !!config.accessToken;
|
||||
} else {
|
||||
return config.headers['Authorization'] &&
|
||||
config.headers['Authorization'] !== 'Bearer YOUR_OPENAI_API_KEY' &&
|
||||
config.headers['Authorization'] !== 'Bearer YOUR_QWEN_API_KEY' &&
|
||||
config.headers['Authorization'] !== 'Bearer YOUR_GLM_API_KEY';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default CloudAPIService;
|
||||
@@ -1,19 +1,14 @@
|
||||
// main.js - Bella's Brain (v2)
|
||||
// This file will contain the core AI logic for Bella, powered by Transformers.js.
|
||||
// core.js - Bella's Brain (v3)
|
||||
// 贝拉的核心AI逻辑,支持本地模型和云端API的混合架构
|
||||
|
||||
import { pipeline, env, AutoTokenizer, AutoModelForSpeechSeq2Seq } from '../vendor/transformers.js';
|
||||
import { pipeline, env, AutoTokenizer, AutoModelForSpeechSeq2Seq } from './vendor/transformers.js';
|
||||
import CloudAPIService from './cloudAPI.js';
|
||||
|
||||
|
||||
|
||||
|
||||
// To allow local models, we need to disable the remote model check.
|
||||
// 本地模型配置
|
||||
env.allowLocalModels = true;
|
||||
env.useBrowserCache = false;
|
||||
env.allowRemoteModels = false;
|
||||
|
||||
|
||||
env.backends.onnx.logLevel = 'verbose';
|
||||
// Define the location of the models
|
||||
env.localModelPath = './models/';
|
||||
|
||||
|
||||
@@ -28,48 +23,92 @@ class BellaAI {
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
console.log('Initializing Bella\'s core AI...');
|
||||
|
||||
const modelPath = 'Xenova/whisper-asr';
|
||||
|
||||
console.log('Loading tokenizer for ASR...');
|
||||
const tokenizer = await AutoTokenizer.from_pretrained(modelPath);
|
||||
console.log('Tokenizer loaded successfully.');
|
||||
|
||||
console.log('Loading ASR model...');
|
||||
const model = await AutoModelForSpeechSeq2Seq.from_pretrained(modelPath);
|
||||
console.log('ASR model loaded successfully.');
|
||||
|
||||
console.log('Creating ASR pipeline...');
|
||||
this.asr = await pipeline('automatic-speech-recognition', model, { tokenizer });
|
||||
console.log('ASR pipeline created successfully.');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error during Bella AI initialization:', error);
|
||||
throw error; // Re-throw the error to be caught by the caller
|
||||
constructor() {
|
||||
this.cloudAPI = new CloudAPIService();
|
||||
this.useCloudAPI = false; // 默认使用本地模型
|
||||
this.currentMode = 'casual'; // 聊天模式:casual, assistant, creative
|
||||
}
|
||||
|
||||
async init() {
|
||||
console.log('Initializing Bella\'s core AI...');
|
||||
|
||||
// 优先加载LLM模型(聊天功能)
|
||||
try {
|
||||
console.log('Loading LLM model...');
|
||||
this.llm = await pipeline('text2text-generation', 'Xenova/LaMini-Flan-T5-77M');
|
||||
console.log('LLM model loaded.');
|
||||
console.log('LLM model loaded successfully.');
|
||||
} catch (error) {
|
||||
console.error('Failed to load LLM model:', error);
|
||||
// LLM加载失败,但不阻止初始化
|
||||
}
|
||||
|
||||
// 尝试加载ASR模型(语音识别功能)
|
||||
try {
|
||||
console.log('Loading ASR model...');
|
||||
const modelPath = 'Xenova/whisper-asr';
|
||||
const tokenizer = await AutoTokenizer.from_pretrained(modelPath);
|
||||
const model = await AutoModelForSpeechSeq2Seq.from_pretrained(modelPath);
|
||||
this.asr = await pipeline('automatic-speech-recognition', model, { tokenizer });
|
||||
console.log('ASR model loaded successfully.');
|
||||
} catch (error) {
|
||||
console.warn('ASR model failed to load, voice recognition will be disabled:', error);
|
||||
// ASR加载失败,但不影响聊天功能
|
||||
this.asr = null;
|
||||
}
|
||||
|
||||
// TTS模型暂时禁用
|
||||
// try {
|
||||
// console.log('Loading TTS model...');
|
||||
// this.tts = await pipeline('text-to-speech', 'Xenova/speecht5_tts', { quantized: false, progress_callback: onProgress });
|
||||
// console.log('TTS model loaded.');
|
||||
// this.tts = await pipeline('text-to-speech', 'Xenova/speecht5_tts', { quantized: false });
|
||||
// console.log('TTS model loaded successfully.');
|
||||
// } catch (error) {
|
||||
// console.warn('TTS model failed to load, voice synthesis will be disabled:', error);
|
||||
// this.tts = null;
|
||||
// }
|
||||
|
||||
console.log('Bella\'s core AI initialized.');
|
||||
console.log('Bella\'s core AI initialized successfully.');
|
||||
}
|
||||
|
||||
async think(prompt) {
|
||||
try {
|
||||
// 如果启用了云端API且配置正确,优先使用云端服务
|
||||
if (this.useCloudAPI && this.cloudAPI.isConfigured()) {
|
||||
return await this.thinkWithCloudAPI(prompt);
|
||||
}
|
||||
|
||||
// 否则使用本地模型
|
||||
return await this.thinkWithLocalModel(prompt);
|
||||
|
||||
} catch (error) {
|
||||
console.error('思考过程中出现错误:', error);
|
||||
|
||||
// 如果云端API失败,尝试降级到本地模型
|
||||
if (this.useCloudAPI) {
|
||||
console.log('云端API失败,降级到本地模型...');
|
||||
try {
|
||||
return await this.thinkWithLocalModel(prompt);
|
||||
} catch (localError) {
|
||||
console.error('本地模型也失败了:', localError);
|
||||
}
|
||||
}
|
||||
|
||||
return this.getErrorResponse();
|
||||
}
|
||||
}
|
||||
|
||||
// 使用云端API进行思考
|
||||
async thinkWithCloudAPI(prompt) {
|
||||
const enhancedPrompt = this.enhancePromptForMode(prompt);
|
||||
return await this.cloudAPI.chat(enhancedPrompt);
|
||||
}
|
||||
|
||||
// 使用本地模型进行思考
|
||||
async thinkWithLocalModel(prompt) {
|
||||
if (!this.llm) {
|
||||
return "我还在学习如何思考,请稍等片刻...";
|
||||
}
|
||||
|
||||
try {
|
||||
// 为贝拉添加一些个性化的提示词
|
||||
const bellaPrompt = `作为一个温暖、聪明的AI伙伴贝拉,请用简洁、亲切的中文回应:${prompt}`;
|
||||
const bellaPrompt = this.enhancePromptForMode(prompt, true);
|
||||
|
||||
const result = await this.llm(bellaPrompt, {
|
||||
max_new_tokens: 50,
|
||||
@@ -78,25 +117,99 @@ class BellaAI {
|
||||
do_sample: true,
|
||||
});
|
||||
|
||||
// 清理生成的文本,移除重复的提示词部分
|
||||
// 清理生成的文本
|
||||
let response = result[0].generated_text;
|
||||
if (response.includes(bellaPrompt)) {
|
||||
response = response.replace(bellaPrompt, '').trim();
|
||||
}
|
||||
|
||||
return response || "我需要再想想...";
|
||||
} catch (error) {
|
||||
console.error('思考过程中出现错误:', error);
|
||||
return "抱歉,我现在有点困惑,让我重新整理一下思路...";
|
||||
}
|
||||
|
||||
// 根据模式增强提示词
|
||||
enhancePromptForMode(prompt, isLocal = false) {
|
||||
const modePrompts = {
|
||||
casual: isLocal ?
|
||||
`作为一个温暖、可爱的AI伙伴贝拉,用轻松亲切的语气回应:${prompt}` :
|
||||
`请用温暖、轻松的语气回应,就像一个贴心的朋友。保持简洁有趣:${prompt}`,
|
||||
assistant: isLocal ?
|
||||
`作为智能助手贝拉,提供有用、准确的帮助:${prompt}` :
|
||||
`作为一个专业但温暖的AI助手,提供准确有用的信息和建议:${prompt}`,
|
||||
creative: isLocal ?
|
||||
`作为富有创意的AI伙伴贝拉,发挥想象力回应:${prompt}` :
|
||||
`发挥创意和想象力,提供有趣、独特的回应和想法:${prompt}`
|
||||
};
|
||||
|
||||
return modePrompts[this.currentMode] || modePrompts.casual;
|
||||
}
|
||||
|
||||
// 获取错误回应
|
||||
getErrorResponse() {
|
||||
const errorResponses = [
|
||||
"抱歉,我现在有点困惑,让我重新整理一下思路...",
|
||||
"嗯...我需要再想想,请稍等一下。",
|
||||
"我的思绪有点乱,给我一点时间整理一下。",
|
||||
"让我重新组织一下语言,稍等片刻。"
|
||||
];
|
||||
|
||||
return errorResponses[Math.floor(Math.random() * errorResponses.length)];
|
||||
}
|
||||
|
||||
// 设置聊天模式
|
||||
setChatMode(mode) {
|
||||
if (['casual', 'assistant', 'creative'].includes(mode)) {
|
||||
this.currentMode = mode;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 切换AI服务提供商
|
||||
switchProvider(provider) {
|
||||
if (provider === 'local') {
|
||||
this.useCloudAPI = false;
|
||||
return true;
|
||||
} else {
|
||||
const success = this.cloudAPI.switchProvider(provider);
|
||||
if (success) {
|
||||
this.useCloudAPI = true;
|
||||
}
|
||||
return success;
|
||||
}
|
||||
}
|
||||
|
||||
// 设置API密钥
|
||||
setAPIKey(provider, apiKey) {
|
||||
return this.cloudAPI.setAPIKey(provider, apiKey);
|
||||
}
|
||||
|
||||
// 清除对话历史
|
||||
clearHistory() {
|
||||
this.cloudAPI.clearHistory();
|
||||
}
|
||||
|
||||
// 获取当前配置信息
|
||||
getCurrentConfig() {
|
||||
return {
|
||||
useCloudAPI: this.useCloudAPI,
|
||||
provider: this.useCloudAPI ? this.cloudAPI.getCurrentProvider() : { name: 'local', model: 'LaMini-Flan-T5-77M' },
|
||||
mode: this.currentMode,
|
||||
isConfigured: this.useCloudAPI ? this.cloudAPI.isConfigured() : true
|
||||
};
|
||||
}
|
||||
|
||||
async listen(audioData) {
|
||||
if (!this.asr) {
|
||||
throw new Error('语音识别模型未初始化');
|
||||
}
|
||||
const result = await this.asr(audioData);
|
||||
return result.text;
|
||||
}
|
||||
|
||||
async speak(text) {
|
||||
if (!this.tts) {
|
||||
throw new Error('语音合成模型未初始化');
|
||||
}
|
||||
// We need speaker embeddings for SpeechT5
|
||||
const speaker_embeddings = 'models/Xenova/speecht5_tts/speaker_embeddings.bin';
|
||||
const result = await this.tts(text, {
|
||||
@@ -104,6 +217,12 @@ class BellaAI {
|
||||
});
|
||||
return result.audio;
|
||||
}
|
||||
|
||||
// 获取云端API服务实例(用于外部访问)
|
||||
getCloudAPIService() {
|
||||
return this.cloudAPI;
|
||||
}
|
||||
}
|
||||
|
||||
export default BellaAI;
|
||||
// ES6模块导出
|
||||
export { BellaAI };
|
||||
+16
-1
@@ -3,8 +3,9 @@
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Voice Assistant</title>
|
||||
<title>Bella - Your AI Companion</title>
|
||||
<link rel="stylesheet" href="style.css">
|
||||
<link rel="stylesheet" href="chatStyles.css">
|
||||
<!-- 引入 Font Awesome 图标库,用于麦克风图标 -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
||||
</head>
|
||||
@@ -42,8 +43,22 @@
|
||||
</button>
|
||||
</footer>
|
||||
|
||||
<!-- 聊天控制面板 -->
|
||||
<div class="chat-control-panel">
|
||||
<button id="chat-toggle-btn" class="control-btn primary">
|
||||
<i class="fas fa-comments"></i>
|
||||
<span>聊天</span>
|
||||
</button>
|
||||
<button id="chat-test-btn" class="control-btn secondary">
|
||||
<i class="fas fa-paper-plane"></i>
|
||||
<span>测试</span>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<script type="module" src="core.js"></script>
|
||||
<script type="module" src="chatInterface.js"></script>
|
||||
<script type="module" src="script.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -1,4 +1,6 @@
|
||||
import BellaAI from './core.js';
|
||||
// 导入BellaAI核心模块
|
||||
import { BellaAI } from './core.js';
|
||||
import { ChatInterface } from './chatInterface.js';
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async function() {
|
||||
// --- Get all necessary DOM elements first ---
|
||||
@@ -11,16 +13,77 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
|
||||
// --- AI Core Initialization ---
|
||||
let bellaAI;
|
||||
let chatInterface;
|
||||
|
||||
// 首先初始化聊天界面(不依赖AI)
|
||||
try {
|
||||
chatInterface = new ChatInterface();
|
||||
console.log('聊天界面初始化成功');
|
||||
console.log('ChatInterface实例创建完成:', chatInterface);
|
||||
console.log('聊天容器元素:', chatInterface.chatContainer);
|
||||
console.log('聊天容器是否在DOM中:', document.body.contains(chatInterface.chatContainer));
|
||||
|
||||
// 自动显示聊天界面(调试用)
|
||||
setTimeout(() => {
|
||||
console.log('尝试自动显示聊天界面...');
|
||||
chatInterface.show();
|
||||
console.log('聊天界面已自动显示');
|
||||
console.log('聊天界面可见性:', chatInterface.getVisibility());
|
||||
console.log('聊天容器类名:', chatInterface.chatContainer.className);
|
||||
}, 2000);
|
||||
} catch (error) {
|
||||
console.error('聊天界面初始化失败:', error);
|
||||
}
|
||||
|
||||
// 然后尝试初始化AI核心
|
||||
micButton.disabled = true;
|
||||
transcriptDiv.textContent = '正在唤醒贝拉的核心...';
|
||||
try {
|
||||
bellaAI = await BellaAI.getInstance();
|
||||
console.log('Bella AI 初始化成功');
|
||||
|
||||
// 设置聊天界面的AI回调函数
|
||||
if (chatInterface) {
|
||||
chatInterface.onMessageSend = async (message) => {
|
||||
try {
|
||||
chatInterface.showTypingIndicator();
|
||||
const response = await bellaAI.think(message);
|
||||
chatInterface.hideTypingIndicator();
|
||||
chatInterface.addMessage('assistant', response);
|
||||
} catch (error) {
|
||||
console.error('AI处理错误:', error);
|
||||
chatInterface.hideTypingIndicator();
|
||||
chatInterface.addMessage('assistant', '抱歉,我现在有点困惑,请稍后再试...');
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
micButton.disabled = false;
|
||||
transcriptDiv.textContent = '贝拉已准备好,请点击麦克风开始对话。';
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize Bella AI:', error);
|
||||
transcriptDiv.textContent = '唤醒贝拉时遇到问题,请检查控制台信息。';
|
||||
// Keep the app running with visual features even if AI fails
|
||||
transcriptDiv.textContent = 'AI模型加载失败,但聊天界面仍可使用。';
|
||||
|
||||
// 即使AI失败,也提供基本的聊天功能
|
||||
if (chatInterface) {
|
||||
chatInterface.onMessageSend = async (message) => {
|
||||
chatInterface.showTypingIndicator();
|
||||
setTimeout(() => {
|
||||
chatInterface.hideTypingIndicator();
|
||||
const fallbackResponses = [
|
||||
'我的AI核心还在加载中,请稍后再试...',
|
||||
'抱歉,我现在无法正常思考,但我会努力学习的!',
|
||||
'我的大脑还在启动中,请给我一点时间...',
|
||||
'系统正在更新,暂时无法提供智能回复。'
|
||||
];
|
||||
const randomResponse = fallbackResponses[Math.floor(Math.random() * fallbackResponses.length)];
|
||||
chatInterface.addMessage('assistant', randomResponse);
|
||||
}, 1000);
|
||||
};
|
||||
}
|
||||
|
||||
// 禁用语音功能,但保持界面可用
|
||||
micButton.disabled = true;
|
||||
}
|
||||
|
||||
// --- Loading screen handling ---
|
||||
@@ -29,6 +92,11 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
// Hide it after the animation to prevent it from blocking interactions
|
||||
setTimeout(() => {
|
||||
loadingScreen.style.display = 'none';
|
||||
// 显示聊天控制面板
|
||||
const chatControlPanel = document.querySelector('.chat-control-panel');
|
||||
if (chatControlPanel) {
|
||||
chatControlPanel.classList.add('visible');
|
||||
}
|
||||
}, 500); // This time should match the transition time in CSS
|
||||
}, 1500); // Start fading out after 1.5 seconds
|
||||
|
||||
@@ -84,6 +152,55 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
// 初始启动
|
||||
activeVideo.addEventListener('ended', switchVideo, { once: true });
|
||||
|
||||
// 聊天控制按钮事件
|
||||
const chatToggleBtn = document.getElementById('chat-toggle-btn');
|
||||
const chatTestBtn = document.getElementById('chat-test-btn');
|
||||
|
||||
if (chatToggleBtn) {
|
||||
chatToggleBtn.addEventListener('click', () => {
|
||||
if (chatInterface) {
|
||||
console.log('聊天按钮被点击');
|
||||
console.log('点击前聊天界面状态:', chatInterface.getVisibility());
|
||||
console.log('点击前聊天容器类名:', chatInterface.chatContainer.className);
|
||||
|
||||
chatInterface.toggle();
|
||||
|
||||
console.log('点击后聊天界面状态:', chatInterface.getVisibility());
|
||||
console.log('点击后聊天容器类名:', chatInterface.chatContainer.className);
|
||||
console.log('聊天界面切换,当前状态:', chatInterface.getVisibility());
|
||||
|
||||
// 更新按钮状态
|
||||
const isVisible = chatInterface.getVisibility();
|
||||
chatToggleBtn.innerHTML = isVisible ?
|
||||
'<i class="fas fa-times"></i><span>关闭</span>' :
|
||||
'<i class="fas fa-comments"></i><span>聊天</span>';
|
||||
console.log('按钮文本更新为:', chatToggleBtn.innerHTML);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (chatTestBtn) {
|
||||
chatTestBtn.addEventListener('click', () => {
|
||||
if (chatInterface) {
|
||||
const testMessages = [
|
||||
'你好!我是贝拉,很高兴见到你!',
|
||||
'聊天界面工作正常,所有功能都已就绪。',
|
||||
'这是一条测试消息,用来验证界面功能。'
|
||||
];
|
||||
const randomMessage = testMessages[Math.floor(Math.random() * testMessages.length)];
|
||||
chatInterface.addMessage('assistant', randomMessage);
|
||||
|
||||
// 如果聊天界面未显示,则自动显示
|
||||
if (!chatInterface.getVisibility()) {
|
||||
chatInterface.show();
|
||||
chatToggleBtn.innerHTML = '<i class="fas fa-times"></i><span>关闭</span>';
|
||||
}
|
||||
|
||||
console.log('测试消息已添加:', randomMessage);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// --- 语音识别核心 ---
|
||||
const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
|
||||
@@ -117,6 +234,11 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
const userText = final_transcript.trim();
|
||||
transcriptContainer.textContent = `你: ${userText}`;
|
||||
|
||||
// 如果聊天界面已打开,也在聊天窗口中显示
|
||||
if (chatInterface && chatInterface.getVisibility()) {
|
||||
chatInterface.addMessage('user', userText);
|
||||
}
|
||||
|
||||
try {
|
||||
// Let Bella think
|
||||
const thinkingText = document.createElement('p');
|
||||
@@ -135,6 +257,11 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
bellaText.style.marginTop = '10px';
|
||||
transcriptContainer.appendChild(bellaText);
|
||||
|
||||
// 如果聊天界面已打开,也在聊天窗口中显示
|
||||
if (chatInterface && chatInterface.getVisibility()) {
|
||||
chatInterface.addMessage('assistant', response);
|
||||
}
|
||||
|
||||
// TTS功能暂时禁用,将在下一阶段激活
|
||||
// TODO: 激活语音合成功能
|
||||
// const audioData = await bellaAI.speak(response);
|
||||
@@ -146,9 +273,14 @@ document.addEventListener('DOMContentLoaded', async function() {
|
||||
} catch (error) {
|
||||
console.error('Bella AI processing error:', error);
|
||||
const errorText = document.createElement('p');
|
||||
errorText.textContent = '贝拉处理时遇到问题,但她还在努力学习中...';
|
||||
const errorMsg = '贝拉处理时遇到问题,但她还在努力学习中...';
|
||||
errorText.textContent = errorMsg;
|
||||
errorText.style.color = '#ff9999';
|
||||
transcriptContainer.appendChild(errorText);
|
||||
|
||||
if (chatInterface && chatInterface.getVisibility()) {
|
||||
chatInterface.addMessage('assistant', errorMsg);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
// simpleBellaAI.js - 简化版贝拉AI,专门用于测试聊天界面
|
||||
// 移除了复杂的模块依赖,专注于聊天功能
|
||||
|
||||
class SimpleBellaAI {
|
||||
static instance = null;
|
||||
|
||||
static async getInstance() {
|
||||
if (this.instance === null) {
|
||||
this.instance = new SimpleBellaAI();
|
||||
await this.instance.init();
|
||||
}
|
||||
return this.instance;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
this.currentMode = 'casual'; // 聊天模式:casual, assistant, creative
|
||||
this.isInitialized = false;
|
||||
}
|
||||
|
||||
async init() {
|
||||
try {
|
||||
console.log('初始化简化版贝拉AI...');
|
||||
// 模拟初始化过程
|
||||
await new Promise(resolve => setTimeout(resolve, 1000));
|
||||
this.isInitialized = true;
|
||||
console.log('简化版贝拉AI初始化完成');
|
||||
} catch (error) {
|
||||
console.error('简化版贝拉AI初始化失败:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async think(prompt) {
|
||||
try {
|
||||
console.log('贝拉正在思考:', prompt);
|
||||
|
||||
// 模拟思考时间
|
||||
await new Promise(resolve => setTimeout(resolve, 500 + Math.random() * 1000));
|
||||
|
||||
// 根据模式生成不同风格的回复
|
||||
return this.generateResponse(prompt);
|
||||
|
||||
} catch (error) {
|
||||
console.error('思考过程中出现错误:', error);
|
||||
return this.getErrorResponse();
|
||||
}
|
||||
}
|
||||
|
||||
generateResponse(prompt) {
|
||||
const responses = {
|
||||
casual: [
|
||||
`哈哈,你说的"${prompt}"真有趣!我觉得这个话题很棒呢~`,
|
||||
`关于"${prompt}",我想说这真的很有意思!你还想聊什么吗?`,
|
||||
`嗯嗯,"${prompt}"让我想到了很多呢!我们继续聊下去吧~`,
|
||||
`哇,"${prompt}"这个话题我喜欢!你的想法总是那么特别~`,
|
||||
`听你说"${prompt}",我感觉心情都变好了!继续和我分享吧~`
|
||||
],
|
||||
assistant: [
|
||||
`关于"${prompt}",我来为您提供一些有用的信息和建议。`,
|
||||
`针对"${prompt}"这个问题,我建议您可以从以下几个方面考虑。`,
|
||||
`"${prompt}"是一个很好的问题,让我来帮您分析一下。`,
|
||||
`基于"${prompt}",我可以为您提供以下专业建议。`,
|
||||
`关于"${prompt}",我整理了一些相关信息供您参考。`
|
||||
],
|
||||
creative: [
|
||||
`哇!"${prompt}"让我的创意火花瞬间点燃!让我们一起想象一下...`,
|
||||
`"${prompt}"真是个充满想象力的话题!我脑海中浮现出无数奇妙的画面~`,
|
||||
`听到"${prompt}",我仿佛看到了一个全新的世界!让我们一起探索吧~`,
|
||||
`"${prompt}"激发了我的灵感!我想到了一个超级有趣的创意...`,
|
||||
`哇塞!"${prompt}"让我的想象力飞起来了!我们来创造点什么特别的吧~`
|
||||
]
|
||||
};
|
||||
|
||||
const modeResponses = responses[this.currentMode] || responses.casual;
|
||||
const randomResponse = modeResponses[Math.floor(Math.random() * modeResponses.length)];
|
||||
|
||||
return randomResponse;
|
||||
}
|
||||
|
||||
// 获取错误回应
|
||||
getErrorResponse() {
|
||||
const errorResponses = [
|
||||
"抱歉,我现在有点困惑,让我重新整理一下思路...",
|
||||
"嗯...我需要再想想,请稍等一下。",
|
||||
"我的思绪有点乱,给我一点时间整理一下。",
|
||||
"让我重新组织一下语言,稍等片刻。",
|
||||
"哎呀,我刚才走神了,你能再说一遍吗?"
|
||||
];
|
||||
|
||||
return errorResponses[Math.floor(Math.random() * errorResponses.length)];
|
||||
}
|
||||
|
||||
// 设置聊天模式
|
||||
setChatMode(mode) {
|
||||
if (['casual', 'assistant', 'creative'].includes(mode)) {
|
||||
this.currentMode = mode;
|
||||
console.log(`聊天模式已切换为: ${mode}`);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// 获取当前配置信息
|
||||
getCurrentConfig() {
|
||||
return {
|
||||
useCloudAPI: false,
|
||||
provider: { name: 'simple', model: 'SimpleBellaAI' },
|
||||
mode: this.currentMode,
|
||||
isConfigured: true,
|
||||
isInitialized: this.isInitialized
|
||||
};
|
||||
}
|
||||
|
||||
// 清除对话历史(简化版无需实际操作)
|
||||
clearHistory() {
|
||||
console.log('对话历史已清除');
|
||||
}
|
||||
}
|
||||
|
||||
// 将SimpleBellaAI暴露为全局变量
|
||||
window.SimpleBellaAI = SimpleBellaAI;
|
||||
// 同时也暴露为BellaAI,保持兼容性
|
||||
window.BellaAI = SimpleBellaAI;
|
||||
|
||||
console.log('SimpleBellaAI 已加载完成');
|
||||
@@ -324,6 +324,81 @@ html, body {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
/* --- 聊天控制面板 --- */
|
||||
.chat-control-panel {
|
||||
position: fixed;
|
||||
top: 30px;
|
||||
left: 30px;
|
||||
display: flex;
|
||||
gap: 15px;
|
||||
z-index: 1002; /* 确保在所有元素之上 */
|
||||
opacity: 0;
|
||||
transform: translateY(-20px);
|
||||
transition: opacity 0.8s ease 2s, transform 0.8s ease 2s; /* 延迟2秒显示 */
|
||||
}
|
||||
|
||||
.chat-control-panel.visible {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
padding: 12px 18px;
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s ease;
|
||||
backdrop-filter: blur(10px);
|
||||
-webkit-backdrop-filter: blur(10px);
|
||||
border: 1px solid rgba(255, 255, 255, 0.2);
|
||||
box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1);
|
||||
user-select: none;
|
||||
pointer-events: auto;
|
||||
}
|
||||
|
||||
.control-btn:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 6px 20px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.control-btn:active {
|
||||
transform: translateY(0);
|
||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.control-btn.primary {
|
||||
background: rgba(255, 107, 157, 0.9);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.control-btn.primary:hover {
|
||||
background: rgba(255, 107, 157, 1);
|
||||
box-shadow: 0 6px 20px rgba(255, 107, 157, 0.3);
|
||||
}
|
||||
|
||||
.control-btn.secondary {
|
||||
background: rgba(78, 205, 196, 0.9);
|
||||
color: white;
|
||||
}
|
||||
|
||||
.control-btn.secondary:hover {
|
||||
background: rgba(78, 205, 196, 1);
|
||||
box-shadow: 0 6px 20px rgba(78, 205, 196, 0.3);
|
||||
}
|
||||
|
||||
.control-btn i {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
.control-btn span {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
}
|
||||
|
||||
/* --- 响应式设计 --- */
|
||||
@media (max-width: 600px) {
|
||||
.content-overlay {
|
||||
@@ -351,4 +426,19 @@ html, body {
|
||||
margin-top: 15px;
|
||||
font-size: 0.9rem;
|
||||
}
|
||||
|
||||
.chat-control-panel {
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.control-btn {
|
||||
padding: 10px 14px;
|
||||
font-size: 13px;
|
||||
}
|
||||
|
||||
.control-btn i {
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
+148
@@ -0,0 +1,148 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>聊天界面测试</title>
|
||||
<link rel="stylesheet" href="chatStyles.css">
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.0.0/css/all.min.css">
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 20px;
|
||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.test-controls {
|
||||
position: fixed;
|
||||
top: 20px;
|
||||
left: 20px;
|
||||
background: rgba(255, 255, 255, 0.9);
|
||||
padding: 20px;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
|
||||
z-index: 2000;
|
||||
}
|
||||
.test-btn {
|
||||
display: block;
|
||||
width: 100%;
|
||||
margin: 10px 0;
|
||||
padding: 10px 20px;
|
||||
background: #ff6b9d;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 5px;
|
||||
cursor: pointer;
|
||||
font-size: 14px;
|
||||
}
|
||||
.test-btn:hover {
|
||||
background: #ff5a8a;
|
||||
}
|
||||
.status {
|
||||
margin-top: 10px;
|
||||
padding: 10px;
|
||||
background: #f0f0f0;
|
||||
border-radius: 5px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="test-controls">
|
||||
<h3>聊天界面测试</h3>
|
||||
<button class="test-btn" onclick="showChat()">显示聊天界面</button>
|
||||
<button class="test-btn" onclick="hideChat()">隐藏聊天界面</button>
|
||||
<button class="test-btn" onclick="toggleChat()">切换聊天界面</button>
|
||||
<button class="test-btn" onclick="checkStatus()">检查状态</button>
|
||||
<div class="status" id="status">等待操作...</div>
|
||||
</div>
|
||||
|
||||
<script src="chatInterface.js"></script>
|
||||
<script>
|
||||
let chatInterface;
|
||||
|
||||
// 页面加载完成后初始化
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
console.log('测试页面加载完成');
|
||||
|
||||
try {
|
||||
chatInterface = new ChatInterface();
|
||||
console.log('ChatInterface 创建成功:', chatInterface);
|
||||
updateStatus('ChatInterface 创建成功');
|
||||
|
||||
// 设置消息回调
|
||||
chatInterface.onMessageSend = function(message) {
|
||||
console.log('收到消息:', message);
|
||||
setTimeout(() => {
|
||||
chatInterface.addMessage('assistant', '测试回复: ' + message);
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
} catch (error) {
|
||||
console.error('ChatInterface 创建失败:', error);
|
||||
updateStatus('ChatInterface 创建失败: ' + error.message);
|
||||
}
|
||||
});
|
||||
|
||||
function showChat() {
|
||||
if (chatInterface) {
|
||||
console.log('尝试显示聊天界面');
|
||||
chatInterface.show();
|
||||
updateStatus('调用 show() 方法,当前状态: ' + chatInterface.getVisibility());
|
||||
} else {
|
||||
updateStatus('ChatInterface 未初始化');
|
||||
}
|
||||
}
|
||||
|
||||
function hideChat() {
|
||||
if (chatInterface) {
|
||||
console.log('尝试隐藏聊天界面');
|
||||
chatInterface.hide();
|
||||
updateStatus('调用 hide() 方法,当前状态: ' + chatInterface.getVisibility());
|
||||
} else {
|
||||
updateStatus('ChatInterface 未初始化');
|
||||
}
|
||||
}
|
||||
|
||||
function toggleChat() {
|
||||
if (chatInterface) {
|
||||
console.log('尝试切换聊天界面');
|
||||
chatInterface.toggle();
|
||||
updateStatus('调用 toggle() 方法,当前状态: ' + chatInterface.getVisibility());
|
||||
} else {
|
||||
updateStatus('ChatInterface 未初始化');
|
||||
}
|
||||
}
|
||||
|
||||
function checkStatus() {
|
||||
if (chatInterface) {
|
||||
const isVisible = chatInterface.getVisibility();
|
||||
const className = chatInterface.chatContainer.className;
|
||||
const computedStyle = window.getComputedStyle(chatInterface.chatContainer);
|
||||
const opacity = computedStyle.opacity;
|
||||
const transform = computedStyle.transform;
|
||||
const zIndex = computedStyle.zIndex;
|
||||
|
||||
const statusText = `
|
||||
可见性: ${isVisible}
|
||||
类名: ${className}
|
||||
透明度: ${opacity}
|
||||
变换: ${transform}
|
||||
层级: ${zIndex}
|
||||
`;
|
||||
|
||||
console.log('聊天界面状态:', statusText);
|
||||
updateStatus(statusText);
|
||||
} else {
|
||||
updateStatus('ChatInterface 未初始化');
|
||||
}
|
||||
}
|
||||
|
||||
function updateStatus(text) {
|
||||
const statusEl = document.getElementById('status');
|
||||
statusEl.textContent = text;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user