重构前端UI和交互逻辑,提升用户体验和代码可维护性

This commit is contained in:
Zylan
2025-03-06 16:19:11 +08:00
parent b514145c13
commit 0992fdc7e8
7 changed files with 494 additions and 732 deletions

View File

@@ -40,25 +40,14 @@ class SnapSolver {
// Crop elements
this.cropCancel = document.getElementById('cropCancel');
this.cropConfirm = document.getElementById('cropConfirm');
// Format toggle elements
this.confidenceIndicator = document.getElementById('confidenceIndicator');
this.confidenceValue = document.querySelector('.confidence-value');
// History elements
this.historyPanel = document.getElementById('historyPanel');
this.historyContent = document.querySelector('.history-content');
this.closeHistory = document.getElementById('closeHistory');
this.historyToggle = document.getElementById('historyToggle');
}
initializeState() {
this.socket = null;
this.cropper = null;
this.croppedImage = null;
this.history = JSON.parse(localStorage.getItem('snapHistory') || '[]');
this.emitTimeout = null;
this.extractedContent = '';
this.emitTimeout = null;
// 确保裁剪容器和其他面板初始为隐藏状态
if (this.cropContainer) {
@@ -274,22 +263,6 @@ class SnapSolver {
this.textEditor.classList.remove('hidden');
this.sendExtractedTextBtn.disabled = false;
// 更新置信度指示器 (如果有的话)
if (data.confidence !== undefined) {
const confidence = data.confidence || 0;
this.confidenceValue.textContent = `${Math.round(confidence * 100)}%`;
// 置信度颜色
const confidenceEl = this.confidenceIndicator;
if (confidence > 0.8) {
confidenceEl.style.color = 'var(--success)';
} else if (confidence > 0.5) {
confidenceEl.style.color = 'var(--primary)';
} else {
confidenceEl.style.color = 'var(--danger)';
}
}
window.uiManager.showToast('文本提取成功', 'success');
} else if (data.error) {
console.error('文本提取失败:', data.error);
@@ -309,24 +282,33 @@ class SnapSolver {
console.log('Received claude_response:', data);
this.updateStatusLight(data.status);
// 确保Claude面板可见
if (this.claudePanel && this.claudePanel.classList.contains('hidden')) {
this.claudePanel.classList.remove('hidden');
}
switch (data.status) {
case 'started':
console.log('Analysis started');
// 清空显示内容
this.responseContent.innerHTML = '';
this.thinkingContent.innerHTML = '';
this.thinkingSection.classList.add('hidden');
this.sendToClaudeBtn.disabled = true;
this.sendExtractedTextBtn.disabled = true;
if (this.responseContent) this.responseContent.innerHTML = '';
if (this.thinkingContent) this.thinkingContent.innerHTML = '';
if (this.thinkingSection) this.thinkingSection.classList.add('hidden');
// 禁用按钮防止重复点击
if (this.sendToClaudeBtn) this.sendToClaudeBtn.disabled = true;
if (this.sendExtractedTextBtn) this.sendExtractedTextBtn.disabled = true;
// 显示进行中状态
this.responseContent.innerHTML = '<div class="loading-message">分析进行中,请稍候...</div>';
this.responseContent.style.display = 'block';
if (this.responseContent) {
this.responseContent.innerHTML = '<div class="loading-message">分析进行中,请稍候...</div>';
this.responseContent.style.display = 'block';
}
break;
case 'thinking':
// 处理思考内容
if (data.content) {
if (data.content && this.thinkingContent && this.thinkingSection) {
console.log('Received thinking content');
this.thinkingSection.classList.remove('hidden');
@@ -365,7 +347,7 @@ class SnapSolver {
case 'thinking_complete':
// 完整的思考内容
if (data.content) {
if (data.content && this.thinkingContent && this.thinkingSection) {
console.log('Thinking complete');
this.thinkingSection.classList.remove('hidden');
@@ -378,47 +360,59 @@ class SnapSolver {
break;
case 'streaming':
if (data.content) {
console.log('Received content');
if (data.content && this.responseContent) {
console.log('Received content chunk');
// 设置结果内容
this.responseContent.innerHTML = data.content;
// 使用更安全的方式设置内容避免HTML解析问题
this.setElementContent(this.responseContent, data.content);
this.responseContent.style.display = 'block';
// 移除思考部分的打字动画
this.thinkingContent.classList.remove('thinking-typing');
if (this.thinkingContent) {
this.thinkingContent.classList.remove('thinking-typing');
}
// 平滑滚动到最新内容
this.responseContent.scrollTop = this.responseContent.scrollHeight;
}
break;
case 'completed':
console.log('Analysis completed');
this.sendToClaudeBtn.disabled = false;
this.sendExtractedTextBtn.disabled = false;
// 重新启用按钮
if (this.sendToClaudeBtn) this.sendToClaudeBtn.disabled = false;
if (this.sendExtractedTextBtn) this.sendExtractedTextBtn.disabled = false;
// 恢复界面
this.updateStatusLight('completed');
// 保存到历史记录
const responseText = this.responseContent.textContent || '';
const thinkingText = this.thinkingContent.textContent || '';
this.addToHistory(this.croppedImage, responseText, thinkingText);
// 确保思考组件可见,只是将内容折叠
if (this.thinkingSection && this.thinkingContent) {
this.thinkingSection.classList.remove('hidden');
this.thinkingContent.classList.remove('expanded');
this.thinkingContent.classList.add('collapsed');
const toggleBtn = document.querySelector('#thinkingToggle .toggle-btn i');
if (toggleBtn) {
toggleBtn.className = 'fas fa-chevron-down';
}
}
// 确保思考内容处于折叠状态
this.thinkingContent.classList.remove('expanded');
this.thinkingContent.classList.add('collapsed');
const toggleBtn = document.querySelector('#thinkingToggle .toggle-btn i');
if (toggleBtn) {
toggleBtn.className = 'fas fa-chevron-down';
// 确保响应内容完整显示
if (data.content && data.content.trim() !== '' && this.responseContent) {
this.setElementContent(this.responseContent, data.content);
}
// 添加明确的提示
window.uiManager.showToast('分析完成,可点击"AI思考过程"查看详细思考内容', 'success');
// 确保结果内容可见
this.responseContent.style.display = 'block';
// 滚动到结果内容
this.responseContent.scrollIntoView({ behavior: 'smooth' });
if (this.responseContent) {
this.responseContent.style.display = 'block';
// 滚动到结果内容
this.responseContent.scrollIntoView({ behavior: 'smooth' });
}
break;
case 'error':
@@ -426,25 +420,33 @@ class SnapSolver {
const errorMessage = data.error || 'Unknown error occurred';
// 显示错误信息
if (errorMessage) {
if (errorMessage && this.responseContent) {
const currentText = this.responseContent.textContent || '';
this.setElementContent(this.responseContent, currentText + '\nError: ' + errorMessage);
}
this.sendToClaudeBtn.disabled = false;
this.sendExtractedTextBtn.disabled = false;
// 重新启用按钮
if (this.sendToClaudeBtn) this.sendToClaudeBtn.disabled = false;
if (this.sendExtractedTextBtn) this.sendExtractedTextBtn.disabled = false;
window.uiManager.showToast('Analysis failed: ' + errorMessage, 'error');
break;
default:
console.warn('Unknown response status:', data.status);
if (data.error) {
const currentText = this.responseContent.textContent || '';
this.setElementContent(this.responseContent, currentText + '\nError: ' + data.error);
this.sendToClaudeBtn.disabled = false;
this.sendExtractedTextBtn.disabled = false;
window.uiManager.showToast('Unknown error occurred', 'error');
// 对于未知状态,尝试显示内容(如果有)
if (data.content && this.responseContent) {
this.setElementContent(this.responseContent, data.content);
this.responseContent.style.display = 'block';
}
// 确保按钮可用
if (this.sendToClaudeBtn) this.sendToClaudeBtn.disabled = false;
if (this.sendExtractedTextBtn) this.sendExtractedTextBtn.disabled = false;
window.uiManager.showToast('Unknown error occurred', 'error');
break;
}
});
@@ -503,9 +505,6 @@ class SnapSolver {
}, 200);
}
// 添加到历史记录
this.addToHistory(this.croppedImage, data.response, data.thinking);
// 确保思考部分完全显示(如果有的话)
if (data.thinking && this.thinkingSection && this.thinkingContent) {
this.thinkingSection.classList.remove('hidden');
@@ -584,89 +583,6 @@ class SnapSolver {
}
}
addToHistory(imageData, response, thinking) {
try {
// 读取现有历史记录
const historyJson = localStorage.getItem('snapHistory') || '[]';
const history = JSON.parse(historyJson);
// 限制图像数据大小 - 缩小图像或者移除图像数据
let optimizedImageData = null;
if (this.isValidImageDataUrl(imageData)) {
// 检查图像字符串长度,如果过大则不存储完整图像
if (imageData.length > 50000) { // 约50KB的限制
// 使用安全的占位符
optimizedImageData = null;
} else {
optimizedImageData = imageData;
}
}
// 创建新的历史记录项
const timestamp = new Date().toISOString();
const id = Date.now();
const item = {
id,
timestamp,
image: optimizedImageData,
response: response ? response.substring(0, 5000) : "", // 限制响应长度
thinking: thinking ? thinking.substring(0, 2000) : "" // 限制思考过程长度
};
// 添加到历史记录并保存
history.unshift(item);
// 限制历史记录数量,更激进地清理以防止存储空间不足
const maxHistoryItems = 10; // 减少最大历史记录数量
if (history.length > maxHistoryItems) {
history.length = maxHistoryItems; // 直接截断数组
}
try {
localStorage.setItem('snapHistory', JSON.stringify(history));
} catch (storageError) {
console.warn('Storage quota exceeded, clearing older history items');
// 如果仍然失败,则更激进地清理
if (history.length > 3) {
history.length = 3; // 只保留最新的3条记录
try {
localStorage.setItem('snapHistory', JSON.stringify(history));
} catch (severeError) {
// 如果还是失败,则清空历史记录
localStorage.removeItem('snapHistory');
localStorage.setItem('snapHistory', JSON.stringify([item])); // 只保留当前项
}
}
}
// 更新历史面板
this.updateHistoryPanel();
} catch (error) {
console.error('Failed to save to history:', error);
}
}
// 新增一个工具函数来判断图像URL是否有效
isValidImageDataUrl(url) {
return url && typeof url === 'string' && url.startsWith('data:image/') && url.includes(',');
}
// 获取一个安全的占位符图像URL
getPlaceholderImageUrl() {
return 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyMDAiIGhlaWdodD0iMTUwIiB2aWV3Qm94PSIwIDAgMjAwIDE1MCI+PHJlY3Qgd2lkdGg9IjIwMCIgaGVpZ2h0PSIxNTAiIGZpbGw9IiNmMGYwZjAiLz48dGV4dCB4PSI1MCUiIHk9IjUwJSIgZG9taW5hbnQtYmFzZWxpbmU9Im1pZGRsZSIgdGV4dC1hbmNob3I9Im1pZGRsZSIgZm9udC1mYW1pbHk9IkFyaWFsIiBmb250LXNpemU9IjE0IiBmaWxsPSIjOTk5Ij7lm77niYflj5HpgIHlt7LkvJjljJY8L3RleHQ+PC9zdmc+';
}
updateHistoryPanel() {
// 如果历史面板存在,更新其内容
if (this.historyContent) {
// 这里可以实现历史记录的加载和显示
// 暂时留空,后续可以实现
}
}
setupEventListeners() {
this.setupCaptureEvents();
this.setupCropEvents();
@@ -869,6 +785,9 @@ class SnapSolver {
this.sendExtractedTextBtn.addEventListener('click', () => {
if (!this.checkConnectionBeforeAction()) return;
// 防止重复点击
if (this.sendExtractedTextBtn.disabled) return;
const text = this.extractedText.value.trim();
if (!text) {
window.uiManager.showToast('请输入一些文本', 'error');
@@ -883,8 +802,14 @@ class SnapSolver {
}
});
// 清空之前的结果
this.responseContent.innerHTML = '';
this.thinkingContent.innerHTML = '';
// 显示Claude分析面板
this.claudePanel.classList.remove('hidden');
this.responseContent.textContent = '';
// 禁用按钮防止重复点击
this.sendExtractedTextBtn.disabled = true;
try {
@@ -907,15 +832,29 @@ class SnapSolver {
this.sendToClaudeBtn.addEventListener('click', () => {
if (!this.checkConnectionBeforeAction()) return;
// 防止重复点击
if (this.sendToClaudeBtn.disabled) return;
this.sendToClaudeBtn.disabled = true;
if (this.croppedImage) {
try {
// 清空之前的结果
this.responseContent.innerHTML = '';
this.thinkingContent.innerHTML = '';
// 显示Claude分析面板
this.claudePanel.classList.remove('hidden');
// 发送图片进行分析
this.sendImageToClaude(this.croppedImage);
} catch (error) {
console.error('Error:', error);
window.uiManager.showToast('发送图片失败: ' + error.message, 'error');
this.sendToClaudeBtn.disabled = false;
}
} else {
window.uiManager.showToast('请先裁剪图片', 'error');
this.sendToClaudeBtn.disabled = false;
}
});
@@ -1043,12 +982,11 @@ class SnapSolver {
}
});
// 显示Claude分析面板
this.claudePanel.classList.remove('hidden');
this.responseContent.textContent = '';
// 注意:Claude面板的显示已经在点击事件中处理,这里不再重复
} catch (error) {
this.responseContent.textContent = 'Error: ' + error.message;
window.uiManager.showToast('发送图片分析失败', 'error');
this.sendToClaudeBtn.disabled = false;
}
}
@@ -1080,21 +1018,20 @@ class SnapSolver {
// 监听窗口大小变化,调整界面
window.addEventListener('resize', this.handleResize.bind(this));
// 点击文档任何地方隐藏历史面板
// 监听document点击事件处理面板关闭
document.addEventListener('click', (e) => {
if (this.historyPanel &&
!this.historyPanel.contains(e.target) &&
!e.target.closest('#historyToggle')) {
this.historyPanel.classList.add('hidden');
// 关闭裁剪器
if (this.cropContainer &&
!this.cropContainer.contains(e.target) &&
!e.target.matches('#cropBtn') &&
!this.cropContainer.classList.contains('hidden')) {
this.cropContainer.classList.add('hidden');
}
});
// 设置默认UI状态
this.enableInterface();
// 初始化历史
this.updateHistoryPanel();
console.log('SnapSolver initialization complete');
}

View File

@@ -1,51 +1,38 @@
class UIManager {
constructor() {
this.initializeElements();
this.setupTheme();
// UI elements
this.settingsPanel = document.getElementById('settingsPanel');
this.settingsToggle = document.getElementById('settingsToggle');
this.closeSettings = document.getElementById('closeSettings');
this.themeToggle = document.getElementById('themeToggle');
this.toastContainer = document.getElementById('toastContainer');
// Check for preferred color scheme
this.checkPreferredColorScheme();
// Initialize event listeners
this.setupEventListeners();
}
initializeElements() {
// Theme elements
this.themeToggle = document.getElementById('themeToggle');
// Panel elements
this.settingsPanel = document.getElementById('settingsPanel');
this.historyPanel = document.getElementById('historyPanel');
this.claudePanel = document.getElementById('claudePanel');
// History elements
this.historyToggle = document.getElementById('historyToggle');
this.closeHistory = document.getElementById('closeHistory');
// Claude panel elements
this.closeClaudePanel = document.getElementById('closeClaudePanel');
// Toast container
this.toastContainer = document.getElementById('toastContainer');
}
setupTheme() {
checkPreferredColorScheme() {
const savedTheme = localStorage.getItem('theme');
const prefersDark = window.matchMedia('(prefers-color-scheme: dark)');
// Initialize theme
const savedTheme = localStorage.getItem('theme');
if (savedTheme) {
this.setTheme(savedTheme === 'dark');
} else {
this.setTheme(prefersDark.matches);
}
// Listen for system theme changes
prefersDark.addEventListener('change', (e) => this.setTheme(e.matches));
}
setTheme(isDark) {
document.documentElement.setAttribute('data-theme', isDark ? 'dark' : 'light');
this.themeToggle.innerHTML = `<i class="fas fa-${isDark ? 'sun' : 'moon'}"></i>`;
localStorage.setItem('theme', isDark ? 'dark' : 'light');
}
showToast(message, type = 'success') {
// 检查是否已经存在相同内容的提示
const existingToasts = this.toastContainer.querySelectorAll('.toast');
@@ -73,81 +60,37 @@ class UIManager {
setTimeout(() => toast.remove(), 300);
}, displayTime);
}
closeAllPanels() {
this.settingsPanel.classList.add('hidden');
this.historyPanel.classList.add('hidden');
}
setupEventListeners() {
// Settings panel
this.settingsToggle.addEventListener('click', () => {
this.closeAllPanels();
this.settingsPanel.classList.toggle('hidden');
});
this.closeSettings.addEventListener('click', () => {
this.settingsPanel.classList.add('hidden');
});
// Theme toggle
this.themeToggle.addEventListener('click', () => {
const isDark = document.documentElement.getAttribute('data-theme') === 'dark';
this.setTheme(!isDark);
const currentTheme = document.documentElement.getAttribute('data-theme');
this.setTheme(currentTheme !== 'dark');
});
// History panel
this.historyToggle.addEventListener('click', () => {
this.closeAllPanels();
this.historyPanel.classList.toggle('hidden');
if (window.app && typeof window.app.updateHistoryPanel === 'function') {
window.app.updateHistoryPanel();
// Close panels when clicking outside
document.addEventListener('click', (e) => {
// Only close if click is outside the panel and not on the toggle button
if (this.settingsPanel &&
!this.settingsPanel.contains(e.target) &&
!e.target.closest('#settingsToggle')) {
this.settingsPanel.classList.add('hidden');
}
});
this.closeHistory.addEventListener('click', () => {
this.historyPanel.classList.add('hidden');
});
// Claude panel
this.closeClaudePanel.addEventListener('click', () => {
this.claudePanel.classList.add('hidden');
});
// Mobile touch events
let touchStartX = 0;
let touchEndX = 0;
document.addEventListener('touchstart', (e) => {
touchStartX = e.changedTouches[0].screenX;
});
document.addEventListener('touchend', (e) => {
touchEndX = e.changedTouches[0].screenX;
this.handleSwipe(touchStartX, touchEndX);
});
// Keyboard shortcuts
document.addEventListener('keydown', (e) => {
if (e.ctrlKey || e.metaKey) {
switch(e.key) {
case ',':
this.settingsPanel.classList.toggle('hidden');
break;
case 'h':
this.historyPanel.classList.toggle('hidden');
if (window.app && typeof window.app.updateHistoryPanel === 'function') {
window.app.updateHistoryPanel();
}
break;
}
} else if (e.key === 'Escape') {
this.closeAllPanels();
}
});
}
handleSwipe(startX, endX) {
const swipeThreshold = 50;
const diff = endX - startX;
if (Math.abs(diff) > swipeThreshold) {
if (diff > 0) {
this.closeAllPanels();
} else {
this.settingsPanel.classList.remove('hidden');
}
}
}
}