mirror of
https://github.com/Zippland/Snap-Solver.git
synced 2026-02-11 17:15:49 +08:00
gitignore
This commit is contained in:
@@ -150,41 +150,53 @@ class SnapSolver {
|
||||
}
|
||||
|
||||
initializeCropper() {
|
||||
if (this.cropper) {
|
||||
this.cropper.destroy();
|
||||
this.cropper = null;
|
||||
}
|
||||
|
||||
const cropArea = document.querySelector('.crop-area');
|
||||
cropArea.innerHTML = '';
|
||||
const clonedImage = this.screenshotImg.cloneNode(true);
|
||||
clonedImage.style.display = 'block';
|
||||
cropArea.appendChild(clonedImage);
|
||||
|
||||
this.cropContainer.classList.remove('hidden');
|
||||
|
||||
this.cropper = new Cropper(clonedImage, {
|
||||
viewMode: 1,
|
||||
dragMode: 'move',
|
||||
autoCropArea: 1,
|
||||
restore: false,
|
||||
modal: true,
|
||||
guides: true,
|
||||
highlight: true,
|
||||
cropBoxMovable: true,
|
||||
cropBoxResizable: true,
|
||||
toggleDragModeOnDblclick: false,
|
||||
minContainerWidth: window.innerWidth,
|
||||
minContainerHeight: window.innerHeight - 100,
|
||||
minCropBoxWidth: 100,
|
||||
minCropBoxHeight: 100,
|
||||
background: true,
|
||||
responsive: true,
|
||||
checkOrientation: true,
|
||||
ready: function() {
|
||||
this.cropper.crop();
|
||||
try {
|
||||
// Clean up existing cropper instance
|
||||
if (this.cropper) {
|
||||
this.cropper.destroy();
|
||||
this.cropper = null;
|
||||
}
|
||||
});
|
||||
|
||||
const cropArea = document.querySelector('.crop-area');
|
||||
cropArea.innerHTML = '';
|
||||
const clonedImage = this.screenshotImg.cloneNode(true);
|
||||
clonedImage.style.display = 'block';
|
||||
cropArea.appendChild(clonedImage);
|
||||
|
||||
this.cropContainer.classList.remove('hidden');
|
||||
|
||||
// Store reference to this for use in ready callback
|
||||
const self = this;
|
||||
|
||||
this.cropper = new Cropper(clonedImage, {
|
||||
viewMode: 1,
|
||||
dragMode: 'move',
|
||||
autoCropArea: 0.8,
|
||||
restore: false,
|
||||
modal: true,
|
||||
guides: true,
|
||||
highlight: true,
|
||||
cropBoxMovable: true,
|
||||
cropBoxResizable: true,
|
||||
toggleDragModeOnDblclick: false,
|
||||
minContainerWidth: 800,
|
||||
minContainerHeight: 600,
|
||||
minCropBoxWidth: 100,
|
||||
minCropBoxHeight: 100,
|
||||
background: true,
|
||||
responsive: true,
|
||||
checkOrientation: true,
|
||||
ready: function() {
|
||||
// Use the stored reference to this
|
||||
if (self.cropper) {
|
||||
self.cropper.crop();
|
||||
}
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error initializing cropper:', error);
|
||||
window.showToast('Failed to initialize cropper', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
addToHistory(imageData, response) {
|
||||
@@ -230,22 +242,53 @@ class SnapSolver {
|
||||
document.getElementById('cropConfirm').addEventListener('click', () => {
|
||||
if (this.cropper) {
|
||||
try {
|
||||
console.log('Starting crop operation...');
|
||||
|
||||
// Validate cropper instance
|
||||
if (!this.cropper) {
|
||||
throw new Error('Cropper not initialized');
|
||||
}
|
||||
|
||||
// Get and validate crop box data
|
||||
const cropBoxData = this.cropper.getCropBoxData();
|
||||
console.log('Crop box data:', cropBoxData);
|
||||
|
||||
if (!cropBoxData || typeof cropBoxData.width !== 'number' || typeof cropBoxData.height !== 'number') {
|
||||
throw new Error('Invalid crop box data');
|
||||
}
|
||||
|
||||
if (cropBoxData.width < 10 || cropBoxData.height < 10) {
|
||||
throw new Error('Crop area is too small. Please select a larger area (minimum 10x10 pixels).');
|
||||
}
|
||||
|
||||
// Get cropped canvas with more conservative size limits
|
||||
console.log('Getting cropped canvas...');
|
||||
const canvas = this.cropper.getCroppedCanvas({
|
||||
maxWidth: 4096,
|
||||
maxHeight: 4096,
|
||||
maxWidth: 1280,
|
||||
maxHeight: 720,
|
||||
fillColor: '#fff',
|
||||
imageSmoothingEnabled: true,
|
||||
imageSmoothingQuality: 'high',
|
||||
imageSmoothingQuality: 'low',
|
||||
});
|
||||
|
||||
if (!canvas) {
|
||||
throw new Error('Failed to create cropped canvas');
|
||||
}
|
||||
|
||||
this.croppedImage = canvas.toDataURL('image/png');
|
||||
|
||||
this.cropper.destroy();
|
||||
this.cropper = null;
|
||||
console.log('Canvas created successfully');
|
||||
|
||||
// Convert to data URL with error handling and compression
|
||||
console.log('Converting to data URL...');
|
||||
try {
|
||||
// Use lower quality for JPEG to reduce size
|
||||
this.croppedImage = canvas.toDataURL('image/jpeg', 0.6);
|
||||
console.log('Data URL conversion successful');
|
||||
} catch (dataUrlError) {
|
||||
console.error('Data URL conversion error:', dataUrlError);
|
||||
throw new Error('Failed to process cropped image. The image might be too large or memory insufficient.');
|
||||
}
|
||||
|
||||
// Clean up cropper and update UI
|
||||
this.cropContainer.classList.add('hidden');
|
||||
document.querySelector('.crop-area').innerHTML = '';
|
||||
this.settingsPanel.classList.add('hidden');
|
||||
@@ -256,8 +299,13 @@ class SnapSolver {
|
||||
this.sendToClaudeBtn.classList.remove('hidden');
|
||||
window.showToast('Image cropped successfully');
|
||||
} catch (error) {
|
||||
console.error('Cropping error:', error);
|
||||
window.showToast('Error while cropping image', 'error');
|
||||
console.error('Cropping error details:', {
|
||||
message: error.message,
|
||||
stack: error.stack,
|
||||
cropperState: this.cropper ? 'initialized' : 'not initialized'
|
||||
});
|
||||
window.showToast(error.message || 'Error while cropping image', 'error');
|
||||
return; // Exit the function to prevent cleanup if error occurs
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -281,8 +329,9 @@ class SnapSolver {
|
||||
}
|
||||
|
||||
const settings = window.settingsManager.getSettings();
|
||||
if (!settings.apiKey) {
|
||||
window.showToast('Please enter your API key in settings', 'error');
|
||||
const apiKey = window.settingsManager.getApiKey();
|
||||
|
||||
if (!apiKey) {
|
||||
this.settingsPanel.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
@@ -295,7 +344,7 @@ class SnapSolver {
|
||||
this.socket.emit('analyze_image', {
|
||||
image: this.croppedImage.split(',')[1],
|
||||
settings: {
|
||||
apiKey: settings.apiKey,
|
||||
apiKey: apiKey,
|
||||
model: settings.model || 'claude-3-5-sonnet-20241022',
|
||||
temperature: parseFloat(settings.temperature) || 0.7,
|
||||
systemPrompt: settings.systemPrompt || 'You are an expert at analyzing questions and providing detailed solutions.',
|
||||
|
||||
@@ -8,7 +8,6 @@ class SettingsManager {
|
||||
initializeElements() {
|
||||
// Settings panel elements
|
||||
this.settingsPanel = document.getElementById('settingsPanel');
|
||||
this.apiKeyInput = document.getElementById('apiKey');
|
||||
this.modelSelect = document.getElementById('modelSelect');
|
||||
this.temperatureInput = document.getElementById('temperature');
|
||||
this.temperatureValue = document.getElementById('temperatureValue');
|
||||
@@ -18,17 +17,52 @@ class SettingsManager {
|
||||
this.proxyPortInput = document.getElementById('proxyPort');
|
||||
this.proxySettings = document.getElementById('proxySettings');
|
||||
|
||||
// API Key elements
|
||||
this.apiKeyInputs = {
|
||||
'claude-3-5-sonnet-20241022': document.getElementById('claudeApiKey'),
|
||||
'gpt-4o-2024-11-20': document.getElementById('gpt4oApiKey'),
|
||||
'deepseek-reasoner': document.getElementById('deepseekApiKey')
|
||||
};
|
||||
|
||||
// Settings toggle elements
|
||||
this.settingsToggle = document.getElementById('settingsToggle');
|
||||
this.closeSettings = document.getElementById('closeSettings');
|
||||
this.toggleApiKey = document.getElementById('toggleApiKey');
|
||||
this.apiKeyGroups = document.querySelectorAll('.api-key-group');
|
||||
|
||||
// Initialize API key toggle buttons
|
||||
document.querySelectorAll('.toggle-api-key').forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
const input = e.target.closest('.input-group').querySelector('input');
|
||||
const type = input.type === 'password' ? 'text' : 'password';
|
||||
input.type = type;
|
||||
const icon = e.target.querySelector('i');
|
||||
if (icon) {
|
||||
icon.className = `fas fa-${type === 'password' ? 'eye' : 'eye-slash'}`;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
loadSettings() {
|
||||
const settings = JSON.parse(localStorage.getItem('aiSettings') || '{}');
|
||||
|
||||
if (settings.apiKey) this.apiKeyInput.value = settings.apiKey;
|
||||
if (settings.model) this.modelSelect.value = settings.model;
|
||||
// Load API keys
|
||||
if (settings.apiKeys) {
|
||||
Object.entries(this.apiKeyInputs).forEach(([model, input]) => {
|
||||
if (settings.apiKeys[model]) {
|
||||
input.value = settings.apiKeys[model];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (settings.model) {
|
||||
this.modelSelect.value = settings.model;
|
||||
this.updateVisibleApiKey(settings.model);
|
||||
} else {
|
||||
// Default to first model if none selected
|
||||
this.updateVisibleApiKey(this.modelSelect.value);
|
||||
}
|
||||
|
||||
if (settings.temperature) {
|
||||
this.temperatureInput.value = settings.temperature;
|
||||
this.temperatureValue.textContent = settings.temperature;
|
||||
@@ -43,9 +77,16 @@ class SettingsManager {
|
||||
this.proxySettings.style.display = this.proxyEnabledInput.checked ? 'block' : 'none';
|
||||
}
|
||||
|
||||
updateVisibleApiKey(selectedModel) {
|
||||
this.apiKeyGroups.forEach(group => {
|
||||
const modelValue = group.dataset.model;
|
||||
group.style.display = modelValue === selectedModel ? 'block' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
saveSettings() {
|
||||
const settings = {
|
||||
apiKey: this.apiKeyInput.value,
|
||||
apiKeys: {},
|
||||
model: this.modelSelect.value,
|
||||
temperature: this.temperatureInput.value,
|
||||
systemPrompt: this.systemPromptInput.value,
|
||||
@@ -53,22 +94,46 @@ class SettingsManager {
|
||||
proxyHost: this.proxyHostInput.value,
|
||||
proxyPort: this.proxyPortInput.value
|
||||
};
|
||||
|
||||
// Save all API keys
|
||||
Object.entries(this.apiKeyInputs).forEach(([model, input]) => {
|
||||
if (input.value) {
|
||||
settings.apiKeys[model] = input.value;
|
||||
}
|
||||
});
|
||||
|
||||
localStorage.setItem('aiSettings', JSON.stringify(settings));
|
||||
window.showToast('Settings saved successfully');
|
||||
}
|
||||
|
||||
getSettings() {
|
||||
return JSON.parse(localStorage.getItem('aiSettings') || '{}');
|
||||
getApiKey() {
|
||||
const selectedModel = this.modelSelect.value;
|
||||
const apiKey = this.apiKeyInputs[selectedModel]?.value;
|
||||
|
||||
if (!apiKey) {
|
||||
window.showToast('Please enter API key for the selected model', 'error');
|
||||
return '';
|
||||
}
|
||||
|
||||
return apiKey;
|
||||
}
|
||||
|
||||
setupEventListeners() {
|
||||
// Save settings on change
|
||||
this.apiKeyInput.addEventListener('change', () => this.saveSettings());
|
||||
this.modelSelect.addEventListener('change', () => this.saveSettings());
|
||||
Object.values(this.apiKeyInputs).forEach(input => {
|
||||
input.addEventListener('change', () => this.saveSettings());
|
||||
});
|
||||
|
||||
this.modelSelect.addEventListener('change', (e) => {
|
||||
this.updateVisibleApiKey(e.target.value);
|
||||
this.saveSettings();
|
||||
});
|
||||
|
||||
this.temperatureInput.addEventListener('input', (e) => {
|
||||
this.temperatureValue.textContent = e.target.value;
|
||||
this.saveSettings();
|
||||
});
|
||||
|
||||
this.systemPromptInput.addEventListener('change', () => this.saveSettings());
|
||||
this.proxyEnabledInput.addEventListener('change', (e) => {
|
||||
this.proxySettings.style.display = e.target.checked ? 'block' : 'none';
|
||||
@@ -77,13 +142,6 @@ class SettingsManager {
|
||||
this.proxyHostInput.addEventListener('change', () => this.saveSettings());
|
||||
this.proxyPortInput.addEventListener('change', () => this.saveSettings());
|
||||
|
||||
// Toggle API key visibility
|
||||
this.toggleApiKey.addEventListener('click', () => {
|
||||
const type = this.apiKeyInput.type === 'password' ? 'text' : 'password';
|
||||
this.apiKeyInput.type = type;
|
||||
this.toggleApiKey.innerHTML = `<i class="fas fa-${type === 'password' ? 'eye' : 'eye-slash'}"></i>`;
|
||||
});
|
||||
|
||||
// Panel visibility
|
||||
this.settingsToggle.addEventListener('click', () => {
|
||||
window.closeAllPanels();
|
||||
|
||||
@@ -660,6 +660,41 @@ button:disabled {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* API Key Groups */
|
||||
.api-key-group {
|
||||
margin-bottom: 1.5rem;
|
||||
padding: 1rem;
|
||||
background-color: var(--background);
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid var(--border-color);
|
||||
transition: all 0.3s ease;
|
||||
}
|
||||
|
||||
.api-key-group:not([style*="display: none"]) {
|
||||
animation: fade-in 0.3s ease;
|
||||
}
|
||||
|
||||
.api-key-group label {
|
||||
font-weight: 500;
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.api-key-group .input-group {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(-10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Utility Classes */
|
||||
.hidden {
|
||||
display: none !important;
|
||||
|
||||
Reference in New Issue
Block a user