1 line
14 KiB
JSON
1 line
14 KiB
JSON
{"repo": "aaaa-zhen/snapvocal", "files": {"script.js": {"142b0d3bdbd1733a65d9fb4033333ebf55f07a50": {"t": 1785769002.0630574, "n": 13439, "h": "464a7aa0f15eb881", "c": "// DOM Elements\nconst takePhotoButton = document.getElementById('takePhotoButton');\nconst cameraFeed = document.getElementById('cameraFeed');\nconst snapshotCanvas = document.getElementById('snapshotCanvas');\nconst resultDiv = document.getElementById('result');\n\n// Global variables\nlet currentWord = null;\nlet currentImage = null;\nlet stream = null;\n\n// ------- Camera Functions --------\nasync function startCamera() {\n try {\n // Stop any existing stream\n if (stream) {\n stream.getTracks().forEach(track => track.stop());\n }\n \n // Request camera access\n stream = await navigator.mediaDevices.getUserMedia({ \n video: { \n facingMode: \"environment\" // Prefer back camera\n } \n });\n \n // Set video source and enable button\n cameraFeed.srcObject = stream;\n takePhotoButton.disabled = false;\n \n // Reset UI\n resultDiv.style.display = 'none';\n } catch (err) {\n console.error(\"Error accessing camera:\", err);\n document.getElementById('cameraContainer').innerHTML = `\n <div style=\"color: #f44336; text-align: center; padding: 40px;\">\n <div style=\"font-size: 48px; margin-bottom: 16px;\">📷</div>\n <h3>Cannot access camera</h3>\n <p>Please ensure camera permissions are granted for this site.</p>\n </div>\n `;\n takePhotoButton.disabled = true;\n }\n}\n\n// Take photo and immediately analyze the image\nasync function takeAndAnalyzePhoto() {\n // First take the photo\n if (!stream || !stream.active) {\n alert(\"Camera is not active. Please refresh the page.\");\n return;\n }\n \n // Set canvas dimensions to match video dimensions\n const context = snapshotCanvas.getContext('2d');\n snapshotCanvas.width = cameraFeed.videoWidth;\n snapshotCanvas.height = cameraFeed.videoHeight;\n \n // Draw the current video frame onto the canvas\n context.save();\n if (cameraFeed.style.transform === 'scaleX(-1)') { // If mirrored\n context.translate(snapshotCanvas.width, 0);\n context.scale(-1, 1);\n }\n context.drawImage(cameraFeed, 0, 0, snapshotCanvas.width, snapshotCanvas.height);\n context.restore();\n \n // Get image as base64 data URL\n currentImage = snapshotCanvas.toDataURL('image/jpeg');\n \n // Immediately analyze the photo without requiring a second button click\n await analyzeImage();\n}\n\n// Analyze the captured image\nasync function analyzeImage() {\n if (!currentImage) {\n alert('Please take a photo first! 📷');\n return;\n }\n \n // Disable button and show loading state\n takePhotoButton.disabled = true;\n takePhotoButton.innerHTML = '<span class=\"icon\">⏳</span> Processing...';\n resultDiv.style.display = 'block';\n resultDiv.className = 'loading';\n resultDiv.innerHTML = '<div style=\"padding: 20px;\">🔍 Identifying the object...</div>';\n \n try {\n // Send API request to identify the object\n const response = await fetch('https://aihubmix.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Authorization': 'Bearer sk-jRGuOoxjlz4cyO5aA6Fe05B8E58c4b17B9DaA0Fb1776F0E8',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n model: \"gpt-4o-mini\",\n messages: [\n {\n role: \"user\",\n content: [\n {\n type: \"text\",\n text: \"Look at this image and tell me the main object in English. Please respond with just the English word (or phrase if necessary) and a very brief description. Format: 'WORD: brief description'. For example: 'CUP: A container for drinking beverages' or 'SMARTPHONE: A mobile device for communication and apps'\"\n },\n {\n type: \"image_url\",\n image_url: {\n url: currentImage\n }\n }\n ]\n }\n ],\n max_tokens: 100,\n temperature: 0.1\n })\n });\n \n if (!response.ok) {\n const errorData = await response.json().catch(() => ({ detail: \"Unknown error\" }));\n console.error(\"API Error Response:\", errorData);\n throw new Error(`HTTP error! status: ${response.status} - ${errorData.detail || response.statusText}`);\n }\n \n const data = await response.json();\n \n // Format and display the result\n resultDiv.className = 'success';\n if (data.choices && data.choices[0] && data.choices[0].message) {\n const content = data.choices[0].message.content;\n const match = content.match(/^([^:]+):\\s*(.+)$/);\n \n if (match) {\n const word = match[1].trim();\n const description = match[2].trim();\n currentWord = { word, description };\n \n resultDiv.innerHTML = `\n <div class=\"english-word\">${word}</div>\n <div class=\"brief-description\">${description}</div>\n <div class=\"action-buttons\">\n <button id=\"speakButton\" class=\"speak-button\" onclick=\"speakWord('${word}')\">\n <span class=\"icon\">🔊</span> Speak\n </button>\n <button id=\"favoriteButton\" class=\"favorite-button\" onclick=\"addToFavorites()\">\n <span class=\"icon\">⭐</span> Add to Favorites\n </button>\n </div>\n `;\n } else {\n currentWord = { word: content, description: '' };\n resultDiv.innerHTML = `\n <div class=\"english-word\">${content}</div>\n <div class=\"action-buttons\">\n <button id=\"favoriteButton\" class=\"favorite-button\" onclick=\"addToFavorites()\">\n <span class=\"icon\">⭐</span> Add to Favorites\n </button>\n </div>\n `;\n }\n } else {\n resultDiv.className = 'error';\n resultDiv.innerHTML = '<div>😕 Could not identify the object. Please try again.</div>';\n }\n } catch (error) {\n console.error('Error:', error);\n resultDiv.className = 'error';\n resultDiv.innerHTML = `<div>❌ Error: ${error.message}<br>Please check your connection and try again.</div>`;\n } finally {\n takePhotoButton.disabled = false;\n takePhotoButton.innerHTML = '<span class=\"icon\">📸</span> Take Photo';\n }\n}\n\n// ------- Favorites Functions --------\nfunction loadFavorites() {\n const favorites = localStorage.getItem('wordFavorites');\n return favorites ? JSON.parse(favorites) : [];\n}\n\nfunction saveFavorites(favorites) {\n localStorage.setItem('wordFavorites', JSON.stringify(favorites));\n updateStats();\n}\n\nfunction addToFavorites() {\n if (!currentWord || !currentImage) return;\n \n const favorites = loadFavorites();\n const newFavorite = {\n word: currentWord.word,\n description: currentWord.description,\n image: currentImage,\n date: new Date().toISOString()\n };\n \n favorites.unshift(newFavorite);\n saveFavorites(favorites);\n \n const favoriteBtn = document.getElementById('favoriteButton');\n if (favoriteBtn) {\n favoriteBtn.innerHTML = '<span class=\"icon\">✅</span> Saved!';\n favoriteBtn.classList.add('favorited');\n setTimeout(() => {\n if (favoriteBtn.classList.contains('favorited')) {\n favoriteBtn.innerHTML = '<span class=\"icon\">⭐</span> Added to Favorites';\n }\n }, 1500);\n }\n}\n\nfunction deleteFavorite(index) {\n if (confirm('Are you sure you want to delete this word?')) {\n const favorites = loadFavorites();\n favorites.splice(index, 1);\n saveFavorites(favorites);\n displayFavorites();\n }\n}\n\nfunction clearAllFavorites() {\n if (confirm('Are you sure you want to clear all saved words?')) {\n localStorage.removeItem('wordFavorites');\n updateStats();\n displayFavorites();\n }\n}\n\nfunction exportFavorites() {\n const favorites = loadFavorites();\n if (favorites.length === 0) {\n alert('No words to export!');\n return;\n }\n \n const text = favorites.map(fav => \n `${fav.word}: ${fav.description} (${new Date(fav.date).toLocaleDateString()})`\n ).join('\\n');\n \n const blob = new Blob([text], { type: 'text/plain' });\n const url = URL.createObjectURL(blob);\n const a = document.createElement('a');\n a.href = url;\n a.download = `vocabulary_${new Date().toISOString().split('T')[0]}.txt`;\n a.click();\n URL.revokeObjectURL(url);\n}\n\n// ------- UI Functions --------\nfunction switchTab(tab) {\n // Update tab buttons\n document.querySelectorAll('.tab-button').forEach(btn => btn.classList.remove('active'));\n document.querySelector(`button[onclick=\"switchTab('${tab}')\"]`).classList.add('active');\n \n // Update tab content\n document.querySelectorAll('.tab-content').forEach(content => content.classList.remove('active'));\n document.getElementById(`${tab}-tab`).classList.add('active');\n \n // Handle tab-specific actions\n if (tab === 'camera') {\n startCamera(); // Start camera when switching to camera tab\n } else if (tab === 'favorites') {\n if (stream) { // Stop camera if switching away\n stream.getTracks().forEach(track => track.stop());\n stream = null;\n }\n displayFavorites();\n }\n}\n\nfunction updateStats() {\n const favorites = loadFavorites();\n const today = new Date().toDateString();\n const todayFavorites = favorites.filter(fav => \n new Date(fav.date).toDateString() === today\n );\n \n document.getElementById('totalWords').textContent = favorites.length;\n document.getElementById('todayWords').textContent = todayFavorites.length;\n}\n\nfunction displayFavorites() {\n const favorites = loadFavorites();\n const container = document.getElementById('favoritesList');\n \n if (favorites.length === 0) {\n container.innerHTML = `\n <div class=\"empty-state\">\n <div class=\"icon\">📚</div>\n <h3>No words saved yet</h3>\n <p>Start taking photos to build your vocabulary!</p>\n </div>\n `;\n return;\n }\n \n container.innerHTML = favorites.map((favorite, index) => `\n <div class=\"favorite-item\">\n <img src=\"${favorite.image}\" alt=\"${favorite.word}\" class=\"favorite-image\">\n <div class=\"favorite-content\">\n <div class=\"favorite-word\">${favorite.word}</div>\n <div class=\"favorite-description\">${favorite.description}</div>\n <div class=\"favorite-date\">${new Date(favorite.date).toLocaleDateString()}</div>\n </div>\n <div class=\"favorite-actions\">\n <button class=\"danger-button\" onclick=\"deleteFavorite(${index})\">\n <span class=\"icon\">🗑️</span>\n </button>\n </div>\n </div>\n `).join('');\n}\n\n// ------- Event Listeners --------\ntakePhotoButton.addEventListener('click', takeAndAnalyzePhoto);\n\n// ------- Initialize --------\ndocument.addEventListener('DOMContentLoaded', function() {\n updateStats();\n if (document.getElementById('camera-tab').classList.contains('active')) {\n startCamera();\n }\n});\n\n// Add this new function for text-to-speech\nasync function speakWord(word) {\n try {\n const speakButton = document.getElementById('speakButton');\n speakButton.disabled = true;\n speakButton.innerHTML = '<span class=\"icon\">⏳</span> Speaking...';\n\n const response = await fetch('https://aihubmix.com/v1/audio/speech', {\n method: 'POST',\n headers: {\n 'Authorization': 'Bearer sk-jRGuOoxjlz4cyO5aA6Fe05B8E58c4b17B9DaA0Fb1776F0E8',\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n model: \"gpt-4o-mini-tts\",\n voice: \"coral\",\n input: word,\n instructions: \"Speak clearly and naturally.\"\n })\n });\n\n if (!response.ok) {\n throw new Error('Failed to generate speech');\n }\n\n const audioBlob = await response.blob();\n const audioUrl = URL.createObjectURL(audioBlob);\n const audio = new Audio(audioUrl);\n \n audio.onended = () => {\n speakButton.disabled = false;\n speakButton.innerHTML = '<span class=\"icon\">🔊</span> Speak';\n };\n\n audio.play();\n } catch (error) {\n console.error('Error playing speech:', error);\n const speakButton = document.getElementById('speakButton');\n speakButton.disabled = false;\n speakButton.innerHTML = '<span class=\"icon\">🔊</span> Speak';\n alert('Failed to play speech. Please try again.');\n }\n}"}}}} |