A Pen by mode-mercury on CodePen.
Created
June 12, 2025 22:52
-
-
Save mode-mercury/2e6eb26934a5a4ee4c982f91f83cc503 to your computer and use it in GitHub Desktop.
Untitled
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Neural Nexus Enhanced</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> | |
| <script> | |
| tailwind.config = { | |
| darkMode: 'class', | |
| theme: { | |
| extend: { | |
| colors: { | |
| primary: { | |
| 400: '#7F7FFE', | |
| 500: '#5D5CDE', | |
| 600: '#4140BE' | |
| }, | |
| secondary: { | |
| 400: '#ff8d8d', | |
| 500: '#ff6b6b', | |
| 600: '#ff4949' | |
| }, | |
| accent: { | |
| 400: '#61edea', | |
| 500: '#32e2df', | |
| 600: '#1bc5c2' | |
| }, | |
| neural: { | |
| 100: '#232338', | |
| 200: '#1a1a2e', | |
| 300: '#16162a', | |
| 400: '#0f0f20' | |
| }, | |
| creative: { | |
| 400: '#FFD166', | |
| 500: '#F2994A', | |
| 600: '#E67E22' | |
| } | |
| }, | |
| animation: { | |
| 'gradient': 'gradient 8s ease infinite', | |
| 'float': 'float 3s ease-in-out infinite', | |
| 'pulse-slow': 'pulse 4s cubic-bezier(0.4, 0, 0.6, 1) infinite', | |
| 'bounce-slow': 'bounce 3s infinite', | |
| 'spin-slow': 'spin 8s linear infinite', | |
| }, | |
| keyframes: { | |
| gradient: { | |
| '0%, 100%': { | |
| 'background-position': '0% 50%' | |
| }, | |
| '50%': { | |
| 'background-position': '100% 50%' | |
| } | |
| }, | |
| float: { | |
| '0%, 100%': { | |
| transform: 'translateY(0)' | |
| }, | |
| '50%': { | |
| transform: 'translateY(-10px)' | |
| } | |
| } | |
| }, | |
| dropShadow: { | |
| 'glow-sm': '0 0 4px rgba(93, 92, 222, 0.3)', | |
| 'glow': '0 0 8px rgba(93, 92, 222, 0.5)', | |
| 'glow-lg': '0 0 12px rgba(93, 92, 222, 0.7)' | |
| } | |
| } | |
| } | |
| } | |
| </script> | |
| <style> | |
| /* Background gradient animation */ | |
| .bg-neural-gradient { | |
| background: linear-gradient(-45deg, #16162a, #0f0f20, #1a1a2e, #232338); | |
| background-size: 400% 400%; | |
| animation: gradient 15s ease infinite; | |
| } | |
| @keyframes gradient { | |
| 0% { | |
| background-position: 0% 50%; | |
| } | |
| 50% { | |
| background-position: 100% 50%; | |
| } | |
| 100% { | |
| background-position: 0% 50%; | |
| } | |
| } | |
| /* Glow effect */ | |
| .glow { | |
| box-shadow: 0 0 15px rgba(93, 92, 222, 0.5); | |
| transition: all 0.3s ease; | |
| } | |
| .glow:hover { | |
| box-shadow: 0 0 25px rgba(93, 92, 222, 0.7); | |
| } | |
| .glow-text { | |
| text-shadow: 0 0 10px rgba(93, 92, 222, 0.7); | |
| } | |
| /* Enhanced pulse animation for nodes with glow */ | |
| @keyframes pulse-node { | |
| 0% { | |
| r: 4; | |
| opacity: 0.7; | |
| filter: drop-shadow(0 0 1px rgba(93, 92, 222, 0.3)); | |
| } | |
| 50% { | |
| r: 7; | |
| opacity: 1; | |
| filter: drop-shadow(0 0 6px rgba(93, 92, 222, 0.7)); | |
| } | |
| 100% { | |
| r: 4; | |
| opacity: 0.7; | |
| filter: drop-shadow(0 0 1px rgba(93, 92, 222, 0.3)); | |
| } | |
| } | |
| @keyframes pulse-node-accent { | |
| 0% { | |
| r: 4; | |
| opacity: 0.7; | |
| filter: drop-shadow(0 0 1px rgba(50, 226, 223, 0.3)); | |
| } | |
| 50% { | |
| r: 7; | |
| opacity: 1; | |
| filter: drop-shadow(0 0 6px rgba(50, 226, 223, 0.7)); | |
| } | |
| 100% { | |
| r: 4; | |
| opacity: 0.7; | |
| filter: drop-shadow(0 0 1px rgba(50, 226, 223, 0.3)); | |
| } | |
| } | |
| @keyframes pulse-node-secondary { | |
| 0% { | |
| r: 4; | |
| opacity: 0.7; | |
| filter: drop-shadow(0 0 1px rgba(255, 107, 107, 0.3)); | |
| } | |
| 50% { | |
| r: 7; | |
| opacity: 1; | |
| filter: drop-shadow(0 0 6px rgba(255, 107, 107, 0.7)); | |
| } | |
| 100% { | |
| r: 4; | |
| opacity: 0.7; | |
| filter: drop-shadow(0 0 1px rgba(255, 107, 107, 0.3)); | |
| } | |
| } | |
| .pulse-animation { | |
| animation: pulse-node 2s infinite; | |
| } | |
| .pulse-animation-accent { | |
| animation: pulse-node-accent 2s infinite; | |
| } | |
| .pulse-animation-secondary { | |
| animation: pulse-node-secondary 2s infinite; | |
| } | |
| /* Enhanced flow animation for connections with glow */ | |
| @keyframes flow-through { | |
| 0% { | |
| stroke-dashoffset: 50; | |
| stroke-width: 1; | |
| } | |
| 50% { | |
| stroke-width: 2; | |
| } | |
| 100% { | |
| stroke-dashoffset: 0; | |
| stroke-width: 1; | |
| } | |
| } | |
| .flow-animation { | |
| stroke-dasharray: 5, 3; | |
| animation: flow-through 1.5s linear infinite; | |
| filter: drop-shadow(0 0 2px rgba(93, 92, 222, 0.6)); | |
| } | |
| /* Data packet animation */ | |
| @keyframes data-packet-flow { | |
| 0% { | |
| offset-distance: 0%; | |
| opacity: 0; | |
| } | |
| 10% { | |
| opacity: 1; | |
| } | |
| 90% { | |
| opacity: 1; | |
| } | |
| 100% { | |
| offset-distance: 100%; | |
| opacity: 0; | |
| } | |
| } | |
| .data-packet { | |
| offset-path: path(var(--path)); | |
| animation: data-packet-flow 1.5s ease-in-out; | |
| animation-fill-mode: forwards; | |
| } | |
| /* Fade in animation */ | |
| @keyframes fade-in { | |
| 0% { | |
| opacity: 0; | |
| transform: translateY(10px); | |
| } | |
| 100% { | |
| opacity: 1; | |
| transform: translateY(0); | |
| } | |
| } | |
| .fade-in { | |
| animation: fade-in 0.5s ease-in; | |
| } | |
| /* Enhanced scrollbar for dark mode */ | |
| ::-webkit-scrollbar { | |
| width: 6px; | |
| height: 6px; | |
| } | |
| ::-webkit-scrollbar-track { | |
| background: rgba(26, 26, 46, 0.6); | |
| border-radius: 8px; | |
| } | |
| ::-webkit-scrollbar-thumb { | |
| background: linear-gradient(to bottom, #5D5CDE, #32e2df); | |
| border-radius: 8px; | |
| } | |
| ::-webkit-scrollbar-thumb:hover { | |
| background: linear-gradient(to bottom, #7F7FFE, #61edea); | |
| } | |
| /* Glass effect */ | |
| .glass { | |
| background: rgba(26, 26, 46, 0.2); | |
| backdrop-filter: blur(8px); | |
| -webkit-backdrop-filter: blur(8px); | |
| border: 1px solid rgba(255, 255, 255, 0.05); | |
| } | |
| .glass-dark { | |
| background: rgba(15, 15, 32, 0.7); | |
| backdrop-filter: blur(8px); | |
| -webkit-backdrop-filter: blur(8px); | |
| border: 1px solid rgba(255, 255, 255, 0.05); | |
| } | |
| /* Token animation */ | |
| @keyframes token-pulse { | |
| 0% { | |
| transform: scale(1); | |
| } | |
| 50% { | |
| transform: scale(1.05); | |
| } | |
| 100% { | |
| transform: scale(1); | |
| } | |
| } | |
| .token-animate { | |
| animation: token-pulse 2s infinite; | |
| } | |
| /* Custom button shine effect */ | |
| .btn-shine { | |
| position: relative; | |
| overflow: hidden; | |
| } | |
| .btn-shine::after { | |
| content: ''; | |
| position: absolute; | |
| top: -50%; | |
| left: -50%; | |
| width: 200%; | |
| height: 200%; | |
| background: linear-gradient(to bottom right, | |
| rgba(255, 255, 255, 0) 0%, | |
| rgba(255, 255, 255, 0.1) 50%, | |
| rgba(255, 255, 255, 0) 100%); | |
| transform: rotate(45deg); | |
| animation: shine 3s infinite; | |
| } | |
| @keyframes shine { | |
| 0% { | |
| transform: translateX(-100%) rotate(45deg); | |
| } | |
| 20%, | |
| 100% { | |
| transform: translateX(100%) rotate(45deg); | |
| } | |
| } | |
| /* Voice wave animation */ | |
| .voice-wave { | |
| position: relative; | |
| } | |
| .voice-wave span { | |
| position: absolute; | |
| width: 6px; | |
| height: 6px; | |
| background: currentColor; | |
| border-radius: 50%; | |
| animation: voice-wave-anim 1.5s ease-in-out infinite; | |
| } | |
| .voice-wave span:nth-child(1) { | |
| left: -12px; | |
| animation-delay: 0s; | |
| } | |
| .voice-wave span:nth-child(2) { | |
| left: -6px; | |
| animation-delay: 0.2s; | |
| } | |
| .voice-wave span:nth-child(3) { | |
| left: 0; | |
| animation-delay: 0.4s; | |
| } | |
| .voice-wave span:nth-child(4) { | |
| left: 6px; | |
| animation-delay: 0.6s; | |
| } | |
| .voice-wave span:nth-child(5) { | |
| left: 12px; | |
| animation-delay: 0.8s; | |
| } | |
| @keyframes voice-wave-anim { | |
| 0%, | |
| 100% { | |
| transform: translateY(0); | |
| } | |
| 50% { | |
| transform: translateY(-8px); | |
| } | |
| } | |
| /* Audio visualization */ | |
| .audio-bar { | |
| display: inline-block; | |
| width: 3px; | |
| margin: 0 1px; | |
| background: linear-gradient(to top, #32e2df, #5D5CDE); | |
| border-radius: 1px; | |
| transition: height 0.1s ease; | |
| } | |
| /* Image generator */ | |
| .color-swatch { | |
| width: 24px; | |
| height: 24px; | |
| border-radius: 4px; | |
| display: inline-block; | |
| cursor: pointer; | |
| transition: transform 0.2s ease; | |
| border: 2px solid transparent; | |
| } | |
| .color-swatch:hover { | |
| transform: scale(1.1); | |
| } | |
| .color-swatch.active { | |
| border: 2px solid white; | |
| transform: scale(1.1); | |
| } | |
| /* Tooltip styles */ | |
| .tooltip { | |
| position: relative; | |
| display: inline-block; | |
| cursor: help; | |
| } | |
| .tooltip .tooltiptext { | |
| visibility: hidden; | |
| width: 200px; | |
| background: rgba(15, 15, 32, 0.9); | |
| backdrop-filter: blur(8px); | |
| color: #fff; | |
| text-align: center; | |
| border-radius: 6px; | |
| padding: 8px; | |
| position: absolute; | |
| z-index: 1; | |
| bottom: 125%; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| opacity: 0; | |
| transition: opacity 0.3s; | |
| font-size: 0.75rem; | |
| box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); | |
| border: 1px solid rgba(255, 255, 255, 0.1); | |
| } | |
| .tooltip .tooltiptext::after { | |
| content: ""; | |
| position: absolute; | |
| top: 100%; | |
| left: 50%; | |
| margin-left: -5px; | |
| border-width: 5px; | |
| border-style: solid; | |
| border-color: rgba(15, 15, 32, 0.9) transparent transparent transparent; | |
| } | |
| .tooltip:hover .tooltiptext { | |
| visibility: visible; | |
| opacity: 1; | |
| } | |
| /* Help section */ | |
| .help-step { | |
| transition: all 0.3s ease; | |
| cursor: pointer; | |
| } | |
| .help-step:hover { | |
| background: rgba(93, 92, 222, 0.1); | |
| } | |
| .help-step.active { | |
| background: rgba(93, 92, 222, 0.15); | |
| border-left: 3px solid #5D5CDE; | |
| } | |
| </style> | |
| </head> | |
| <body class="bg-neural-gradient text-gray-200 min-h-screen transition-colors duration-300"> | |
| <div class="absolute inset-0 overflow-hidden pointer-events-none opacity-30"> | |
| <div class="absolute w-96 h-96 bg-primary-500 rounded-full filter blur-3xl -top-20 -left-20 animate-pulse-slow"></div> | |
| <div class="absolute w-96 h-96 bg-accent-500 rounded-full filter blur-3xl -bottom-20 -right-20 animate-pulse-slow" style="animation-delay: 2s"></div> | |
| </div> | |
| <!-- Help button (fixed position) --> | |
| <div class="fixed top-4 right-4 z-20"> | |
| <button id="help-toggle" class="w-10 h-10 rounded-full bg-gradient-to-r from-accent-500 to-primary-500 text-white flex items-center justify-center shadow-lg hover:shadow-xl transition-all"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8.228 9c.549-1.165 2.03-2 3.772-2 2.21 0 4 1.343 4 3 0 1.4-1.278 2.575-3.006 2.907-.542.104-.994.54-.994 1.093m0 3h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
| </svg> | |
| </button> | |
| </div> | |
| <!-- Help overlay (hidden by default) --> | |
| <div id="help-overlay" class="fixed inset-0 z-30 bg-neural-400/80 backdrop-blur-sm flex items-center justify-center hidden"> | |
| <div class="w-full max-w-3xl max-h-[90vh] overflow-auto bg-neural-200 rounded-xl shadow-2xl p-6 border border-primary-500/30"> | |
| <div class="flex justify-between items-center mb-4"> | |
| <h2 class="text-xl font-bold text-white">Neural Nexus - Interactive Guide</h2> | |
| <button id="help-close" class="text-gray-400 hover:text-white"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> | |
| </svg> | |
| </button> | |
| </div> | |
| <div class="grid grid-cols-3 gap-6"> | |
| <!-- Help navigation sidebar --> | |
| <div class="col-span-1 border-r border-gray-700 pr-4"> | |
| <div class="text-sm font-medium text-gray-400 mb-2">GUIDE SECTIONS</div> | |
| <div class="flex flex-col space-y-1"> | |
| <div class="help-step active p-2 rounded" data-step="neural-network"> | |
| Neural Network Visualization | |
| </div> | |
| <div class="help-step p-2 rounded" data-step="language-model"> | |
| How the Language Model Works | |
| </div> | |
| <div class="help-step p-2 rounded" data-step="voice-features"> | |
| Voice Features Guide | |
| </div> | |
| <div class="help-step p-2 rounded" data-step="image-generator"> | |
| AI Image Generation | |
| </div> | |
| <div class="help-step p-2 rounded" data-step="music-generator"> | |
| AI Music Creation | |
| </div> | |
| <div class="help-step p-2 rounded" data-step="training-data"> | |
| Training & Data Sources | |
| </div> | |
| <div class="help-step p-2 rounded" data-step="tips-tricks"> | |
| Tips & Tricks | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Help content area --> | |
| <div class="col-span-2"> | |
| <!-- Neural Network section --> | |
| <div id="help-neural-network" class="help-content"> | |
| <h3 class="text-lg font-semibold text-accent-400 mb-3">Understanding the Neural Network Visualization</h3> | |
| <p class="mb-4"> | |
| The visualization shows a simple neural network with three layers: Input, Hidden, and Output. | |
| This represents how AI models process information through interconnected nodes. | |
| </p> | |
| <div class="mb-4 p-3 bg-neural-300 rounded-lg"> | |
| <h4 class="font-medium text-primary-400 mb-2">What You're Seeing:</h4> | |
| <ul class="list-disc list-inside text-sm space-y-2"> | |
| <li><span class="text-primary-400 font-medium">Blue nodes (Input layer)</span>: Represent the words from your input text</li> | |
| <li><span class="text-accent-400 font-medium">Teal nodes (Hidden layer)</span>: Process and transform the input data</li> | |
| <li><span class="text-secondary-400 font-medium">Red nodes (Output layer)</span>: Generate potential next words</li> | |
| <li>The <span class="text-white font-medium">glowing connections</span> show information flowing between nodes</li> | |
| <li>The <span class="text-white font-medium">moving dots</span> represent data flowing through the network</li> | |
| </ul> | |
| </div> | |
| <p class="mb-4"> | |
| When you enter text, the system activates different patterns in the network. | |
| Each step in generating a response activates different pathways, showing how an AI | |
| language model might process text and make predictions about what comes next. | |
| </p> | |
| <div class="p-3 bg-neural-300 rounded-lg text-sm"> | |
| <p class="font-medium text-accent-400">Note:</p> | |
| <p>This is a simplified visualization - real neural networks can have millions or billions of parameters | |
| across many more layers, making them impossible to visualize completely.</p> | |
| </div> | |
| </div> | |
| <!-- Language Model section --> | |
| <div id="help-language-model" class="help-content hidden"> | |
| <h3 class="text-lg font-semibold text-accent-400 mb-3">How the Language Model Works</h3> | |
| <p class="mb-4"> | |
| Our app uses a simple n-gram language model, which predicts the next word based on the | |
| previous 1-2 words (context). Real AI language models like GPT-4 and Claude use much more sophisticated | |
| transformer architectures with attention mechanisms. | |
| </p> | |
| <div class="mb-4 p-3 bg-neural-300 rounded-lg"> | |
| <h4 class="font-medium text-primary-400 mb-2">Generation Process:</h4> | |
| <ol class="list-decimal list-inside text-sm space-y-2"> | |
| <li>The model takes your input text</li> | |
| <li>It tokenizes the text (breaks it into words or subwords)</li> | |
| <li>For each position, it considers the context (previous words)</li> | |
| <li>It uses statistical patterns from training data to predict possible next words</li> | |
| <li>It selects one of these candidates (our model chooses randomly)</li> | |
| <li>This process repeats until the response is complete</li> | |
| </ol> | |
| </div> | |
| <p class="mb-3"> | |
| The "Model State" section shows you which patterns the model is using to generate text: | |
| </p> | |
| <div class="p-3 bg-neural-300 rounded-lg text-sm mb-4"> | |
| <ul class="list-disc list-inside space-y-1"> | |
| <li><span class="text-primary-400 font-medium">Using 2-word context</span>: The model is using the previous two words (trigram)</li> | |
| <li><span class="text-accent-400 font-medium">Using 1-word context</span>: The model is using just the previous word (bigram)</li> | |
| <li><span class="text-secondary-400 font-medium">Using random selection</span>: No matching patterns were found, so it's selecting randomly</li> | |
| </ul> | |
| </div> | |
| <p> | |
| You can improve the model's responses by training it with more data using the "Train" tab! | |
| </p> | |
| </div> | |
| <!-- Voice Features section --> | |
| <div id="help-voice-features" class="help-content hidden"> | |
| <h3 class="text-lg font-semibold text-accent-400 mb-3">Using Voice Features</h3> | |
| <div class="grid grid-cols-2 gap-4 mb-4"> | |
| <div class="p-3 bg-neural-300 rounded-lg"> | |
| <h4 class="font-medium text-primary-400 mb-2">Voice Input (Dictation)</h4> | |
| <p class="text-sm mb-3"> | |
| Speak to the app instead of typing with the built-in speech recognition. | |
| </p> | |
| <ol class="list-decimal list-inside text-xs space-y-1"> | |
| <li>Click the microphone icon in the input field</li> | |
| <li>When the sound wave appears, start speaking</li> | |
| <li>The app will transcribe your speech in real-time</li> | |
| <li>When you stop speaking, it automatically processes your request</li> | |
| </ol> | |
| </div> | |
| <div class="p-3 bg-neural-300 rounded-lg"> | |
| <h4 class="font-medium text-accent-400 mb-2">Voice Output (Text-to-Speech)</h4> | |
| <p class="text-sm mb-3"> | |
| Have the app read responses aloud using speech synthesis. | |
| </p> | |
| <ol class="list-decimal list-inside text-xs space-y-1"> | |
| <li>Responses are read automatically when voice is enabled</li> | |
| <li>Click the speaker icon next to "Output" for on-demand speech</li> | |
| <li>Change the voice in the Voice Settings panel</li> | |
| <li>Adjust speech speed using the slider control</li> | |
| </ol> | |
| </div> | |
| </div> | |
| <div class="p-3 bg-neural-300 rounded-lg text-sm"> | |
| <h4 class="font-medium text-primary-400 mb-2">Voice Controls:</h4> | |
| <ul class="list-disc list-inside space-y-1"> | |
| <li>Toggle voice output on/off with the Voice button</li> | |
| <li>Select from available system voices in the dropdown</li> | |
| <li>Adjust the speaking rate from 0.5x (slow) to 2.0x (fast)</li> | |
| <li>Click the microphone again to stop dictation</li> | |
| </ul> | |
| <p class="mt-3 text-accent-400"> | |
| <strong>Note:</strong> Voice features depend on browser support and may work differently across devices. | |
| </p> | |
| </div> | |
| </div> | |
| <!-- Image Generator section --> | |
| <div id="help-image-generator" class="help-content hidden"> | |
| <h3 class="text-lg font-semibold text-accent-400 mb-3">Using the AI Image Generator</h3> | |
| <p class="mb-4"> | |
| The image generator creates abstract algorithmic art that simulates AI-generated imagery. | |
| Real AI image generators like DALL-E and Midjourney use diffusion models trained on millions of images. | |
| </p> | |
| <div class="mb-4 p-3 bg-neural-300 rounded-lg"> | |
| <h4 class="font-medium text-primary-400 mb-2">How to Generate Images:</h4> | |
| <ol class="list-decimal list-inside text-sm space-y-2"> | |
| <li>Go to the "Create" tab and select "Image"</li> | |
| <li>Enter a descriptive prompt (colors, shapes, style)</li> | |
| <li>Adjust the complexity slider to control detail level</li> | |
| <li>Choose a color palette that matches your vision</li> | |
| <li>Click "Generate" to create your image</li> | |
| <li>Save your creation using the download button</li> | |
| </ol> | |
| </div> | |
| <p class="mb-4"> | |
| Each generation creates a unique image based on your inputs and randomness. | |
| The results are abstract patterns that might remind you of AI-generated art. | |
| </p> | |
| <div class="p-3 bg-neural-300 rounded-lg text-sm"> | |
| <h4 class="font-medium text-accent-400">Tips for Better Results:</h4> | |
| <ul class="list-disc list-inside space-y-1"> | |
| <li>Use descriptive terms like "waves," "geometric," or "organic"</li> | |
| <li>Higher complexity creates more detailed patterns</li> | |
| <li>The seed value lets you recreate the same image again</li> | |
| <li>Try different color palettes for dramatic changes</li> | |
| <li>You can combine multiple techniques like "spirals with fractals"</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <!-- Music Generator section --> | |
| <div id="help-music-generator" class="help-content hidden"> | |
| <h3 class="text-lg font-semibold text-accent-400 mb-3">Creating Music with the AI Composer</h3> | |
| <p class="mb-4"> | |
| The music generator creates simple melodic patterns using the Web Audio API. | |
| This simulates how AI music generators work, though real AI music tools like | |
| MusicLM and AudioLDM use much more sophisticated neural networks. | |
| </p> | |
| <div class="mb-4 p-3 bg-neural-300 rounded-lg"> | |
| <h4 class="font-medium text-primary-400 mb-2">How to Create Music:</h4> | |
| <ol class="list-decimal list-inside text-sm space-y-2"> | |
| <li>Go to the "Create" tab and select "Music"</li> | |
| <li>Choose a musical scale (major, minor, pentatonic)</li> | |
| <li>Set the tempo (speed) of your composition</li> | |
| <li>Adjust the complexity slider for pattern variations</li> | |
| <li>Select an instrument type</li> | |
| <li>Click "Generate" to create a melody</li> | |
| <li>Press "Play" to hear your composition</li> | |
| </ol> | |
| </div> | |
| <div class="p-3 bg-neural-300 rounded-lg text-sm"> | |
| <h4 class="font-medium text-accent-400">Understanding the Visualizer:</h4> | |
| <p class="mb-2"> | |
| The audio waveform shows you the volume and pattern of the generated music: | |
| </p> | |
| <ul class="list-disc list-inside space-y-1"> | |
| <li>Taller bars represent louder notes</li> | |
| <li>The pattern shows you the rhythm and structure</li> | |
| <li>Colors represent different frequencies (pitches)</li> | |
| <li>The sequencer grid shows which notes are playing</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <!-- Training Data section --> | |
| <div id="help-training-data" class="help-content hidden"> | |
| <h3 class="text-lg font-semibold text-accent-400 mb-3">Training & Data Sources</h3> | |
| <p class="mb-4"> | |
| The language model learns from the text you provide. The more quality data you give it, | |
| the better its responses will become. You can add data in several ways: | |
| </p> | |
| <div class="mb-4 p-3 bg-neural-300 rounded-lg"> | |
| <h4 class="font-medium text-primary-400 mb-2">Adding Training Data:</h4> | |
| <ul class="list-disc list-inside text-sm space-y-2"> | |
| <li> | |
| <span class="text-accent-400 font-medium">Train tab</span>: | |
| Directly enter text to add to the model's knowledge | |
| </li> | |
| <li> | |
| <span class="text-accent-400 font-medium">Web tab</span>: | |
| Import content from websites (enter URL and fetch) | |
| </li> | |
| <li> | |
| <span class="text-accent-400 font-medium">File tab</span>: | |
| Upload text files (.txt, .md) to train on larger documents | |
| </li> | |
| <li> | |
| <span class="text-accent-400 font-medium">Import/Export</span>: | |
| Save trained models and share them with others | |
| </li> | |
| </ul> | |
| </div> | |
| <div class="mb-4"> | |
| <h4 class="font-medium text-primary-400 mb-2">How Training Works:</h4> | |
| <p class="text-sm"> | |
| The system analyzes patterns in your text, looking at sequences of words (n-grams). | |
| It learns which words tend to follow others, building a statistical model of language. | |
| These patterns are stored and used to generate new text that follows similar patterns. | |
| </p> | |
| </div> | |
| <div class="p-3 bg-neural-300 rounded-lg text-sm"> | |
| <h4 class="font-medium text-accent-400 mb-2">Tips for Better Training:</h4> | |
| <ul class="list-disc list-inside space-y-1"> | |
| <li>Use complete, grammatically correct sentences</li> | |
| <li>Train on text in a similar style to what you want to generate</li> | |
| <li>More data usually leads to better results</li> | |
| <li>Diverse sources help the model learn different patterns</li> | |
| <li>Check the "Sources" tab to see what data the model has learned from</li> | |
| <li>Use the Export feature to save your trained model</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <!-- Tips & Tricks section --> | |
| <div id="help-tips-tricks" class="help-content hidden"> | |
| <h3 class="text-lg font-semibold text-accent-400 mb-3">Tips & Tricks</h3> | |
| <div class="grid grid-cols-2 gap-4 mb-4"> | |
| <div class="p-3 bg-neural-300 rounded-lg"> | |
| <h4 class="font-medium text-primary-400 mb-2">For Better Text Generation</h4> | |
| <ul class="list-disc list-inside text-xs space-y-1"> | |
| <li>Start prompts with relevant context words</li> | |
| <li>Train on specialized text for domain-specific responses</li> | |
| <li>Generate multiple times for different variations</li> | |
| <li>More training data = better quality responses</li> | |
| <li>Import pre-trained models for specialized topics</li> | |
| </ul> | |
| </div> | |
| <div class="p-3 bg-neural-300 rounded-lg"> | |
| <h4 class="font-medium text-primary-400 mb-2">For Better Voice Interaction</h4> | |
| <ul class="list-disc list-inside text-xs space-y-1"> | |
| <li>Speak clearly and at a moderate pace</li> | |
| <li>Use a quiet environment for better recognition</li> | |
| <li>Try different voices to find one you prefer</li> | |
| <li>For longer texts, adjust the speech rate faster</li> | |
| <li>Click the microphone again to cancel dictation</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-4 mb-4"> | |
| <div class="p-3 bg-neural-300 rounded-lg"> | |
| <h4 class="font-medium text-primary-400 mb-2">For Creative Images</h4> | |
| <ul class="list-disc list-inside text-xs space-y-1"> | |
| <li>Mix different style terms for unique results</li> | |
| <li>Save the seed number to recreate favorites</li> | |
| <li>Try "evolving" an image with small changes</li> | |
| <li>Combine contrasting techniques (e.g., "geometric organic")</li> | |
| <li>Custom color palettes create distinctive moods</li> | |
| </ul> | |
| </div> | |
| <div class="p-3 bg-neural-300 rounded-lg"> | |
| <h4 class="font-medium text-primary-400 mb-2">For Interesting Music</h4> | |
| <ul class="list-disc list-inside text-xs space-y-1"> | |
| <li>Minor scales create moodier compositions</li> | |
| <li>Pentatonic scales sound good at any complexity</li> | |
| <li>Higher complexity creates more varied melodies</li> | |
| <li>Slower tempos work better for complex patterns</li> | |
| <li>Try layering different instrument sounds</li> | |
| </ul> | |
| </div> | |
| </div> | |
| <div class="p-3 bg-neural-300 rounded-lg"> | |
| <h4 class="font-medium text-accent-400 mb-2">Keyboard Shortcuts:</h4> | |
| <div class="grid grid-cols-2 gap-2 text-xs"> | |
| <div><span class="bg-neural-100 px-1 rounded">Enter</span> - Submit prompt</div> | |
| <div><span class="bg-neural-100 px-1 rounded">Esc</span> - Close help panel</div> | |
| <div><span class="bg-neural-100 px-1 rounded">Ctrl+M</span> - Toggle microphone</div> | |
| <div><span class="bg-neural-100 px-1 rounded">Space</span> - Play/pause music</div> | |
| <div><span class="bg-neural-100 px-1 rounded">Ctrl+S</span> - Save image/music</div> | |
| <div><span class="bg-neural-100 px-1 rounded">?</span> - Open this help panel</div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Navigation buttons --> | |
| <div class="mt-6 flex justify-between"> | |
| <button id="prev-help" class="px-3 py-1.5 bg-neural-300 text-white rounded-lg text-sm hover:bg-neural-100 transition-colors"> | |
| ← Previous | |
| </button> | |
| <button id="next-help" class="px-3 py-1.5 bg-gradient-to-r from-primary-500 to-accent-500 text-white rounded-lg text-sm hover:from-primary-600 hover:to-accent-600 transition-colors"> | |
| Next → | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Main content container --> | |
| <div class="container mx-auto px-3 py-6 max-w-md relative z-10"> | |
| <h1 class="text-2xl font-bold text-center mb-5 text-transparent bg-clip-text bg-gradient-to-r from-primary-400 to-accent-400 drop-shadow-glow">Neural Nexus Enhanced</h1> | |
| <!-- Visualization area (always visible) --> | |
| <div class="mb-6 glass rounded-xl p-4 shadow-lg border border-gray-700 glow relative overflow-hidden"> | |
| <div class="flex justify-between items-center mb-3"> | |
| <h2 class="text-lg font-semibold text-white drop-shadow-glow-sm">Neural Network</h2> | |
| <div class="flex items-center gap-2"> | |
| <button id="export-btn" class="text-xs bg-gradient-to-r from-primary-500 to-primary-600 text-white px-3 py-1.5 rounded-lg shadow-md btn-shine" title="Export model data"> | |
| Export | |
| </button> | |
| <label for="import-file" class="text-xs bg-gradient-to-r from-accent-500 to-accent-600 text-white px-3 py-1.5 rounded-lg shadow-md cursor-pointer btn-shine" title="Import model data"> | |
| Import | |
| <input id="import-file" type="file" class="hidden" accept=".json"> | |
| </label> | |
| </div> | |
| </div> | |
| <!-- Neural Network Visualization --> | |
| <div id="network-viz" class="h-[170px] w-full rounded-lg glass-dark p-3 mb-3 relative"> | |
| <svg id="neural-network" class="w-full h-full"></svg> | |
| <div class="absolute top-2 right-2"> | |
| <div class="tooltip"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4 text-gray-400 hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
| </svg> | |
| <span class="tooltiptext"> | |
| This visualization shows how neural networks process data. Input nodes (left) receive your text, hidden nodes (middle) process it, and output nodes (right) generate responses. | |
| </span> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Input/Output Display --> | |
| <div class="grid grid-cols-2 gap-3"> | |
| <div> | |
| <h3 class="text-xs font-medium mb-1 flex items-center"> | |
| <span class="inline-block w-2 h-2 rounded-full bg-primary-400 mr-1.5"></span> | |
| Input | |
| <div class="tooltip ml-1"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3 text-gray-400 hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
| </svg> | |
| <span class="tooltiptext"> | |
| Your text is broken into tokens (words) that the model processes. | |
| </span> | |
| </div> | |
| </h3> | |
| <div id="input-viz" class="h-[80px] overflow-auto w-full glass-dark rounded-lg p-2 text-xs"> | |
| <p class="text-gray-400 text-center">Enter input...</p> | |
| </div> | |
| </div> | |
| <div> | |
| <h3 class="text-xs font-medium mb-1 flex items-center"> | |
| <span class="inline-block w-2 h-2 rounded-full bg-accent-400 mr-1.5"></span> | |
| Output | |
| <button id="speak-output" class="ml-2 p-1 text-accent-400 hover:text-white" title="Speak output"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15.536 8.464a5 5 0 010 7.072m2.828-9.9a9 9 0 010 12.728M5.586 15H4a1 1 0 01-1-1v-4a1 1 0 011-1h1.586l4.707-4.707C10.923 3.663 12 4.109 12 5v14c0 .891-1.077 1.337-1.707.707L5.586 15z" /> | |
| </svg> | |
| </button> | |
| <div class="tooltip ml-1"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3 text-gray-400 hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
| </svg> | |
| <span class="tooltiptext"> | |
| Generated text appears here word by word as the model creates it. | |
| </span> | |
| </div> | |
| </h3> | |
| <div id="output-viz" class="h-[80px] overflow-auto w-full glass-dark rounded-lg p-2 text-xs"> | |
| <p class="text-gray-400 text-center">Response will appear here</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Model State --> | |
| <div class="mt-3"> | |
| <h3 class="text-xs font-medium mb-1 flex items-center"> | |
| <span class="inline-block w-2 h-2 rounded-full bg-secondary-400 mr-1.5"></span> | |
| Model State | |
| <div class="tooltip ml-1"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-3 w-3 text-gray-400 hover:text-white" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
| </svg> | |
| <span class="tooltiptext"> | |
| Shows the model's internal patterns and decision-making process during text generation. | |
| </span> | |
| </div> | |
| </h3> | |
| <div id="model-state" class="text-xs h-[60px] overflow-auto glass-dark rounded-lg p-2"></div> | |
| </div> | |
| </div> | |
| <!-- Speech settings panel --> | |
| <div class="mb-4 glass rounded-xl p-3 shadow-lg border border-gray-700"> | |
| <div class="flex justify-between items-center mb-2"> | |
| <h3 class="text-sm font-medium text-white">Voice Settings</h3> | |
| <div class="flex space-x-2"> | |
| <button id="toggle-voice" class="text-xs bg-gradient-to-r from-accent-500 to-accent-600 text-white px-2 py-1 rounded-lg shadow-md btn-shine" title="Toggle voice output"> | |
| <span id="voice-status">Voice: ON</span> | |
| </button> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-3"> | |
| <div> | |
| <label for="voice-select" class="text-xs text-gray-300">Voice</label> | |
| <select id="voice-select" class="w-full text-xs rounded bg-neural-100 text-white border border-gray-700 focus:outline-none focus:ring-1 focus:ring-accent-500"> | |
| <option value="">Loading voices...</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="voice-rate" class="text-xs text-gray-300">Speed</label> | |
| <div class="flex items-center space-x-2"> | |
| <input type="range" id="voice-rate" min="0.5" max="2" step="0.1" value="1" class="w-full h-1 accent-accent-500"> | |
| <span id="rate-value" class="text-xs">1.0</span> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Tabs navigation --> | |
| <div class="flex justify-between mb-4 bg-neural-100/30 rounded-lg p-1 shadow-md"> | |
| <button id="tab-prompt" class="flex-1 py-2 font-medium rounded-md bg-gradient-to-br from-primary-400 to-primary-600 text-white text-sm text-center shadow-md">Prompt</button> | |
| <button id="tab-create" class="flex-1 py-2 font-medium rounded-md bg-transparent text-gray-300 text-sm hover:bg-neural-100/50 transition-all text-center">Create</button> | |
| <button id="tab-web" class="flex-1 py-2 font-medium rounded-md bg-transparent text-gray-300 text-sm hover:bg-neural-100/50 transition-all text-center">Web</button> | |
| <button id="tab-data" class="flex-1 py-2 font-medium rounded-md bg-transparent text-gray-300 text-sm hover:bg-neural-100/50 transition-all text-center">Train</button> | |
| <button id="tab-sources" class="flex-1 py-2 font-medium rounded-md bg-transparent text-gray-300 text-sm hover:bg-neural-100/50 transition-all text-center">Sources</button> | |
| </div> | |
| <!-- Prompt Tab Panel --> | |
| <div id="panel-prompt" class="tab-panel"> | |
| <div class="glass rounded-xl p-4 shadow-lg border border-gray-700"> | |
| <div class="flex gap-2"> | |
| <div class="relative flex-grow"> | |
| <input id="prompt-input" type="text" placeholder="Ask something..." class="w-full pl-4 pr-10 py-3 text-base rounded-lg border border-gray-700 | |
| glass-dark focus:outline-none focus:ring-2 focus:ring-primary-500 text-white placeholder-gray-400"> | |
| <button id="mic-btn" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-gray-400 hover:text-accent-400 transition-colors"> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor"> | |
| <path fill-rule="evenodd" d="M7 4a3 3 0 016 0v4a3 3 0 11-6 0V4zm4 10.93A7.001 7.001 0 0017 8a1 1 0 10-2 0A5 5 0 015 8a1 1 0 00-2 0 7.001 7.001 0 006 6.93V17H6a1 1 0 100 2h8a1 1 0 100-2h-3v-2.07z" clip-rule="evenodd" /> | |
| </svg> | |
| </button> | |
| <!-- Voice recording indicator (hidden by default) --> | |
| <div id="voice-indicator" class="absolute right-3 top-1/2 transform -translate-y-1/2 text-accent-400 hidden"> | |
| <div class="voice-wave"> | |
| <span></span> | |
| <span></span> | |
| <span></span> | |
| <span></span> | |
| <span></span> | |
| </div> | |
| </div> | |
| </div> | |
| <button id="submit-btn" class="px-4 py-2 bg-gradient-to-br from-primary-400 to-primary-600 hover:from-primary-500 hover:to-primary-700 text-white rounded-lg font-medium transition-all shadow-md btn-shine"> | |
| Send | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Create Tab Panel (new) --> | |
| <div id="panel-create" class="tab-panel hidden"> | |
| <div class="glass rounded-xl p-4 shadow-lg border border-gray-700"> | |
| <!-- Creation type selector --> | |
| <div class="flex justify-between mb-4 bg-neural-100/30 rounded-lg p-1 shadow-md"> | |
| <button id="create-type-image" class="flex-1 py-1.5 font-medium rounded-md bg-gradient-to-br from-creative-400 to-creative-600 text-white text-xs text-center shadow-md"> | |
| Image | |
| </button> | |
| <button id="create-type-music" class="flex-1 py-1.5 font-medium rounded-md bg-transparent text-gray-300 text-xs hover:bg-neural-100/50 transition-all text-center"> | |
| Music | |
| </button> | |
| </div> | |
| <!-- Image generation panel --> | |
| <div id="creation-image" class="creation-panel"> | |
| <div class="mb-3"> | |
| <label for="image-prompt" class="text-xs text-gray-300 mb-1 block">Image Description</label> | |
| <input type="text" id="image-prompt" placeholder="Abstract flow with geometric shapes..." class="w-full p-2 text-sm rounded-lg border border-gray-700 glass-dark focus:outline-none focus:ring-2 focus:ring-creative-500 text-white placeholder-gray-400"> | |
| </div> | |
| <div class="grid grid-cols-2 gap-4 mb-3"> | |
| <div> | |
| <label for="image-complexity" class="text-xs text-gray-300 mb-1 block">Complexity</label> | |
| <input type="range" id="image-complexity" min="1" max="10" value="5" class="w-full h-1 accent-creative-500"> | |
| </div> | |
| <div> | |
| <label for="image-seed" class="text-xs text-gray-300 mb-1 block">Seed <span class="text-gray-400">(for reproducibility)</span></label> | |
| <input type="number" id="image-seed" min="1" max="999999" value="42" class="w-full p-1.5 text-sm rounded-lg border border-gray-700 glass-dark focus:outline-none focus:ring-2 focus:ring-creative-500 text-white"> | |
| </div> | |
| </div> | |
| <div class="mb-3"> | |
| <label class="text-xs text-gray-300 mb-1 block">Color Palette</label> | |
| <div class="flex space-x-2"> | |
| <div class="color-swatch active" style="background: linear-gradient(to right, #5D5CDE, #32e2df)" data-palette="neural"></div> | |
| <div class="color-swatch" style="background: linear-gradient(to right, #FF416C, #FF4B2B)" data-palette="sunset"></div> | |
| <div class="color-swatch" style="background: linear-gradient(to right, #56CCF2, #2F80ED)" data-palette="ocean"></div> | |
| <div class="color-swatch" style="background: linear-gradient(to right, #EECDA3, #EF629F)" data-palette="peach"></div> | |
| <div class="color-swatch" style="background: linear-gradient(to right, #8E2DE2, #4A00E0)" data-palette="royal"></div> | |
| <div class="color-swatch" style="background: linear-gradient(to right, #F2994A, #F2C94C)" data-palette="amber"></div> | |
| </div> | |
| </div> | |
| <div class="glass-dark rounded-lg overflow-hidden mb-3"> | |
| <canvas id="image-canvas" width="300" height="200" class="w-full h-40 object-cover"></canvas> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button id="generate-image" class="flex-1 px-3 py-2 bg-gradient-to-br from-creative-400 to-creative-600 hover:from-creative-500 hover:to-creative-700 text-white rounded-lg text-sm transition-all shadow-md btn-shine"> | |
| Generate Image | |
| </button> | |
| <button id="download-image" class="px-3 py-2 bg-neural-100 hover:bg-neural-200 text-white rounded-lg text-sm transition-all shadow-md" disabled> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 16v1a3 3 0 003 3h10a3 3 0 003-3v-1m-4-4l-4 4m0 0l-4-4m4 4V4" /> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| <!-- Music generation panel --> | |
| <div id="creation-music" class="creation-panel hidden"> | |
| <div class="grid grid-cols-2 gap-4 mb-3"> | |
| <div> | |
| <label for="music-scale" class="text-xs text-gray-300 mb-1 block">Musical Scale</label> | |
| <select id="music-scale" class="w-full p-1.5 text-sm rounded-lg border border-gray-700 glass-dark focus:outline-none focus:ring-2 focus:ring-creative-500 text-white"> | |
| <option value="major">Major (Happy)</option> | |
| <option value="minor">Minor (Melancholic)</option> | |
| <option value="pentatonic" selected>Pentatonic (Smooth)</option> | |
| <option value="blues">Blues (Soulful)</option> | |
| </select> | |
| </div> | |
| <div> | |
| <label for="music-tempo" class="text-xs text-gray-300 mb-1 block">Tempo (BPM)</label> | |
| <input type="range" id="music-tempo" min="60" max="180" value="120" class="w-full h-1 accent-creative-500"> | |
| </div> | |
| </div> | |
| <div class="grid grid-cols-2 gap-4 mb-3"> | |
| <div> | |
| <label for="music-complexity" class="text-xs text-gray-300 mb-1 block">Complexity</label> | |
| <input type="range" id="music-complexity" min="1" max="10" value="5" class="w-full h-1 accent-creative-500"> | |
| </div> | |
| <div> | |
| <label for="music-instrument" class="text-xs text-gray-300 mb-1 block">Instrument</label> | |
| <select id="music-instrument" class="w-full p-1.5 text-sm rounded-lg border border-gray-700 glass-dark focus:outline-none focus:ring-2 focus:ring-creative-500 text-white"> | |
| <option value="sine" selected>Sine (Smooth)</option> | |
| <option value="square">Square (Retro)</option> | |
| <option value="sawtooth">Sawtooth (Sharp)</option> | |
| <option value="triangle">Triangle (Gentle)</option> | |
| </select> | |
| </div> | |
| </div> | |
| <div class="glass-dark rounded-lg p-3 mb-3"> | |
| <div id="audio-visualizer" class="h-24 flex items-center justify-center"> | |
| <div class="text-gray-500 text-sm">Audio visualizer will appear here</div> | |
| </div> | |
| </div> | |
| <div class="flex space-x-2"> | |
| <button id="generate-music" class="flex-1 px-3 py-2 bg-gradient-to-br from-creative-400 to-creative-600 hover:from-creative-500 hover:to-creative-700 text-white rounded-lg text-sm transition-all shadow-md btn-shine"> | |
| Generate Music | |
| </button> | |
| <button id="play-music" class="px-3 py-2 bg-neural-100 hover:bg-neural-200 text-white rounded-lg text-sm transition-all shadow-md" disabled> | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
| </svg> | |
| </button> | |
| </div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Web Tab Panel --> | |
| <div id="panel-web" class="tab-panel hidden"> | |
| <div class="glass rounded-xl p-4 shadow-lg border border-gray-700"> | |
| <div class="flex flex-col gap-2"> | |
| <div class="flex gap-2"> | |
| <input id="url-input" type="url" placeholder="Enter website URL" class="w-full px-4 py-3 text-base rounded-lg border border-gray-700 | |
| glass-dark focus:outline-none focus:ring-2 focus:ring-primary-500 text-white placeholder-gray-400"> | |
| <button id="fetch-btn" class="px-4 py-2 bg-gradient-to-br from-primary-400 to-primary-600 hover:from-primary-500 hover:to-primary-700 text-white rounded-lg font-medium transition-all shadow-md btn-shine"> | |
| Fetch | |
| </button> | |
| </div> | |
| <p class="text-xs text-gray-400"> | |
| Try URLs from allowed domains like: wikipedia.org, githubusercontent.com | |
| </p> | |
| </div> | |
| <div id="web-preview" class="mt-3 glass-dark rounded-lg p-3 max-h-[150px] overflow-auto hidden"></div> | |
| </div> | |
| </div> | |
| <!-- Training Data Tab Panel --> | |
| <div id="panel-data" class="tab-panel hidden"> | |
| <div class="glass rounded-xl p-4 shadow-lg border border-gray-700"> | |
| <p class="text-sm mb-2 font-medium text-gray-200">Add new training data:</p> | |
| <textarea id="training-input" rows="3" class="w-full p-3 text-base rounded-lg border border-gray-700 | |
| glass-dark focus:outline-none focus:ring-2 focus:ring-primary-500 text-white placeholder-gray-400" placeholder="Enter text to add to model..."></textarea> | |
| <div class="flex justify-between mt-3 gap-2"> | |
| <div class="flex-1"> | |
| <button id="train-btn" class="w-full px-4 py-2 bg-gradient-to-br from-primary-400 to-primary-600 hover:from-primary-500 hover:to-primary-700 text-white rounded-lg text-sm transition-all shadow-md btn-shine"> | |
| Add to Model | |
| </button> | |
| </div> | |
| <div> | |
| <label for="file-upload" class="px-4 py-2 bg-gradient-to-br from-accent-400 to-accent-600 hover:from-accent-500 hover:to-accent-700 text-white rounded-lg text-sm transition-all shadow-md btn-shine cursor-pointer inline-block"> | |
| Upload File | |
| <input id="file-upload" type="file" accept=".txt,.text,.md" class="hidden"> | |
| </label> | |
| </div> | |
| <div> | |
| <button id="reset-btn" class="px-4 py-2 bg-gradient-to-br from-secondary-400 to-secondary-600 hover:from-secondary-500 hover:to-secondary-700 text-white rounded-lg text-sm transition-all shadow-md btn-shine"> | |
| Reset | |
| </button> | |
| </div> | |
| </div> | |
| <div class="mt-4 pt-4 border-t border-gray-700"> | |
| <p class="text-sm font-medium mb-2 text-gray-200">Model Statistics:</p> | |
| <div id="model-stats" class="text-sm glass-dark p-3 rounded-lg"></div> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Data Sources Tab Panel --> | |
| <div id="panel-sources" class="tab-panel hidden"> | |
| <div class="glass rounded-xl p-4 shadow-lg border border-gray-700"> | |
| <div class="flex justify-between items-center mb-3"> | |
| <h3 class="text-sm font-medium text-gray-200">Knowledge Sources</h3> | |
| <span id="source-count" class="bg-gradient-to-r from-primary-500 to-accent-500 text-white px-2 py-0.5 rounded-full text-xs">0</span> | |
| </div> | |
| <div id="data-sources" class="max-h-[200px] overflow-auto text-sm glass-dark rounded-lg p-3"> | |
| <p class="text-gray-400">No external data sources added yet</p> | |
| </div> | |
| </div> | |
| </div> | |
| <!-- Floating particles for visual effect --> | |
| <div class="fixed inset-0 pointer-events-none z-0"> | |
| <div class="absolute w-3 h-3 rounded-full bg-primary-400/40 top-1/4 left-1/4 animate-float"></div> | |
| <div class="absolute w-2 h-2 rounded-full bg-accent-400/40 top-3/4 left-1/3 animate-float" style="animation-delay: 1s"></div> | |
| <div class="absolute w-4 h-4 rounded-full bg-secondary-400/30 top-1/2 right-1/4 animate-float" style="animation-delay: 0.5s"></div> | |
| <div class="absolute w-2 h-2 rounded-full bg-primary-400/40 bottom-1/4 right-1/3 animate-float" style="animation-delay: 1.5s"></div> | |
| <div class="absolute w-3 h-3 rounded-full bg-creative-400/30 top-1/3 right-1/5 animate-float" style="animation-delay: 0.8s"></div> | |
| </div> | |
| </div> | |
| <script> | |
| // Check for dark mode preference and set it | |
| if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) { | |
| document.documentElement.classList.add('dark'); | |
| } | |
| // Listen for changes in color scheme preference | |
| window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', event => { | |
| if (event.matches) { | |
| document.documentElement.classList.add('dark'); | |
| } else { | |
| document.documentElement.classList.remove('dark'); | |
| } | |
| }); | |
| // Tab switching functionality | |
| function switchTab(tabId) { | |
| // Hide all panels | |
| document.querySelectorAll('.tab-panel').forEach(panel => { | |
| panel.classList.add('hidden'); | |
| }); | |
| // Reset all tab buttons | |
| document.querySelectorAll('[id^="tab-"]').forEach(tab => { | |
| tab.classList.remove('border-primary', 'dark:text-white', 'bg-gradient-to-br', 'from-primary-400', 'to-primary-600'); | |
| tab.classList.add('bg-transparent', 'text-gray-300'); | |
| }); | |
| // Show selected panel and highlight tab | |
| document.getElementById(`panel-${tabId}`).classList.remove('hidden'); | |
| const tabButton = document.getElementById(`tab-${tabId}`); | |
| tabButton.classList.remove('bg-transparent', 'text-gray-300'); | |
| tabButton.classList.add('bg-gradient-to-br', 'from-primary-400', 'to-primary-600', 'text-white'); | |
| } | |
| // Creation tab type switching | |
| function switchCreationType(type) { | |
| // Hide all creation panels | |
| document.querySelectorAll('.creation-panel').forEach(panel => { | |
| panel.classList.add('hidden'); | |
| }); | |
| // Reset all type buttons | |
| document.querySelectorAll('[id^="create-type-"]').forEach(btn => { | |
| btn.classList.remove('bg-gradient-to-br', 'from-creative-400', 'to-creative-600', 'text-white'); | |
| btn.classList.add('bg-transparent', 'text-gray-300'); | |
| }); | |
| // Show selected panel and highlight button | |
| document.getElementById(`creation-${type}`).classList.remove('hidden'); | |
| const typeButton = document.getElementById(`create-type-${type}`); | |
| typeButton.classList.remove('bg-transparent', 'text-gray-300'); | |
| typeButton.classList.add('bg-gradient-to-br', 'from-creative-400', 'to-creative-600', 'text-white'); | |
| } | |
| // Help navigation | |
| function switchHelpSection(sectionId) { | |
| // Hide all help content sections | |
| document.querySelectorAll('.help-content').forEach(section => { | |
| section.classList.add('hidden'); | |
| }); | |
| // Remove active class from all steps | |
| document.querySelectorAll('.help-step').forEach(step => { | |
| step.classList.remove('active'); | |
| }); | |
| // Show selected section and highlight step | |
| document.getElementById(`help-${sectionId}`).classList.remove('hidden'); | |
| document.querySelector(`.help-step[data-step="${sectionId}"]`).classList.add('active'); | |
| // Update current section | |
| currentHelpSection = sectionId; | |
| } | |
| // Store the current help section | |
| let currentHelpSection = 'neural-network'; | |
| // Help section ordering | |
| const helpSections = [ | |
| 'neural-network', | |
| 'language-model', | |
| 'voice-features', | |
| 'image-generator', | |
| 'music-generator', | |
| 'training-data', | |
| 'tips-tricks' | |
| ]; | |
| // Simple N-gram based LLM | |
| class SimpleLLM { | |
| constructor() { | |
| // Load from localStorage or initialize with defaults | |
| this.loadOrInitialize(); | |
| } | |
| loadOrInitialize() { | |
| try { | |
| const savedData = localStorage.getItem('simpleLLM'); | |
| if (savedData) { | |
| const parsed = JSON.parse(savedData); | |
| this.ngramMap = parsed.ngramMap || {}; | |
| this.vocabulary = new Set(parsed.vocabulary || []); | |
| this.trainingCount = parsed.trainingCount || 0; | |
| this.dataSources = parsed.dataSources || []; | |
| } else { | |
| this.reset(); | |
| // Pre-load with some example data | |
| this.train("Neural networks are systems of interconnected nodes that process data."); | |
| this.train("Large language models learn patterns from text data to generate responses."); | |
| this.train("AI systems use neural networks to understand and generate human language."); | |
| this.train("Machine learning algorithms improve through training on example data."); | |
| this.train("Natural language processing helps computers understand human language."); | |
| this.dataSources = [{ | |
| type: "initial", | |
| name: "Initial model data", | |
| date: new Date().toISOString() | |
| }]; | |
| } | |
| } catch (error) { | |
| console.error("Error loading model:", error); | |
| this.reset(); | |
| } | |
| this.updateStats(); | |
| this.updateSourcesList(); | |
| } | |
| reset() { | |
| // Initialize model | |
| this.ngramMap = {}; // Maps n-grams to possible next words | |
| this.vocabulary = new Set(); // Set of known words | |
| this.trainingCount = 0; | |
| this.dataSources = []; | |
| // Save empty state | |
| this.save(); | |
| this.updateStats(); | |
| this.updateSourcesList(); | |
| } | |
| save() { | |
| // Convert Set to Array for JSON serialization | |
| const dataToSave = { | |
| ngramMap: this.ngramMap, | |
| vocabulary: Array.from(this.vocabulary), | |
| trainingCount: this.trainingCount, | |
| dataSources: this.dataSources | |
| }; | |
| try { | |
| localStorage.setItem('simpleLLM', JSON.stringify(dataToSave)); | |
| } catch (error) { | |
| console.error("Error saving model:", error); | |
| } | |
| return dataToSave; // Return for external export | |
| } | |
| // Export model data as JSON file | |
| exportModel() { | |
| const modelData = this.save(); | |
| const dataStr = JSON.stringify(modelData, null, 2); | |
| const dataUri = 'data:application/json;charset=utf-8,' + encodeURIComponent(dataStr); | |
| const exportName = 'llm-model-' + new Date().toISOString().slice(0, 10) + '.json'; | |
| const linkElement = document.createElement('a'); | |
| linkElement.setAttribute('href', dataUri); | |
| linkElement.setAttribute('download', exportName); | |
| linkElement.click(); | |
| } | |
| // Import model from JSON file | |
| importModel(jsonData) { | |
| try { | |
| const data = JSON.parse(jsonData); | |
| // Validate data format | |
| if (!data.ngramMap || !Array.isArray(data.vocabulary)) { | |
| throw new Error("Invalid model data format"); | |
| } | |
| this.ngramMap = data.ngramMap; | |
| this.vocabulary = new Set(data.vocabulary); | |
| this.trainingCount = data.trainingCount || 0; | |
| this.dataSources = data.dataSources || []; | |
| // Add import source if not already noted | |
| this.dataSources.push({ | |
| type: "import", | |
| name: "Imported model data", | |
| date: new Date().toISOString() | |
| }); | |
| this.save(); | |
| this.updateStats(); | |
| this.updateSourcesList(); | |
| return true; | |
| } catch (error) { | |
| console.error("Error importing model:", error); | |
| return false; | |
| } | |
| } | |
| tokenize(text) { | |
| // Simple tokenization (split by spaces and punctuation) | |
| return text.toLowerCase() | |
| .replace(/[^\w\s']|_/g, " ") // Replace punctuation with spaces | |
| .replace(/\s+/g, " ") // Collapse multiple spaces | |
| .trim() // Trim leading/trailing spaces | |
| .split(" ") // Split into words | |
| .filter(word => word.length > 0); // Remove empty strings | |
| } | |
| addDataSource(source) { | |
| this.dataSources.push({ | |
| ...source, | |
| date: new Date().toISOString() | |
| }); | |
| this.save(); | |
| this.updateSourcesList(); | |
| } | |
| train(text, source = null) { | |
| // Tokenize the input text | |
| const tokens = this.tokenize(text); | |
| if (tokens.length < 2) return tokens; // Need at least 2 tokens to train | |
| // Add words to vocabulary | |
| tokens.forEach(token => this.vocabulary.add(token)); | |
| // Build n-gram model (for bigrams and trigrams) | |
| for (let i = 0; i < tokens.length - 1; i++) { | |
| // Bigrams | |
| const bigram = tokens[i]; | |
| const nextWord = tokens[i + 1]; | |
| if (!this.ngramMap[bigram]) { | |
| this.ngramMap[bigram] = []; | |
| } | |
| this.ngramMap[bigram].push(nextWord); | |
| // Trigrams | |
| if (i < tokens.length - 2) { | |
| const trigram = `${tokens[i]} ${tokens[i + 1]}`; | |
| const nextWordAfterTrigram = tokens[i + 2]; | |
| if (!this.ngramMap[trigram]) { | |
| this.ngramMap[trigram] = []; | |
| } | |
| this.ngramMap[trigram].push(nextWordAfterTrigram); | |
| } | |
| } | |
| this.trainingCount++; | |
| // Add data source if provided | |
| if (source) { | |
| this.addDataSource(source); | |
| } | |
| this.save(); | |
| this.updateStats(); | |
| return tokens; | |
| } | |
| generate(prompt, maxLength = 15) { | |
| // Tokenize the prompt | |
| let tokens = this.tokenize(prompt); | |
| // If prompt is empty or no tokens, start with a random word | |
| if (tokens.length === 0) { | |
| const vocabArray = Array.from(this.vocabulary); | |
| if (vocabArray.length === 0) return "Model needs training data first."; | |
| tokens = [vocabArray[Math.floor(Math.random() * vocabArray.length)]]; | |
| } | |
| // Keep last 2 tokens for context if we have more | |
| if (tokens.length > 2) { | |
| tokens = tokens.slice(-2); | |
| } | |
| // Generate text | |
| const initialTokens = [...tokens]; | |
| const steps = []; | |
| for (let i = 0; i < maxLength; i++) { | |
| // Try to use trigram if available | |
| let nextWordOptions = []; | |
| let contextType = ''; | |
| if (tokens.length >= 2) { | |
| const trigram = `${tokens[tokens.length - 2]} ${tokens[tokens.length - 1]}`; | |
| nextWordOptions = this.ngramMap[trigram] || []; | |
| if (nextWordOptions.length > 0) { | |
| contextType = 'trigram'; | |
| } | |
| } | |
| // Fall back to bigram if no trigram match | |
| if (nextWordOptions.length === 0 && tokens.length >= 1) { | |
| const bigram = tokens[tokens.length - 1]; | |
| nextWordOptions = this.ngramMap[bigram] || []; | |
| if (nextWordOptions.length > 0) { | |
| contextType = 'bigram'; | |
| } | |
| } | |
| // If no match, select random word from vocabulary | |
| if (nextWordOptions.length === 0) { | |
| const vocabArray = Array.from(this.vocabulary); | |
| if (vocabArray.length === 0) break; | |
| nextWordOptions = [vocabArray[Math.floor(Math.random() * vocabArray.length)]]; | |
| contextType = 'random'; | |
| } | |
| // Select a random next word from options | |
| const nextWord = nextWordOptions[Math.floor(Math.random() * nextWordOptions.length)]; | |
| tokens.push(nextWord); | |
| // Record this step for visualization | |
| steps.push({ | |
| context: tokens.slice(-3, -1).join(' '), | |
| contextType: contextType, | |
| options: nextWordOptions, | |
| selected: nextWord | |
| }); | |
| // End if we generated a period | |
| if (nextWord.endsWith('.')) { | |
| break; | |
| } | |
| } | |
| // Format and return | |
| return { | |
| prompt: initialTokens.join(' '), | |
| generated: tokens.slice(initialTokens.length).join(' '), | |
| full: tokens.join(' '), | |
| steps: steps | |
| }; | |
| } | |
| getState() { | |
| // Return a sample of the model's internal state for visualization | |
| const ngrams = Object.keys(this.ngramMap).slice(0, 5); | |
| const state = {}; | |
| ngrams.forEach(ngram => { | |
| state[ngram] = this.ngramMap[ngram].slice(0, 3); | |
| }); | |
| return state; | |
| } | |
| updateStats() { | |
| const statsEl = document.getElementById('model-stats'); | |
| if (!statsEl) return; | |
| statsEl.innerHTML = ` | |
| <div>Training examples: ${this.trainingCount}</div> | |
| <div>Vocabulary size: ${this.vocabulary.size} words</div> | |
| <div>N-gram patterns: ${Object.keys(this.ngramMap).length}</div> | |
| `; | |
| } | |
| updateSourcesList() { | |
| const sourcesEl = document.getElementById('data-sources'); | |
| const countEl = document.getElementById('source-count'); | |
| if (!sourcesEl || !countEl) return; | |
| // Update count badge | |
| countEl.textContent = this.dataSources.length; | |
| // No sources yet | |
| if (this.dataSources.length === 0) { | |
| sourcesEl.innerHTML = '<p class="text-gray-500 dark:text-gray-400">No external data sources added yet</p>'; | |
| return; | |
| } | |
| // Display the list of sources | |
| sourcesEl.innerHTML = ''; | |
| this.dataSources.forEach((source, index) => { | |
| const sourceEl = document.createElement('div'); | |
| sourceEl.className = 'py-1 border-b dark:border-gray-700 last:border-b-0'; | |
| const date = new Date(source.date); | |
| const dateStr = date.toLocaleDateString() + ' ' + date.toLocaleTimeString([], { | |
| hour: '2-digit', | |
| minute: '2-digit' | |
| }); | |
| let typeLabel = ''; | |
| let typeBgColor = ''; | |
| switch (source.type) { | |
| case 'initial': | |
| typeLabel = 'Initial Data'; | |
| typeBgColor = 'bg-gray-500'; | |
| break; | |
| case 'training': | |
| typeLabel = 'Manual Training'; | |
| typeBgColor = 'bg-purple-500'; | |
| break; | |
| case 'web': | |
| typeLabel = 'Web Content'; | |
| typeBgColor = 'bg-blue-500'; | |
| break; | |
| case 'file': | |
| typeLabel = 'Text File'; | |
| typeBgColor = 'bg-green-500'; | |
| break; | |
| case 'import': | |
| typeLabel = 'Imported'; | |
| typeBgColor = 'bg-yellow-500'; | |
| break; | |
| default: | |
| typeLabel = 'Unknown'; | |
| typeBgColor = 'bg-gray-500'; | |
| } | |
| const typeBadge = `<span class="inline-block px-2 py-0.5 text-xs ${typeBgColor} text-white rounded-full mr-2">${typeLabel}</span>`; | |
| sourceEl.innerHTML = ` | |
| <div class="flex justify-between"> | |
| <div>${typeBadge} ${source.name}</div> | |
| <div class="text-xs text-gray-500 dark:text-gray-400">${dateStr}</div> | |
| </div> | |
| `; | |
| sourcesEl.appendChild(sourceEl); | |
| }); | |
| } | |
| } | |
| // Speech Synthesis Controller | |
| class SpeechController { | |
| constructor() { | |
| this.synth = window.speechSynthesis; | |
| this.voices = []; | |
| this.isVoiceEnabled = true; | |
| this.currentUtterance = null; | |
| this.voiceRate = 1.0; | |
| // Initialize voices when they are available | |
| if (this.synth.onvoiceschanged !== undefined) { | |
| this.synth.onvoiceschanged = this.loadVoices.bind(this); | |
| } | |
| // Initial voice loading | |
| this.loadVoices(); | |
| // Initialize voice settings UI | |
| this.initializeVoiceSettings(); | |
| } | |
| loadVoices() { | |
| // Get available voices | |
| this.voices = this.synth.getVoices(); | |
| // Populate voice select | |
| const voiceSelect = document.getElementById('voice-select'); | |
| if (voiceSelect) { | |
| voiceSelect.innerHTML = ''; | |
| // Sort voices - English first, then by language | |
| const englishVoices = this.voices.filter(voice => voice.lang.includes('en')); | |
| const otherVoices = this.voices.filter(voice => !voice.lang.includes('en')); | |
| const sortedVoices = [...englishVoices, ...otherVoices]; | |
| sortedVoices.forEach(voice => { | |
| const option = document.createElement('option'); | |
| option.value = voice.name; | |
| option.textContent = `${voice.name} (${voice.lang})`; | |
| voiceSelect.appendChild(option); | |
| }); | |
| // Select a default voice (English if available) | |
| if (englishVoices.length > 0) { | |
| voiceSelect.value = englishVoices[0].name; | |
| } | |
| } | |
| } | |
| initializeVoiceSettings() { | |
| const toggleVoiceBtn = document.getElementById('toggle-voice'); | |
| const voiceSelect = document.getElementById('voice-select'); | |
| const voiceRateInput = document.getElementById('voice-rate'); | |
| const rateValue = document.getElementById('rate-value'); | |
| // Toggle voice on/off | |
| toggleVoiceBtn.addEventListener('click', () => { | |
| this.isVoiceEnabled = !this.isVoiceEnabled; | |
| document.getElementById('voice-status').textContent = | |
| this.isVoiceEnabled ? 'Voice: ON' : 'Voice: OFF'; | |
| // If disabling, stop any current speech | |
| if (!this.isVoiceEnabled) { | |
| this.stop(); | |
| } | |
| }); | |
| // Update voice selection | |
| voiceSelect.addEventListener('change', () => { | |
| // Stop any current speech when changing voice | |
| this.stop(); | |
| }); | |
| // Update speech rate | |
| voiceRateInput.addEventListener('input', () => { | |
| this.voiceRate = parseFloat(voiceRateInput.value); | |
| rateValue.textContent = this.voiceRate.toFixed(1); | |
| // If already speaking, update rate in real-time | |
| if (this.currentUtterance) { | |
| this.currentUtterance.rate = this.voiceRate; | |
| } | |
| }); | |
| // Direct speak button | |
| document.getElementById('speak-output').addEventListener('click', () => { | |
| // Get output text to speak | |
| const outputViz = document.getElementById('output-viz'); | |
| const text = outputViz.textContent.trim(); | |
| // Speak only if there's something to say and it's not the placeholder | |
| if (text && !text.includes('Response will appear here')) { | |
| this.speak(text); | |
| } | |
| }); | |
| } | |
| speak(text) { | |
| // Don't speak if voice is disabled | |
| if (!this.isVoiceEnabled) return; | |
| // Stop any current speech first | |
| this.stop(); | |
| // Create a new utterance | |
| const utterance = new SpeechSynthesisUtterance(text); | |
| // Set voice if selected | |
| const voiceSelect = document.getElementById('voice-select'); | |
| if (voiceSelect && voiceSelect.value) { | |
| const selectedVoice = this.voices.find(voice => voice.name === voiceSelect.value); | |
| if (selectedVoice) { | |
| utterance.voice = selectedVoice; | |
| } | |
| } | |
| // Set rate | |
| utterance.rate = this.voiceRate; | |
| // Store the utterance for later control | |
| this.currentUtterance = utterance; | |
| // Speak | |
| this.synth.speak(utterance); | |
| } | |
| stop() { | |
| if (this.synth.speaking) { | |
| this.synth.cancel(); | |
| } | |
| this.currentUtterance = null; | |
| } | |
| } | |
| // Speech Recognition Controller | |
| class SpeechRecognitionController { | |
| constructor() { | |
| // Initialize SpeechRecognition with browser prefixes | |
| const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; | |
| if (SpeechRecognition) { | |
| this.recognition = new SpeechRecognition(); | |
| this.recognition.continuous = false; | |
| this.recognition.interimResults = true; | |
| this.recognition.lang = 'en-US'; | |
| // Set up event handlers | |
| this.recognition.onstart = this.handleStart.bind(this); | |
| this.recognition.onresult = this.handleResult.bind(this); | |
| this.recognition.onerror = this.handleError.bind(this); | |
| this.recognition.onend = this.handleEnd.bind(this); | |
| this.isListening = false; | |
| this.initializeMicButton(); | |
| } else { | |
| console.error('Speech recognition not supported in this browser'); | |
| document.getElementById('mic-btn').style.display = 'none'; | |
| } | |
| } | |
| initializeMicButton() { | |
| const micBtn = document.getElementById('mic-btn'); | |
| const voiceIndicator = document.getElementById('voice-indicator'); | |
| micBtn.addEventListener('click', () => { | |
| if (this.isListening) { | |
| this.stop(); | |
| } else { | |
| this.start(); | |
| } | |
| }); | |
| } | |
| start() { | |
| if (!this.isListening) { | |
| try { | |
| this.recognition.start(); | |
| } catch (e) { | |
| console.error('Speech recognition error:', e); | |
| } | |
| } | |
| } | |
| stop() { | |
| if (this.isListening) { | |
| this.recognition.stop(); | |
| } | |
| } | |
| handleStart() { | |
| this.isListening = true; | |
| // Show voice indicator, hide mic button | |
| document.getElementById('mic-btn').style.display = 'none'; | |
| document.getElementById('voice-indicator').style.display = 'block'; | |
| // Clear input field | |
| document.getElementById('prompt-input').value = ''; | |
| } | |
| handleResult(event) { | |
| const transcript = Array.from(event.results) | |
| .map(result => result[0].transcript) | |
| .join(''); | |
| // Update input field with recognized text | |
| const inputField = document.getElementById('prompt-input'); | |
| inputField.value = transcript; | |
| // If this is a final result and we have something to submit, do it | |
| if (event.results[0].isFinal && transcript.trim() !== '') { | |
| // Wait a brief moment before submitting to show the final transcript | |
| setTimeout(() => { | |
| if (this.isListening) { // Check if still listening before submitting | |
| document.getElementById('submit-btn').click(); | |
| } | |
| }, 500); | |
| } | |
| } | |
| handleError(event) { | |
| console.error('Speech recognition error', event.error); | |
| // Show error feedback | |
| const inputField = document.getElementById('prompt-input'); | |
| inputField.placeholder = 'Speech recognition error. Try again.'; | |
| // Reset after short delay | |
| setTimeout(() => { | |
| inputField.placeholder = 'Ask something...'; | |
| }, 3000); | |
| this.resetUI(); | |
| } | |
| handleEnd() { | |
| this.isListening = false; | |
| this.resetUI(); | |
| } | |
| resetUI() { | |
| // Hide voice indicator, show mic button | |
| document.getElementById('mic-btn').style.display = 'block'; | |
| document.getElementById('voice-indicator').style.display = 'none'; | |
| } | |
| } | |
| // AI Image Generator | |
| class ImageGenerator { | |
| constructor() { | |
| this.canvas = document.getElementById('image-canvas'); | |
| this.ctx = this.canvas.getContext('2d'); | |
| this.complexity = 5; // Default complexity | |
| this.seed = 42; // Default seed | |
| this.palette = 'neural'; // Default color palette | |
| this.hasImage = false; | |
| // Initialize event listeners | |
| this.initializeControls(); | |
| } | |
| initializeControls() { | |
| // Generate button | |
| document.getElementById('generate-image').addEventListener('click', () => { | |
| this.generateImage(); | |
| }); | |
| // Download button | |
| document.getElementById('download-image').addEventListener('click', () => { | |
| this.downloadImage(); | |
| }); | |
| // Complexity slider | |
| document.getElementById('image-complexity').addEventListener('input', (e) => { | |
| this.complexity = parseInt(e.target.value); | |
| }); | |
| // Seed input | |
| document.getElementById('image-seed').addEventListener('change', (e) => { | |
| this.seed = parseInt(e.target.value); | |
| }); | |
| // Color palette selection | |
| document.querySelectorAll('.color-swatch').forEach(swatch => { | |
| swatch.addEventListener('click', () => { | |
| // Remove active class from all swatches | |
| document.querySelectorAll('.color-swatch').forEach(s => { | |
| s.classList.remove('active'); | |
| }); | |
| // Add active class to clicked swatch | |
| swatch.classList.add('active'); | |
| // Update palette | |
| this.palette = swatch.dataset.palette; | |
| }); | |
| }); | |
| } | |
| // Seeded random number generator for reproducible results | |
| seededRandom() { | |
| const x = Math.sin(this.seed++) * 10000; | |
| return x - Math.floor(x); | |
| } | |
| // Get colors for the selected palette | |
| getColors() { | |
| const palettes = { | |
| neural: ['#5D5CDE', '#32e2df', '#7F7FFE', '#1bc5c2'], | |
| sunset: ['#FF416C', '#FF4B2B', '#f7b733', '#fc4a1a'], | |
| ocean: ['#56CCF2', '#2F80ED', '#38ef7d', '#11998e'], | |
| peach: ['#EECDA3', '#EF629F', '#ee9ca7', '#ffdde1'], | |
| royal: ['#8E2DE2', '#4A00E0', '#6a3093', '#a044ff'], | |
| amber: ['#F2994A', '#F2C94C', '#f7971e', '#FFD200'] | |
| }; | |
| return palettes[this.palette] || palettes.neural; | |
| } | |
| // Generate a procedural image based on settings | |
| generateImage() { | |
| const width = this.canvas.width; | |
| const height = this.canvas.height; | |
| const colors = this.getColors(); | |
| const prompt = document.getElementById('image-prompt').value.toLowerCase() || 'abstract pattern'; | |
| // Clear canvas | |
| this.ctx.clearRect(0, 0, width, height); | |
| // Draw background gradient | |
| const bgGradient = this.ctx.createLinearGradient(0, 0, width, height); | |
| bgGradient.addColorStop(0, colors[0] + '22'); // Semi-transparent | |
| bgGradient.addColorStop(1, colors[1] + '22'); | |
| this.ctx.fillStyle = bgGradient; | |
| this.ctx.fillRect(0, 0, width, height); | |
| // Choose techniques based on prompt | |
| const techniques = { | |
| wave: prompt.includes('wave') || prompt.includes('flow') || this.seededRandom() > 0.7, | |
| circle: prompt.includes('circle') || prompt.includes('round') || this.seededRandom() > 0.7, | |
| line: prompt.includes('line') || prompt.includes('stripe') || this.seededRandom() > 0.7, | |
| dot: prompt.includes('dot') || prompt.includes('point') || this.seededRandom() > 0.7, | |
| spiral: prompt.includes('spiral') || prompt.includes('swirl') || this.seededRandom() > 0.8, | |
| fractal: prompt.includes('fractal') || prompt.includes('complex') || this.seededRandom() > 0.9, | |
| geometric: prompt.includes('geometric') || prompt.includes('pattern') || this.seededRandom() > 0.6, | |
| organic: prompt.includes('organic') || prompt.includes('natural') || this.seededRandom() > 0.7 | |
| }; | |
| // Apply chosen techniques | |
| if (techniques.wave) this.drawWaves(width, height, colors); | |
| if (techniques.circle) this.drawCircles(width, height, colors); | |
| if (techniques.line) this.drawLines(width, height, colors); | |
| if (techniques.dot) this.drawDots(width, height, colors); | |
| if (techniques.spiral) this.drawSpirals(width, height, colors); | |
| if (techniques.geometric) this.drawGeometric(width, height, colors); | |
| if (techniques.organic) this.drawOrganic(width, height, colors); | |
| if (techniques.fractal) this.drawFractal(width, height, colors); | |
| // Add noise overlay for texture | |
| this.addNoise(width, height, 0.03); | |
| // Enable download button | |
| document.getElementById('download-image').disabled = false; | |
| this.hasImage = true; | |
| } | |
| drawWaves(width, height, colors) { | |
| const numWaves = 3 + Math.floor(this.complexity * 0.8); | |
| for (let i = 0; i < numWaves; i++) { | |
| const color = colors[i % colors.length]; | |
| const amplitude = 10 + (this.seededRandom() * this.complexity * 5); | |
| const frequency = 0.01 + (this.seededRandom() * 0.05); | |
| const phase = this.seededRandom() * Math.PI * 2; | |
| const opacity = 0.1 + (this.seededRandom() * 0.3); | |
| this.ctx.beginPath(); | |
| for (let x = 0; x < width; x++) { | |
| const y = (height / 2) + Math.sin(x * frequency + phase) * amplitude; | |
| if (x === 0) { | |
| this.ctx.moveTo(x, y); | |
| } else { | |
| this.ctx.lineTo(x, y); | |
| } | |
| } | |
| this.ctx.strokeStyle = color + Math.floor(opacity * 255).toString(16).padStart(2, '0'); | |
| this.ctx.lineWidth = 2 + (this.seededRandom() * 4); | |
| this.ctx.stroke(); | |
| } | |
| } | |
| drawCircles(width, height, colors) { | |
| const numCircles = 5 + Math.floor(this.complexity * 0.7); | |
| for (let i = 0; i < numCircles; i++) { | |
| const color = colors[i % colors.length]; | |
| const x = this.seededRandom() * width; | |
| const y = this.seededRandom() * height; | |
| const radius = 5 + (this.seededRandom() * this.complexity * 8); | |
| const opacity = 0.1 + (this.seededRandom() * 0.4); | |
| this.ctx.beginPath(); | |
| this.ctx.arc(x, y, radius, 0, Math.PI * 2); | |
| this.ctx.fillStyle = color + Math.floor(opacity * 255).toString(16).padStart(2, '0'); | |
| this.ctx.fill(); | |
| } | |
| } | |
| drawLines(width, height, colors) { | |
| const numLines = 5 + Math.floor(this.complexity * 1.2); | |
| for (let i = 0; i < numLines; i++) { | |
| const color = colors[i % colors.length]; | |
| const startX = this.seededRandom() * width; | |
| const startY = this.seededRandom() * height; | |
| const endX = this.seededRandom() * width; | |
| const endY = this.seededRandom() * height; | |
| const opacity = 0.1 + (this.seededRandom() * 0.5); | |
| this.ctx.beginPath(); | |
| this.ctx.moveTo(startX, startY); | |
| this.ctx.lineTo(endX, endY); | |
| this.ctx.strokeStyle = color + Math.floor(opacity * 255).toString(16).padStart(2, '0'); | |
| this.ctx.lineWidth = 1 + (this.seededRandom() * 3); | |
| this.ctx.stroke(); | |
| } | |
| } | |
| drawDots(width, height, colors) { | |
| const numDots = 100 + (this.complexity * 50); | |
| for (let i = 0; i < numDots; i++) { | |
| const color = colors[i % colors.length]; | |
| const x = this.seededRandom() * width; | |
| const y = this.seededRandom() * height; | |
| const radius = 0.5 + (this.seededRandom() * 2); | |
| const opacity = 0.2 + (this.seededRandom() * 0.5); | |
| this.ctx.beginPath(); | |
| this.ctx.arc(x, y, radius, 0, Math.PI * 2); | |
| this.ctx.fillStyle = color + Math.floor(opacity * 255).toString(16).padStart(2, '0'); | |
| this.ctx.fill(); | |
| } | |
| } | |
| drawSpirals(width, height, colors) { | |
| const centerX = width / 2; | |
| const centerY = height / 2; | |
| const maxRadius = Math.min(width, height) / 2 * 0.8; | |
| const numSpirals = 1 + Math.floor(this.complexity * 0.4); | |
| for (let s = 0; s < numSpirals; s++) { | |
| const color = colors[s % colors.length]; | |
| const tightness = 0.2 + (this.seededRandom() * 0.6); | |
| const opacity = 0.2 + (this.seededRandom() * 0.6); | |
| const startAngle = this.seededRandom() * Math.PI * 2; | |
| const direction = this.seededRandom() > 0.5 ? 1 : -1; | |
| this.ctx.beginPath(); | |
| for (let i = 0; i < 100 * this.complexity; i++) { | |
| const angle = startAngle + (direction * i * tightness * 0.1); | |
| const radius = (i / (100 * this.complexity)) * maxRadius; | |
| const x = centerX + Math.cos(angle) * radius; | |
| const y = centerY + Math.sin(angle) * radius; | |
| if (i === 0) { | |
| this.ctx.moveTo(x, y); | |
| } else { | |
| this.ctx.lineTo(x, y); | |
| } | |
| } | |
| this.ctx.strokeStyle = color + Math.floor(opacity * 255).toString(16).padStart(2, '0'); | |
| this.ctx.lineWidth = 1 + (this.seededRandom() * 2); | |
| this.ctx.stroke(); | |
| } | |
| } | |
| drawGeometric(width, height, colors) { | |
| const numShapes = 3 + Math.floor(this.complexity * 0.8); | |
| for (let i = 0; i < numShapes; i++) { | |
| const color = colors[i % colors.length]; | |
| const x = this.seededRandom() * width; | |
| const y = this.seededRandom() * height; | |
| const size = 10 + (this.seededRandom() * this.complexity * 5); | |
| const opacity = 0.1 + (this.seededRandom() * 0.3); | |
| const rotation = this.seededRandom() * Math.PI * 2; | |
| this.ctx.save(); | |
| this.ctx.translate(x, y); | |
| this.ctx.rotate(rotation); | |
| // Choose shape type | |
| const shapeType = Math.floor(this.seededRandom() * 3); | |
| this.ctx.beginPath(); | |
| if (shapeType === 0) { | |
| // Square | |
| this.ctx.rect(-size / 2, -size / 2, size, size); | |
| } else if (shapeType === 1) { | |
| // Triangle | |
| this.ctx.moveTo(0, -size / 2); | |
| this.ctx.lineTo(-size / 2, size / 2); | |
| this.ctx.lineTo(size / 2, size / 2); | |
| this.ctx.closePath(); | |
| } else { | |
| // Hexagon | |
| for (let j = 0; j < 6; j++) { | |
| const angle = (j * Math.PI) / 3; | |
| const hx = Math.cos(angle) * size / 2; | |
| const hy = Math.sin(angle) * size / 2; | |
| if (j === 0) { | |
| this.ctx.moveTo(hx, hy); | |
| } else { | |
| this.ctx.lineTo(hx, hy); | |
| } | |
| } | |
| this.ctx.closePath(); | |
| } | |
| this.ctx.fillStyle = color + Math.floor(opacity * 255).toString(16).padStart(2, '0'); | |
| this.ctx.fill(); | |
| this.ctx.restore(); | |
| } | |
| } | |
| drawOrganic(width, height, colors) { | |
| const numShapes = 3 + Math.floor(this.complexity * 0.7); | |
| for (let i = 0; i < numShapes; i++) { | |
| const color = colors[i % colors.length]; | |
| const centerX = this.seededRandom() * width; | |
| const centerY = this.seededRandom() * height; | |
| const radius = 10 + (this.seededRandom() * this.complexity * 7); | |
| const opacity = 0.1 + (this.seededRandom() * 0.3); | |
| this.ctx.beginPath(); | |
| // Create organic shape with perlin-like noise | |
| const points = 10 + Math.floor(this.seededRandom() * 8); | |
| for (let j = 0; j <= points; j++) { | |
| const angle = (j / points) * Math.PI * 2; | |
| const noise = 0.5 + (this.seededRandom() * 0.5); | |
| const adjustedRadius = radius * noise; | |
| const x = centerX + Math.cos(angle) * adjustedRadius; | |
| const y = centerY + Math.sin(angle) * adjustedRadius; | |
| if (j === 0) { | |
| this.ctx.moveTo(x, y); | |
| } else { | |
| const cpx1 = centerX + Math.cos(angle - 0.1) * adjustedRadius * 1.2; | |
| const cpy1 = centerY + Math.sin(angle - 0.1) * adjustedRadius * 1.2; | |
| this.ctx.quadraticCurveTo(cpx1, cpy1, x, y); | |
| } | |
| } | |
| this.ctx.fillStyle = color + Math.floor(opacity * 255).toString(16).padStart(2, '0'); | |
| this.ctx.fill(); | |
| } | |
| } | |
| drawFractal(width, height, colors) { | |
| // Simple recursive pattern | |
| const maxDepth = Math.floor(2 + (this.complexity * 0.5)); | |
| const color = colors[Math.floor(this.seededRandom() * colors.length)]; | |
| const drawBranch = (x, y, length, angle, depth) => { | |
| if (depth > maxDepth) return; | |
| const endX = x + Math.cos(angle) * length; | |
| const endY = y + Math.sin(angle) * length; | |
| this.ctx.beginPath(); | |
| this.ctx.moveTo(x, y); | |
| this.ctx.lineTo(endX, endY); | |
| const opacity = 0.7 - (depth / maxDepth * 0.5); | |
| this.ctx.strokeStyle = color + Math.floor(opacity * 255).toString(16).padStart(2, '0'); | |
| this.ctx.lineWidth = (maxDepth - depth) + 1; | |
| this.ctx.stroke(); | |
| const branchAngle = 0.3 + (this.seededRandom() * 0.4); | |
| // Recursive branches | |
| drawBranch(endX, endY, length * 0.67, angle + branchAngle, depth + 1); | |
| drawBranch(endX, endY, length * 0.67, angle - branchAngle, depth + 1); | |
| }; | |
| // Start the fractal | |
| drawBranch(width * 0.5, height * 0.7, height * 0.15, -Math.PI / 2, 0); | |
| } | |
| addNoise(width, height, intensity) { | |
| const imageData = this.ctx.getImageData(0, 0, width, height); | |
| const data = imageData.data; | |
| for (let i = 0; i < data.length; i += 4) { | |
| const noise = this.seededRandom() * intensity; | |
| data[i] = Math.min(255, Math.max(0, data[i] * (1 + noise))); | |
| data[i + 1] = Math.min(255, Math.max(0, data[i + 1] * (1 + noise))); | |
| data[i + 2] = Math.min(255, Math.max(0, data[i + 2] * (1 + noise))); | |
| } | |
| this.ctx.putImageData(imageData, 0, 0); | |
| } | |
| // Download the generated image | |
| downloadImage() { | |
| if (!this.hasImage) return; | |
| // Create a temporary link | |
| const link = document.createElement('a'); | |
| link.download = 'neural-art-' + Math.floor(Math.random() * 10000) + '.png'; | |
| link.href = this.canvas.toDataURL('image/png'); | |
| link.click(); | |
| } | |
| } | |
| // AI Music Generator | |
| class MusicGenerator { | |
| constructor() { | |
| // Check if Web Audio API is supported | |
| this.isAudioSupported = typeof window.AudioContext !== 'undefined' || | |
| typeof window.webkitAudioContext !== 'undefined'; | |
| if (this.isAudioSupported) { | |
| this.audioContext = new(window.AudioContext || window.webkitAudioContext)(); | |
| this.visualizer = document.getElementById('audio-visualizer'); | |
| this.isPlaying = false; | |
| this.sequence = []; | |
| this.currentNote = 0; | |
| this.analyser = null; | |
| this.dataArray = null; | |
| this.rafId = null; | |
| // Initialize UI | |
| this.initializeControls(); | |
| } else { | |
| // Show unsupported message | |
| document.getElementById('audio-visualizer').innerHTML = | |
| '<div class="text-secondary-400 text-sm">Web Audio API is not supported in this browser</div>'; | |
| document.getElementById('generate-music').disabled = true; | |
| document.getElementById('play-music').disabled = true; | |
| } | |
| } | |
| initializeControls() { | |
| // Generate button | |
| document.getElementById('generate-music').addEventListener('click', () => { | |
| this.generateMusic(); | |
| }); | |
| // Play button | |
| document.getElementById('play-music').addEventListener('click', () => { | |
| if (this.isPlaying) { | |
| this.stopMusic(); | |
| } else { | |
| this.playMusic(); | |
| } | |
| }); | |
| // Initialize visualizer | |
| this.setupVisualizer(); | |
| } | |
| // Setup audio analyzer for visualization | |
| setupVisualizer() { | |
| this.analyser = this.audioContext.createAnalyser(); | |
| this.analyser.fftSize = 256; | |
| const bufferLength = this.analyser.frequencyBinCount; | |
| this.dataArray = new Uint8Array(bufferLength); | |
| // Create visualization bars | |
| this.visualizer.innerHTML = ''; | |
| for (let i = 0; i < 32; i++) { | |
| const bar = document.createElement('div'); | |
| bar.className = 'audio-bar h-0'; | |
| this.visualizer.appendChild(bar); | |
| } | |
| } | |
| // Generate a melody based on current settings | |
| generateMusic() { | |
| // Get settings | |
| const scale = document.getElementById('music-scale').value; | |
| const complexity = parseInt(document.getElementById('music-complexity').value); | |
| // Define scale patterns | |
| const scales = { | |
| major: [0, 2, 4, 5, 7, 9, 11, 12], | |
| minor: [0, 2, 3, 5, 7, 8, 10, 12], | |
| pentatonic: [0, 2, 4, 7, 9, 12], | |
| blues: [0, 3, 5, 6, 7, 10, 12] | |
| }; | |
| const selectedScale = scales[scale] || scales.pentatonic; | |
| const baseNote = 60; // Middle C | |
| const sequenceLength = 16 + (complexity * 4); // More complexity = longer sequence | |
| // Create melody sequence | |
| this.sequence = []; | |
| let prevNote = null; | |
| for (let i = 0; i < sequenceLength; i++) { | |
| // Choose a note from the scale | |
| const octaveShift = Math.floor(this.seededRandom() * 3) - 1; // -1, 0, or 1 octave shift | |
| const scaleIndex = Math.floor(this.seededRandom() * selectedScale.length); | |
| // More complex patterns have higher probability of rests and jumps | |
| const isRest = this.seededRandom() < (0.1 + (complexity * 0.02)); | |
| if (isRest) { | |
| // Rest note (no sound) | |
| this.sequence.push(null); | |
| } else { | |
| // Calculate note frequency | |
| const note = baseNote + selectedScale[scaleIndex] + (octaveShift * 12); | |
| const frequency = 440 * Math.pow(2, (note - 69) / 12); // Convert MIDI note to frequency | |
| // Duration depends on complexity | |
| const duration = 0.1 + (this.seededRandom() * 0.2); | |
| // Smoother progression for less complex melodies | |
| if (prevNote && complexity < 5) { | |
| // Avoid large jumps for smoother melodies | |
| const maxJump = 7 + (complexity * 2); | |
| if (Math.abs(note - prevNote) > maxJump) { | |
| // Choose a closer note | |
| const direction = note > prevNote ? 1 : -1; | |
| const newNote = prevNote + (direction * (this.seededRandom() * maxJump)); | |
| this.sequence.push({ | |
| frequency: 440 * Math.pow(2, (newNote - 69) / 12), | |
| duration | |
| }); | |
| prevNote = newNote; | |
| continue; | |
| } | |
| } | |
| this.sequence.push({ | |
| frequency, | |
| duration | |
| }); | |
| prevNote = note; | |
| } | |
| } | |
| // Enable play button | |
| document.getElementById('play-music').disabled = false; | |
| this.visualizeSequence(); | |
| } | |
| // Play the generated melody | |
| playMusic() { | |
| if (!this.sequence.length) return; | |
| if (this.isPlaying) this.stopMusic(); | |
| this.isPlaying = true; | |
| this.currentNote = 0; | |
| // Update play button to stop icon | |
| const playBtn = document.getElementById('play-music'); | |
| playBtn.innerHTML = ` | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 9v6m4-6v6m7-3a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
| </svg> | |
| `; | |
| // Get tempo and instrument | |
| const tempo = parseInt(document.getElementById('music-tempo').value); | |
| const instrument = document.getElementById('music-instrument').value; | |
| // Calculate time between notes | |
| const noteInterval = 60000 / tempo; // Convert BPM to milliseconds | |
| // Start playback | |
| this.startVisualization(); | |
| this.playNextNote(instrument, noteInterval); | |
| } | |
| // Play each note in sequence | |
| playNextNote(instrument, interval) { | |
| if (!this.isPlaying || this.currentNote >= this.sequence.length) { | |
| this.stopMusic(); | |
| return; | |
| } | |
| const note = this.sequence[this.currentNote]; | |
| if (note) { | |
| // Create oscillator | |
| const oscillator = this.audioContext.createOscillator(); | |
| const gainNode = this.audioContext.createGain(); | |
| // Set waveform type | |
| oscillator.type = instrument; | |
| oscillator.frequency.value = note.frequency; | |
| // Set envelope | |
| gainNode.gain.value = 0; | |
| gainNode.gain.setValueAtTime(0, this.audioContext.currentTime); | |
| gainNode.gain.linearRampToValueAtTime(0.5, this.audioContext.currentTime + 0.01); | |
| gainNode.gain.exponentialRampToValueAtTime(0.001, this.audioContext.currentTime + note.duration); | |
| // Connect nodes | |
| oscillator.connect(gainNode); | |
| gainNode.connect(this.analyser); | |
| this.analyser.connect(this.audioContext.destination); | |
| // Start and stop oscillator | |
| oscillator.start(); | |
| oscillator.stop(this.audioContext.currentTime + note.duration); | |
| } | |
| // Move to next note | |
| this.currentNote++; | |
| // Schedule next note | |
| setTimeout(() => { | |
| this.playNextNote(instrument, interval); | |
| }, interval); | |
| } | |
| // Stop playback | |
| stopMusic() { | |
| this.isPlaying = false; | |
| this.currentNote = 0; | |
| // Update play button back to play icon | |
| const playBtn = document.getElementById('play-music'); | |
| playBtn.innerHTML = ` | |
| <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor"> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z" /> | |
| <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 12a9 9 0 11-18 0 9 9 0 0118 0z" /> | |
| </svg> | |
| `; | |
| // Stop visualization | |
| this.stopVisualization(); | |
| } | |
| // Start audio visualization | |
| startVisualization() { | |
| // Cancel any existing animation | |
| if (this.rafId) { | |
| cancelAnimationFrame(this.rafId); | |
| } | |
| const bars = document.querySelectorAll('.audio-bar'); | |
| const visualize = () => { | |
| this.analyser.getByteFrequencyData(this.dataArray); | |
| for (let i = 0; i < bars.length; i++) { | |
| // Map analyzer data to bar heights | |
| const index = Math.floor(i * this.dataArray.length / bars.length); | |
| const value = this.dataArray[index] / 255; | |
| // Set bar height | |
| const height = value * 80; // Max height in pixels | |
| bars[i].style.height = height + 'px'; | |
| } | |
| this.rafId = requestAnimationFrame(visualize); | |
| }; | |
| visualize(); | |
| } | |
| // Stop visualization | |
| stopVisualization() { | |
| if (this.rafId) { | |
| cancelAnimationFrame(this.rafId); | |
| this.rafId = null; | |
| } | |
| // Reset bars | |
| const bars = document.querySelectorAll('.audio-bar'); | |
| for (let i = 0; i < bars.length; i++) { | |
| bars[i].style.height = '0px'; | |
| } | |
| } | |
| // Visualize the sequence pattern without playing | |
| visualizeSequence() { | |
| if (!this.sequence.length) return; | |
| // Reset visualizer | |
| this.visualizer.innerHTML = ''; | |
| // Create sequencer grid | |
| const grid = document.createElement('div'); | |
| grid.className = 'flex items-center justify-center h-full w-full'; | |
| // Create visual representation of sequence | |
| const sequenceContainer = document.createElement('div'); | |
| sequenceContainer.className = 'flex flex-wrap justify-center gap-1 p-2'; | |
| // Show a subset of the sequence for clarity | |
| const displayCount = Math.min(this.sequence.length, 32); | |
| for (let i = 0; i < displayCount; i++) { | |
| const note = this.sequence[i]; | |
| const noteEl = document.createElement('div'); | |
| if (note === null) { | |
| // Rest | |
| noteEl.className = 'w-4 h-4 rounded-full bg-gray-700'; | |
| } else { | |
| // Map frequency to color | |
| const hue = ((note.frequency - 100) / 1000 * 360) % 360; | |
| // Map frequency to height | |
| const height = Math.min(40, Math.max(4, ((note.frequency - 100) / 1000) * 40)); | |
| noteEl.className = 'w-4 rounded-sm'; | |
| noteEl.style.height = `${height}px`; | |
| noteEl.style.backgroundColor = `hsl(${hue}, 80%, 60%)`; | |
| } | |
| sequenceContainer.appendChild(noteEl); | |
| } | |
| grid.appendChild(sequenceContainer); | |
| this.visualizer.appendChild(grid); | |
| } | |
| // Seeded random for reproducible results | |
| seededRandom() { | |
| const x = Math.sin(this.seed++) * 10000; | |
| return x - Math.floor(x); | |
| } | |
| // Set random seed | |
| setSeed(seed) { | |
| this.seed = seed; | |
| } | |
| } | |
| // Initialize model | |
| const llm = new SimpleLLM(); | |
| // Initialize speech controllers | |
| let speechSynth, speechRecognition, imageGenerator, musicGenerator; | |
| // Neural network visualization | |
| const svgElement = document.getElementById('neural-network'); | |
| // Initialize neural network visualization | |
| function initializeNeuralNetwork() { | |
| const width = svgElement.clientWidth; | |
| const height = svgElement.clientHeight; | |
| const nodeRadius = 4; | |
| const layerSpacing = width / 6; | |
| // Define layers (input, hidden layers, output) | |
| const layers = [{ | |
| name: "Input", | |
| nodes: 6, | |
| x: layerSpacing, | |
| color: "#5D5CDE" | |
| }, | |
| { | |
| name: "Hidden", | |
| nodes: 8, | |
| x: layerSpacing * 3, | |
| color: "#32e2df" | |
| }, | |
| { | |
| name: "Output", | |
| nodes: 6, | |
| x: layerSpacing * 5, | |
| color: "#ff6b6b" | |
| } | |
| ]; | |
| // Create gradient definitions | |
| const defs = document.createElementNS("http://www.w3.org/2000/svg", "defs"); | |
| svgElement.innerHTML = ''; | |
| svgElement.appendChild(defs); | |
| // Create glowing filter | |
| const filter = document.createElementNS("http://www.w3.org/2000/svg", "filter"); | |
| filter.setAttribute("id", "glow"); | |
| filter.setAttribute("x", "-50%"); | |
| filter.setAttribute("y", "-50%"); | |
| filter.setAttribute("width", "200%"); | |
| filter.setAttribute("height", "200%"); | |
| const feGaussianBlur = document.createElementNS("http://www.w3.org/2000/svg", "feGaussianBlur"); | |
| feGaussianBlur.setAttribute("stdDeviation", "2"); | |
| feGaussianBlur.setAttribute("result", "blur"); | |
| filter.appendChild(feGaussianBlur); | |
| const feComposite = document.createElementNS("http://www.w3.org/2000/svg", "feComposite"); | |
| feComposite.setAttribute("in", "SourceGraphic"); | |
| feComposite.setAttribute("in2", "blur"); | |
| feComposite.setAttribute("operator", "over"); | |
| filter.appendChild(feComposite); | |
| defs.appendChild(filter); | |
| // Create connection gradient | |
| const connGradient = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient"); | |
| connGradient.setAttribute("id", "connGradient"); | |
| connGradient.setAttribute("gradientUnits", "userSpaceOnUse"); | |
| const stop1 = document.createElementNS("http://www.w3.org/2000/svg", "stop"); | |
| stop1.setAttribute("offset", "0%"); | |
| stop1.setAttribute("stop-color", "rgba(93, 92, 222, 0.7)"); | |
| connGradient.appendChild(stop1); | |
| const stop2 = document.createElementNS("http://www.w3.org/2000/svg", "stop"); | |
| stop2.setAttribute("offset", "100%"); | |
| stop2.setAttribute("stop-color", "rgba(50, 226, 223, 0.7)"); | |
| connGradient.appendChild(stop2); | |
| defs.appendChild(connGradient); | |
| // Create radial gradients for nodes | |
| layers.forEach((layer, i) => { | |
| const gradient = document.createElementNS("http://www.w3.org/2000/svg", "radialGradient"); | |
| gradient.setAttribute("id", `nodeGradient${i}`); | |
| const stop1 = document.createElementNS("http://www.w3.org/2000/svg", "stop"); | |
| stop1.setAttribute("offset", "0%"); | |
| stop1.setAttribute("stop-color", layer.color); | |
| const stop2 = document.createElementNS("http://www.w3.org/2000/svg", "stop"); | |
| stop2.setAttribute("offset", "80%"); | |
| stop2.setAttribute("stop-color", layer.color); | |
| const stop3 = document.createElementNS("http://www.w3.org/2000/svg", "stop"); | |
| stop3.setAttribute("offset", "100%"); | |
| stop3.setAttribute("stop-color", `${layer.color}80`); | |
| gradient.appendChild(stop1); | |
| gradient.appendChild(stop2); | |
| gradient.appendChild(stop3); | |
| defs.appendChild(gradient); | |
| }); | |
| // Add a subtle background grid | |
| const gridSize = 15; | |
| const gridGroup = document.createElementNS("http://www.w3.org/2000/svg", "g"); | |
| gridGroup.classList.add("grid"); | |
| // Horizontal grid lines | |
| for (let y = 0; y < height; y += gridSize) { | |
| const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); | |
| line.setAttribute("x1", "0"); | |
| line.setAttribute("y1", y); | |
| line.setAttribute("x2", width); | |
| line.setAttribute("y2", y); | |
| line.setAttribute("stroke", "rgba(93, 92, 222, 0.05)"); | |
| line.setAttribute("stroke-width", "1"); | |
| gridGroup.appendChild(line); | |
| } | |
| // Vertical grid lines | |
| for (let x = 0; x < width; x += gridSize) { | |
| const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); | |
| line.setAttribute("x1", x); | |
| line.setAttribute("y1", "0"); | |
| line.setAttribute("x2", x); | |
| line.setAttribute("y2", height); | |
| line.setAttribute("stroke", "rgba(93, 92, 222, 0.05)"); | |
| line.setAttribute("stroke-width", "1"); | |
| gridGroup.appendChild(line); | |
| } | |
| svgElement.appendChild(gridGroup); | |
| // Draw connections first (behind nodes) | |
| const connGroup = document.createElementNS("http://www.w3.org/2000/svg", "g"); | |
| connGroup.classList.add("connections"); | |
| for (let i = 0; i < layers.length - 1; i++) { | |
| const currentLayer = layers[i]; | |
| const nextLayer = layers[i + 1]; | |
| const currentNodeSpacing = height / (currentLayer.nodes + 1); | |
| const nextNodeSpacing = height / (nextLayer.nodes + 1); | |
| for (let j = 0; j < currentLayer.nodes; j++) { | |
| const startX = currentLayer.x; | |
| const startY = currentNodeSpacing * (j + 1); | |
| for (let k = 0; k < nextLayer.nodes; k++) { | |
| const endX = nextLayer.x; | |
| const endY = nextNodeSpacing * (k + 1); | |
| // Update conn gradient with correct positions | |
| const connId = `conn-${i}-${j}-${k}`; | |
| const connGradient = document.createElementNS("http://www.w3.org/2000/svg", "linearGradient"); | |
| connGradient.setAttribute("id", connId); | |
| connGradient.setAttribute("gradientUnits", "userSpaceOnUse"); | |
| connGradient.setAttribute("x1", startX); | |
| connGradient.setAttribute("y1", startY); | |
| connGradient.setAttribute("x2", endX); | |
| connGradient.setAttribute("y2", endY); | |
| const stop1 = document.createElementNS("http://www.w3.org/2000/svg", "stop"); | |
| stop1.setAttribute("offset", "0%"); | |
| stop1.setAttribute("stop-color", currentLayer.color + "50"); | |
| connGradient.appendChild(stop1); | |
| const stop2 = document.createElementNS("http://www.w3.org/2000/svg", "stop"); | |
| stop2.setAttribute("offset", "100%"); | |
| stop2.setAttribute("stop-color", nextLayer.color + "50"); | |
| connGradient.appendChild(stop2); | |
| defs.appendChild(connGradient); | |
| // Draw connection with custom gradient | |
| const line = document.createElementNS("http://www.w3.org/2000/svg", "line"); | |
| line.setAttribute("x1", startX); | |
| line.setAttribute("y1", startY); | |
| line.setAttribute("x2", endX); | |
| line.setAttribute("y2", endY); | |
| line.setAttribute("stroke", `url(#${connId})`); | |
| line.setAttribute("stroke-width", "1"); | |
| line.setAttribute("data-path", `M${startX},${startY} L${endX},${endY}`); | |
| line.classList.add("connection"); | |
| // Add connection to the group | |
| connGroup.appendChild(line); | |
| } | |
| } | |
| } | |
| svgElement.appendChild(connGroup); | |
| // Create a group for the data packets | |
| const packetGroup = document.createElementNS("http://www.w3.org/2000/svg", "g"); | |
| packetGroup.classList.add("packets"); | |
| svgElement.appendChild(packetGroup); | |
| // Create node groups for layers | |
| for (let i = 0; i < layers.length; i++) { | |
| const layer = layers[i]; | |
| const nodeSpacing = height / (layer.nodes + 1); | |
| // Create a group for each layer | |
| const layerGroup = document.createElementNS("http://www.w3.org/2000/svg", "g"); | |
| layerGroup.classList.add(`layer-${layer.name.toLowerCase()}-group`); | |
| // Add subtle glow behind the layer | |
| const layerGlow = document.createElementNS("http://www.w3.org/2000/svg", "ellipse"); | |
| layerGlow.setAttribute("cx", layer.x); | |
| layerGlow.setAttribute("cy", height / 2); | |
| layerGlow.setAttribute("rx", 15); | |
| layerGlow.setAttribute("ry", height / 2.5); | |
| layerGlow.setAttribute("fill", `${layer.color}15`); | |
| layerGlow.setAttribute("filter", "blur(15px)"); | |
| svgElement.appendChild(layerGlow); | |
| for (let j = 0; j < layer.nodes; j++) { | |
| const nodeY = nodeSpacing * (j + 1); | |
| // Create glow effect (optional) | |
| const glow = document.createElementNS("http://www.w3.org/2000/svg", "circle"); | |
| glow.setAttribute("cx", layer.x); | |
| glow.setAttribute("cy", nodeY); | |
| glow.setAttribute("r", nodeRadius * 2.5); | |
| glow.setAttribute("fill", `${layer.color}20`); | |
| glow.setAttribute("filter", "blur(3px)"); | |
| layerGroup.appendChild(glow); | |
| // Create node | |
| const circle = document.createElementNS("http://www.w3.org/2000/svg", "circle"); | |
| circle.setAttribute("cx", layer.x); | |
| circle.setAttribute("cy", nodeY); | |
| circle.setAttribute("r", nodeRadius); | |
| circle.setAttribute("fill", `url(#nodeGradient${i})`); | |
| circle.setAttribute("stroke", layer.color); | |
| circle.setAttribute("stroke-width", "0.5"); | |
| circle.classList.add("node"); | |
| circle.classList.add(`layer-${layer.name.toLowerCase()}`); | |
| // Add a small rotation effect to some nodes | |
| if (j % 2 === 0) { | |
| const animation = document.createElementNS("http://www.w3.org/2000/svg", "animateTransform"); | |
| animation.setAttribute("attributeName", "transform"); | |
| animation.setAttribute("type", "rotate"); | |
| animation.setAttribute("from", `0 ${layer.x} ${nodeY}`); | |
| animation.setAttribute("to", `360 ${layer.x} ${nodeY}`); | |
| animation.setAttribute("dur", `${20 + j * 5}s`); | |
| animation.setAttribute("repeatCount", "indefinite"); | |
| circle.appendChild(animation); | |
| } | |
| layerGroup.appendChild(circle); | |
| } | |
| // Add layer label | |
| const text = document.createElementNS("http://www.w3.org/2000/svg", "text"); | |
| text.setAttribute("x", layer.x); | |
| text.setAttribute("y", height - 5); | |
| text.setAttribute("text-anchor", "middle"); | |
| text.setAttribute("font-size", "10px"); | |
| text.setAttribute("fill", layer.color); | |
| text.setAttribute("filter", "drop-shadow(0 1px 1px rgba(0,0,0,0.3))"); | |
| text.textContent = layer.name; | |
| layerGroup.appendChild(text); | |
| svgElement.appendChild(layerGroup); | |
| } | |
| // Add idle animation to a few random nodes | |
| const nodes = svgElement.querySelectorAll('.node'); | |
| const randomCount = Math.min(5, nodes.length); | |
| const randomIndices = []; | |
| while (randomIndices.length < randomCount) { | |
| const idx = Math.floor(Math.random() * nodes.length); | |
| if (!randomIndices.includes(idx)) { | |
| randomIndices.push(idx); | |
| // Apply a subtle pulse animation to random nodes for ambient effect | |
| setTimeout(() => { | |
| nodes[idx].classList.add("pulse-animation"); | |
| setTimeout(() => { | |
| nodes[idx].classList.remove("pulse-animation"); | |
| }, 3000 + Math.random() * 1000); | |
| }, Math.random() * 5000); | |
| } | |
| } | |
| } | |
| // Create data packet elements for the animation | |
| function createDataPacket(path, color) { | |
| const packetGroup = svgElement.querySelector('.packets'); | |
| const packetSize = 4; | |
| // Create packet element | |
| const packet = document.createElementNS("http://www.w3.org/2000/svg", "circle"); | |
| packet.setAttribute("r", packetSize); | |
| packet.setAttribute("fill", color); | |
| packet.setAttribute("filter", "drop-shadow(0 0 3px " + color + ")"); | |
| packet.style.setProperty('--path', path); | |
| packet.classList.add('data-packet'); | |
| packetGroup.appendChild(packet); | |
| // Remove the packet after animation completes | |
| setTimeout(() => { | |
| packet.remove(); | |
| }, 1500); | |
| return packet; | |
| } | |
| // Animate data flowing through the network | |
| function animateNetwork(steps = []) { | |
| const connections = svgElement.querySelectorAll('.connection'); | |
| const nodes = svgElement.querySelectorAll('.node'); | |
| const packetGroup = svgElement.querySelector('.packets'); | |
| // Colors for active elements | |
| const inputColor = "#7F7FFE"; | |
| const hiddenColor = "#32e2df"; | |
| const outputColor = "#ff6b6b"; | |
| const selectedColor = "#4CAF50"; | |
| // Clear existing packets | |
| packetGroup.innerHTML = ''; | |
| // Reset all connections and nodes | |
| connections.forEach(conn => { | |
| conn.setAttribute("stroke-width", "1"); | |
| conn.classList.remove("flow-animation"); | |
| // Restore original gradient (using the id attribute that contains the gradient info) | |
| if (conn.id && conn.id.startsWith("conn-")) { | |
| conn.setAttribute("stroke", `url(#${conn.id})`); | |
| } else { | |
| conn.setAttribute("stroke", "rgba(93, 92, 222, 0.2)"); | |
| } | |
| }); | |
| nodes.forEach(node => { | |
| if (node.classList.contains('layer-input')) { | |
| node.setAttribute("fill", "url(#nodeGradient0)"); | |
| } else if (node.classList.contains('layer-hidden')) { | |
| node.setAttribute("fill", "url(#nodeGradient1)"); | |
| } else if (node.classList.contains('layer-output')) { | |
| node.setAttribute("fill", "url(#nodeGradient2)"); | |
| } | |
| node.setAttribute("r", "4"); | |
| node.classList.remove("pulse-animation", "pulse-animation-accent", "pulse-animation-secondary"); | |
| }); | |
| // Animation timing | |
| let delay = 0; | |
| const stepDuration = 800; | |
| // If we have steps from the model, visualize those specifically | |
| if (steps.length > 0) { | |
| // Update model state display | |
| const modelStateEl = document.getElementById('model-state'); | |
| modelStateEl.innerHTML = ''; | |
| // Show the generation steps with jQuery for smoother animations | |
| steps.forEach((step, index) => { | |
| setTimeout(() => { | |
| // Helper function to activate a node with specific color/animation | |
| const activateNode = (node, color, size, animClass) => { | |
| $(node).animate({ | |
| r: size | |
| }, 300); | |
| node.setAttribute("fill", color); | |
| node.setAttribute("filter", "drop-shadow(0 0 5px " + color + "80)"); | |
| if (animClass) node.classList.add(animClass); | |
| // Create a pulsing ring around active node | |
| const glow = document.createElementNS("http://www.w3.org/2000/svg", "circle"); | |
| glow.setAttribute("cx", node.getAttribute("cx")); | |
| glow.setAttribute("cy", node.getAttribute("cy")); | |
| glow.setAttribute("r", size * 1.5); | |
| glow.setAttribute("fill", "none"); | |
| glow.setAttribute("stroke", color); | |
| glow.setAttribute("stroke-width", "1"); | |
| glow.setAttribute("opacity", "0.5"); | |
| glow.style.animation = "pulse 1.5s infinite ease-out"; | |
| node.parentNode.appendChild(glow); | |
| setTimeout(() => { | |
| // Remove glow and animations | |
| glow.remove(); | |
| $(node).animate({ | |
| r: 4 | |
| }, 300); | |
| node.removeAttribute("filter"); | |
| if (node.classList.contains('layer-input')) { | |
| node.setAttribute("fill", "url(#nodeGradient0)"); | |
| } else if (node.classList.contains('layer-hidden')) { | |
| node.setAttribute("fill", "url(#nodeGradient1)"); | |
| } else if (node.classList.contains('layer-output')) { | |
| node.setAttribute("fill", "url(#nodeGradient2)"); | |
| } | |
| if (animClass) node.classList.remove(animClass); | |
| }, stepDuration - 200); | |
| }; | |
| // Activate input nodes (representing current context) | |
| const inputNodes = document.querySelectorAll('.layer-input'); | |
| const contextNodes = Array.from(inputNodes).slice(0, 2); | |
| contextNodes.forEach((node, i) => { | |
| activateNode(node, inputColor, 7, "pulse-animation"); | |
| }); | |
| // Get random connections from input to hidden, and add data packets | |
| setTimeout(() => { | |
| contextNodes.forEach(inputNode => { | |
| const hiddenNodes = document.querySelectorAll('.layer-hidden'); | |
| // Choose 2-3 random hidden nodes to connect to from each input node | |
| const targetCount = 2 + Math.floor(Math.random() * 2); | |
| const targetIndices = []; | |
| while (targetIndices.length < targetCount) { | |
| const idx = Math.floor(Math.random() * hiddenNodes.length); | |
| if (!targetIndices.includes(idx)) { | |
| targetIndices.push(idx); | |
| } | |
| } | |
| // Create data flows to these targets | |
| targetIndices.forEach(idx => { | |
| const target = hiddenNodes[idx]; | |
| const startX = inputNode.getAttribute("cx"); | |
| const startY = inputNode.getAttribute("cy"); | |
| const endX = target.getAttribute("cx"); | |
| const endY = target.getAttribute("cy"); | |
| // Define path for animation | |
| const path = `M${startX},${startY} L${endX},${endY}`; | |
| // Create active connection with gradient | |
| const conn = document.createElementNS("http://www.w3.org/2000/svg", "line"); | |
| conn.setAttribute("x1", startX); | |
| conn.setAttribute("y1", startY); | |
| conn.setAttribute("x2", endX); | |
| conn.setAttribute("y2", endY); | |
| conn.setAttribute("stroke", inputColor); | |
| conn.setAttribute("stroke-width", "2"); | |
| conn.setAttribute("opacity", "0.6"); | |
| conn.setAttribute("stroke-dasharray", "5,3"); | |
| conn.classList.add("flow-animation"); | |
| // Add to SVG temporarily | |
| svgElement.appendChild(conn); | |
| // Create data packet flowing along this path | |
| createDataPacket(path, inputColor); | |
| // Remove connection after delay | |
| setTimeout(() => { | |
| conn.remove(); | |
| }, 600); | |
| // Activate target node after data arrives | |
| setTimeout(() => { | |
| activateNode(target, hiddenColor, 7, "pulse-animation-accent"); | |
| }, 300); | |
| }); | |
| }); | |
| }, 200); | |
| // Connect hidden to output nodes and flow more data | |
| setTimeout(() => { | |
| const hiddenNodes = document.querySelectorAll('.layer-hidden'); | |
| const activatedHiddenIndices = Array.from({ | |
| length: hiddenNodes.length | |
| }, (_, i) => i) | |
| .sort(() => Math.random() - 0.5) | |
| .slice(0, 3); | |
| const activeHiddenNodes = activatedHiddenIndices.map(i => hiddenNodes[i]); | |
| // Get output nodes | |
| const outputNodes = document.querySelectorAll('.layer-output'); | |
| const numOptions = Math.min(step.options.length, outputNodes.length); | |
| const outputTargets = Array.from(outputNodes).slice(0, numOptions); | |
| // Create connections from hidden to outputs | |
| activeHiddenNodes.forEach(hiddenNode => { | |
| outputTargets.forEach((outputNode, outputIdx) => { | |
| const startX = hiddenNode.getAttribute("cx"); | |
| const startY = hiddenNode.getAttribute("cy"); | |
| const endX = outputNode.getAttribute("cx"); | |
| const endY = outputNode.getAttribute("cy"); | |
| // Define path for animation | |
| const path = `M${startX},${startY} L${endX},${endY}`; | |
| // Create active connection with gradient | |
| const conn = document.createElementNS("http://www.w3.org/2000/svg", "line"); | |
| conn.setAttribute("x1", startX); | |
| conn.setAttribute("y1", startY); | |
| conn.setAttribute("x2", endX); | |
| conn.setAttribute("y2", endY); | |
| conn.setAttribute("stroke", hiddenColor); | |
| conn.setAttribute("stroke-width", "2"); | |
| conn.setAttribute("opacity", "0.6"); | |
| conn.setAttribute("stroke-dasharray", "5,3"); | |
| conn.classList.add("flow-animation"); | |
| // Add to SVG temporarily | |
| svgElement.appendChild(conn); | |
| // Create data packet flowing along this path | |
| createDataPacket(path, hiddenColor); | |
| // Remove connection after delay | |
| setTimeout(() => { | |
| conn.remove(); | |
| }, 600); | |
| }); | |
| }); | |
| // Activate output nodes with staggered timing | |
| outputTargets.forEach((node, i) => { | |
| setTimeout(() => { | |
| // First node is the selected output (green) | |
| if (i === 0) { | |
| activateNode(node, selectedColor, 8, null); | |
| // Add extra effect for selected node | |
| const selectedGlow = document.createElementNS("http://www.w3.org/2000/svg", "circle"); | |
| selectedGlow.setAttribute("cx", node.getAttribute("cx")); | |
| selectedGlow.setAttribute("cy", node.getAttribute("cy")); | |
| selectedGlow.setAttribute("r", 15); | |
| selectedGlow.setAttribute("fill", `${selectedColor}20`); | |
| selectedGlow.setAttribute("filter", "blur(8px)"); | |
| node.parentNode.appendChild(selectedGlow); | |
| // Animate the glow | |
| $(selectedGlow).animate({ | |
| r: 30, | |
| opacity: 0 | |
| }, 1000, function() { | |
| selectedGlow.remove(); | |
| }); | |
| } else { | |
| activateNode(node, outputColor, 6, "pulse-animation-secondary"); | |
| } | |
| }, 400 + i * 100); | |
| }); | |
| }, 600); | |
| // Update model state display with the current step | |
| const stepEl = document.createElement('div'); | |
| stepEl.className = "mb-1 pb-1 border-b dark:border-gray-700 fade-in"; | |
| let contextDescription; | |
| let contextClass; | |
| switch (step.contextType) { | |
| case 'trigram': | |
| contextDescription = "Using 2-word context"; | |
| contextClass = "text-primary-400"; | |
| break; | |
| case 'bigram': | |
| contextDescription = "Using 1-word context"; | |
| contextClass = "text-accent-400"; | |
| break; | |
| case 'random': | |
| contextDescription = "Using random selection"; | |
| contextClass = "text-secondary-400"; | |
| break; | |
| default: | |
| contextDescription = "Context unknown"; | |
| contextClass = "text-gray-400"; | |
| } | |
| stepEl.innerHTML = ` | |
| <div class="text-xs"><strong class="${contextClass}">${contextDescription}:</strong> | |
| "${step.context || 'start'}" → | |
| <span class="text-green-500 font-bold">"${step.selected}"</span> | |
| </div> | |
| <div class="text-xs text-gray-500">Options: ${step.options.slice(0, 3).join(", ")}${step.options.length > 3 ? '...' : ''}</div> | |
| `; | |
| modelStateEl.appendChild(stepEl); | |
| // Scroll to bottom of state display | |
| modelStateEl.scrollTop = modelStateEl.scrollHeight; | |
| }, delay); | |
| delay += stepDuration; | |
| }); | |
| return; | |
| } | |
| // Default animation if no steps provided - using jQuery for smoother animations | |
| const activateLayer = (layerSelector, nextLayerSelector, connectionCount, color) => { | |
| setTimeout(() => { | |
| // Activate nodes in this layer | |
| const layerNodes = document.querySelectorAll(layerSelector); | |
| $(layerNodes).each(function() { | |
| const node = this; | |
| $(node).animate({ | |
| r: 7 | |
| }, 300); | |
| node.setAttribute("fill", color); | |
| node.setAttribute("filter", "drop-shadow(0 0 5px " + color + "80)"); | |
| // Create a pulsing ring around active node | |
| const glow = document.createElementNS("http://www.w3.org/2000/svg", "circle"); | |
| glow.setAttribute("cx", node.getAttribute("cx")); | |
| glow.setAttribute("cy", node.getAttribute("cy")); | |
| glow.setAttribute("r", 10); | |
| glow.setAttribute("fill", "none"); | |
| glow.setAttribute("stroke", color); | |
| glow.setAttribute("stroke-width", "1"); | |
| glow.setAttribute("opacity", "0.5"); | |
| glow.style.animation = "pulse 1.5s infinite ease-out"; | |
| node.parentNode.appendChild(glow); | |
| setTimeout(() => { | |
| glow.remove(); | |
| $(node).animate({ | |
| r: 4 | |
| }, 300); | |
| node.removeAttribute("filter"); | |
| if (node.classList.contains('layer-input')) { | |
| node.setAttribute("fill", "url(#nodeGradient0)"); | |
| } else if (node.classList.contains('layer-hidden')) { | |
| node.setAttribute("fill", "url(#nodeGradient1)"); | |
| } else if (node.classList.contains('layer-output')) { | |
| node.setAttribute("fill", "url(#nodeGradient2)"); | |
| } | |
| }, 800); | |
| }); | |
| // Activate connections to next layer | |
| if (nextLayerSelector) { | |
| const nextLayerNodes = document.querySelectorAll(nextLayerSelector); | |
| // Create flowing data between nodes | |
| layerNodes.forEach(sourceNode => { | |
| // Choose random target nodes | |
| const targetCount = 1 + Math.floor(Math.random() * 2); | |
| const targetIndices = []; | |
| while (targetIndices.length < targetCount && targetIndices.length < nextLayerNodes.length) { | |
| const idx = Math.floor(Math.random() * nextLayerNodes.length); | |
| if (!targetIndices.includes(idx)) { | |
| targetIndices.push(idx); | |
| } | |
| } | |
| // Create data flows to these targets | |
| targetIndices.forEach(idx => { | |
| const targetNode = nextLayerNodes[idx]; | |
| const startX = sourceNode.getAttribute("cx"); | |
| const startY = sourceNode.getAttribute("cy"); | |
| const endX = targetNode.getAttribute("cx"); | |
| const endY = targetNode.getAttribute("cy"); | |
| // Define path for animation | |
| const path = `M${startX},${startY} L${endX},${endY}`; | |
| // Create active connection with color | |
| const conn = document.createElementNS("http://www.w3.org/2000/svg", "line"); | |
| conn.setAttribute("x1", startX); | |
| conn.setAttribute("y1", startY); | |
| conn.setAttribute("x2", endX); | |
| conn.setAttribute("y2", endY); | |
| conn.setAttribute("stroke", color); | |
| conn.setAttribute("stroke-width", "2"); | |
| conn.setAttribute("opacity", "0.6"); | |
| conn.setAttribute("stroke-dasharray", "5,3"); | |
| conn.classList.add("flow-animation"); | |
| // Add to SVG temporarily | |
| svgElement.appendChild(conn); | |
| // Create data packet flowing along this path | |
| createDataPacket(path, color); | |
| // Remove connection after delay | |
| setTimeout(() => { | |
| conn.remove(); | |
| }, 800); | |
| }); | |
| }); | |
| } | |
| }, delay); | |
| delay += 600; | |
| }; | |
| // Update model state with sample data | |
| const modelStateEl = document.getElementById('model-state'); | |
| modelStateEl.innerHTML = ''; | |
| const modelState = llm.getState(); | |
| Object.entries(modelState).forEach(([ngram, nextWords]) => { | |
| const entryEl = document.createElement('div'); | |
| entryEl.className = "mb-1 fade-in text-xs"; | |
| entryEl.innerHTML = `<strong class="text-primary-400">"${ngram}"</strong> → | |
| <span class="text-accent-400">${nextWords.join('</span>, <span class="text-accent-400">')}</span>`; | |
| modelStateEl.appendChild(entryEl); | |
| }); | |
| // Animate through the layers with different colors | |
| activateLayer('.layer-input', '.layer-hidden', 8, inputColor); | |
| activateLayer('.layer-hidden', '.layer-output', 8, hiddenColor); | |
| activateLayer('.layer-output', null, 0, outputColor); | |
| } | |
| // Tokenize input text | |
| function visualizeTokenization(text) { | |
| const inputViz = document.getElementById('input-viz'); | |
| // Clear previous content | |
| inputViz.innerHTML = ''; | |
| // Tokenize the text | |
| const tokens = llm.tokenize(text); | |
| // Create token visualization | |
| const tokenContainer = document.createElement('div'); | |
| tokenContainer.className = 'flex flex-wrap gap-1 p-1'; | |
| // Animate token appearance with jQuery | |
| tokens.forEach((token, index) => { | |
| setTimeout(() => { | |
| const tokenEl = document.createElement('div'); | |
| tokenEl.className = 'px-2 py-1 rounded bg-primary text-white text-xs opacity-0'; | |
| tokenEl.textContent = token; | |
| tokenContainer.appendChild(tokenEl); | |
| // Fade in with jQuery | |
| $(tokenEl).animate({ | |
| opacity: 1 | |
| }, 300); | |
| // Pulse effect | |
| setTimeout(() => { | |
| $(tokenEl).addClass('pulse-animation'); | |
| setTimeout(() => { | |
| $(tokenEl).removeClass('pulse-animation'); | |
| }, 800); | |
| }, 300); | |
| }, index * 100); | |
| }); | |
| inputViz.appendChild(tokenContainer); | |
| return tokens; | |
| } | |
| // Generate and display output | |
| function generateAndDisplayOutput(prompt) { | |
| const outputViz = document.getElementById('output-viz'); | |
| // Clear previous content | |
| outputViz.innerHTML = ''; | |
| // Generate response from model | |
| const response = llm.generate(prompt); | |
| // Create response element | |
| const responseEl = document.createElement('div'); | |
| responseEl.className = 'p-1'; | |
| // Create output container | |
| const outputContainer = document.createElement('div'); | |
| outputContainer.className = 'text-gray-800 dark:text-gray-200'; | |
| // Add typing indicator | |
| const typingIndicator = document.createElement('span'); | |
| typingIndicator.className = 'inline-block w-2 h-4 bg-primary animate-pulse ml-1'; | |
| outputContainer.appendChild(typingIndicator); | |
| responseEl.appendChild(outputContainer); | |
| outputViz.appendChild(responseEl); | |
| // Animate the generation | |
| const words = response.generated.split(' '); | |
| let wordIndex = 0; | |
| let outputText = ''; | |
| const wordInterval = setInterval(() => { | |
| if (wordIndex < words.length) { | |
| // Remove typing indicator | |
| if (wordIndex === 0) { | |
| typingIndicator.remove(); | |
| } | |
| // Add the word | |
| const wordSpan = document.createElement('span'); | |
| wordSpan.textContent = (wordIndex > 0 ? ' ' : '') + words[wordIndex]; | |
| outputContainer.appendChild(wordSpan); | |
| // Update output text for speech | |
| outputText += (wordIndex > 0 ? ' ' : '') + words[wordIndex]; | |
| // Animate the word briefly with jQuery | |
| $(wordSpan).css('color', '#5D5CDE').animate({ | |
| color: document.documentElement.classList.contains('dark') ? '#e2e8f0' : '#1a202c' | |
| }, 500); | |
| wordIndex++; | |
| // Scroll to bottom | |
| outputViz.scrollTop = outputViz.scrollHeight; | |
| } else { | |
| clearInterval(wordInterval); | |
| // Add the final typing indicator at the end | |
| const finalIndicator = document.createElement('span'); | |
| finalIndicator.className = 'inline-block w-2 h-4 bg-primary animate-pulse ml-1'; | |
| outputContainer.appendChild(finalIndicator); | |
| // Remove the final indicator after a delay | |
| setTimeout(() => { | |
| finalIndicator.remove(); | |
| // Speak the output if voice is enabled | |
| if (speechSynth && speechSynth.isVoiceEnabled) { | |
| speechSynth.speak(outputText); | |
| } | |
| }, 1000); | |
| } | |
| }, 200); | |
| // Return the generation steps for network animation | |
| return response.steps; | |
| } | |
| // Process the prompt | |
| function processPrompt() { | |
| const promptInput = document.getElementById('prompt-input'); | |
| const text = promptInput.value.trim(); | |
| if (text === '') { | |
| alert('Please enter a prompt'); | |
| return; | |
| } | |
| // Stop any current speech synthesis | |
| if (speechSynth) { | |
| speechSynth.stop(); | |
| } | |
| // Stop any active speech recognition | |
| if (speechRecognition && speechRecognition.isListening) { | |
| speechRecognition.stop(); | |
| } | |
| // Visualize tokenization | |
| visualizeTokenization(text); | |
| // Animate neural network (generic animation first) | |
| animateNetwork(); | |
| // Generate output after a delay | |
| setTimeout(() => { | |
| const steps = generateAndDisplayOutput(text); | |
| // Animate network with specific steps | |
| setTimeout(() => { | |
| animateNetwork(steps); | |
| }, 600); | |
| }, 1000); | |
| } | |
| // Process web content | |
| async function processWebContent() { | |
| const urlInput = document.getElementById('url-input'); | |
| const url = urlInput.value.trim(); | |
| const webPreview = document.getElementById('web-preview'); | |
| if (!url || !url.startsWith('http')) { | |
| alert('Please enter a valid URL'); | |
| return; | |
| } | |
| // Show loading state | |
| webPreview.classList.remove('hidden'); | |
| webPreview.innerHTML = '<p class="text-center py-4"><span class="inline-block w-4 h-4 border-2 border-primary border-t-transparent rounded-full animate-spin mr-2"></span> Fetching content...</p>'; | |
| try { | |
| // Attempt to fetch the URL's content | |
| const response = await fetch(url); | |
| const text = await response.text(); | |
| // Extract text content from HTML | |
| const parser = new DOMParser(); | |
| const doc = parser.parseFromString(text, 'text/html'); | |
| // Remove script and style elements | |
| const scripts = doc.querySelectorAll('script, style, svg, iframe, noscript'); | |
| scripts.forEach(el => el.remove()); | |
| // Get meaningful text from the document | |
| const bodyText = doc.body.textContent || ""; | |
| const cleanText = bodyText | |
| .replace(/\s+/g, ' ') | |
| .replace(/\n+/g, ' ') | |
| .trim(); | |
| // Display a preview | |
| const textPreview = cleanText.slice(0, 300) + (cleanText.length > 300 ? '...' : ''); | |
| webPreview.innerHTML = ` | |
| <div class="text-sm"> | |
| <p class="font-medium mb-2">Content Preview:</p> | |
| <p class="text-gray-700 dark:text-gray-300">${textPreview}</p> | |
| </div> | |
| <div class="mt-3 flex justify-between"> | |
| <span class="text-xs text-gray-500">${cleanText.length} characters</span> | |
| <button id="train-web-btn" class="px-3 py-1 bg-primary hover:bg-opacity-90 text-white rounded-lg text-xs transition-all shadow-md"> | |
| Train Model on Content | |
| </button> | |
| </div> | |
| `; | |
| // Store the full text for training | |
| webPreview.dataset.fullText = cleanText; | |
| // Add event listener to train button | |
| document.getElementById('train-web-btn').addEventListener('click', () => { | |
| trainOnWebContent(url, cleanText); | |
| }); | |
| } catch (error) { | |
| console.error("Error fetching URL:", error); | |
| webPreview.innerHTML = ` | |
| <div class="bg-red-100 dark:bg-red-900 border-l-4 border-red-500 text-red-700 dark:text-red-300 p-3"> | |
| <p>Error fetching content. This could be due to CORS restrictions or the domain not being allowed by the CSP.</p> | |
| <p class="text-xs mt-1">Try a different URL from an allowed domain.</p> | |
| </div> | |
| `; | |
| } | |
| } | |
| // Train on web content | |
| function trainOnWebContent(url, text) { | |
| if (!text || text.length < 10) { | |
| alert('Not enough content to train on'); | |
| return; | |
| } | |
| // Extract domain for source name | |
| let domain; | |
| try { | |
| domain = new URL(url).hostname; | |
| } catch (e) { | |
| domain = url; | |
| } | |
| // Train the model | |
| llm.train(text, { | |
| type: 'web', | |
| name: `Web: ${domain}` | |
| }); | |
| // Show success message | |
| const webPreview = document.getElementById('web-preview'); | |
| webPreview.innerHTML += ` | |
| <div class="mt-2 bg-green-100 dark:bg-green-900 border-l-4 border-green-500 text-green-700 dark:text-green-300 p-2 text-sm"> | |
| Model trained successfully on content from ${domain}! | |
| </div> | |
| `; | |
| // Animate the neural network | |
| animateNetwork(); | |
| } | |
| // Handle text file upload | |
| function handleFileUpload(e) { | |
| const file = e.target.files[0]; | |
| if (!file) { | |
| return; | |
| } | |
| // Check if it's a text file | |
| if (!file.type.match('text.*') && !file.name.match(/\.(txt|md|text)$/i)) { | |
| alert('Please select a text file (.txt, .md, etc.)'); | |
| return; | |
| } | |
| // Read the file | |
| const reader = new FileReader(); | |
| reader.onload = function(event) { | |
| const text = event.target.result; | |
| // Train the model on file content | |
| trainOnFileContent(file.name, text); | |
| }; | |
| reader.onerror = function() { | |
| alert('Error reading file. Please try another file.'); | |
| }; | |
| reader.readAsText(file); | |
| } | |
| // Train on file content | |
| function trainOnFileContent(fileName, text) { | |
| if (!text || text.length < 10) { | |
| alert('Not enough content to train on'); | |
| return; | |
| } | |
| // Train the model | |
| llm.train(text, { | |
| type: 'file', | |
| name: `File: ${fileName}` | |
| }); | |
| // Show success message | |
| const feedback = $('<div>') | |
| .text(`Model trained successfully on file: ${fileName}!`) | |
| .addClass('text-green-600 dark:text-green-400 text-sm mt-2 p-2 bg-green-100 dark:bg-green-900 rounded') | |
| .hide() | |
| .appendTo('#panel-data') | |
| .slideDown(); | |
| // Fade out and remove the feedback | |
| setTimeout(() => { | |
| feedback.slideUp(function() { | |
| $(this).remove(); | |
| }); | |
| }, 2000); | |
| // Animate the neural network | |
| animateNetwork(); | |
| // Switch to the sources tab to show the new data source | |
| setTimeout(() => { | |
| switchTab('sources'); | |
| }, 1000); | |
| } | |
| // Add training data to the model | |
| function addTrainingData() { | |
| const trainingInput = document.getElementById('training-input'); | |
| const text = trainingInput.value.trim(); | |
| if (text === '') { | |
| alert('Please enter some text to add to the model'); | |
| return; | |
| } | |
| // Train the model on the new data | |
| llm.train(text, { | |
| type: 'training', | |
| name: 'Manual Training Entry' | |
| }); | |
| // Clear the input | |
| trainingInput.value = ''; | |
| // Show feedback with jQuery animation | |
| const feedback = $('<div>') | |
| .text('Training data added successfully!') | |
| .addClass('text-green-600 dark:text-green-400 text-sm mt-2 p-2 bg-green-100 dark:bg-green-900 rounded') | |
| .hide() | |
| .appendTo('#panel-data') | |
| .slideDown(); | |
| // Fade out and remove the feedback | |
| setTimeout(() => { | |
| feedback.slideUp(function() { | |
| $(this).remove(); | |
| }); | |
| }, 2000); | |
| // Animate the neural network | |
| animateNetwork(); | |
| } | |
| // Reset the model | |
| function resetModel() { | |
| if (confirm('Are you sure you want to reset the model? All training data will be lost.')) { | |
| llm.reset(); | |
| // Show feedback with jQuery | |
| const feedback = $('<div>') | |
| .text('Model has been reset to default state.') | |
| .addClass('text-blue-600 dark:text-blue-400 text-sm mt-2 p-2 bg-blue-100 dark:bg-blue-900 rounded') | |
| .hide() | |
| .appendTo('#panel-data') | |
| .slideDown(); | |
| // Fade out and remove the feedback | |
| setTimeout(() => { | |
| feedback.slideUp(function() { | |
| $(this).remove(); | |
| }); | |
| }, 2000); | |
| // Update visualization | |
| animateNetwork(); | |
| } | |
| } | |
| // Export model data | |
| function exportModelData() { | |
| llm.exportModel(); | |
| } | |
| // Import model data | |
| function importModelData(e) { | |
| const file = e.target.files[0]; | |
| if (!file) return; | |
| // Must be JSON file | |
| if (file.type !== 'application/json' && !file.name.endsWith('.json')) { | |
| alert('Please select a JSON file.'); | |
| return; | |
| } | |
| const reader = new FileReader(); | |
| reader.onload = function(event) { | |
| const jsonData = event.target.result; | |
| const success = llm.importModel(jsonData); | |
| if (success) { | |
| // Show success message | |
| const feedback = $('<div>') | |
| .text('Model imported successfully!') | |
| .addClass('text-green-600 dark:text-green-400 text-sm p-2 bg-green-100 dark:bg-green-900 rounded') | |
| .css({ | |
| position: 'fixed', | |
| bottom: '20px', | |
| left: '50%', | |
| transform: 'translateX(-50%)', | |
| zIndex: '1000', | |
| padding: '10px 20px', | |
| boxShadow: '0 4px 6px rgba(0,0,0,0.1)', | |
| borderRadius: '8px' | |
| }) | |
| .hide() | |
| .appendTo('body') | |
| .fadeIn(); | |
| // Fade out and remove the feedback | |
| setTimeout(() => { | |
| feedback.fadeOut(function() { | |
| $(this).remove(); | |
| }); | |
| }, 3000); | |
| // Update visualization and switch to sources tab | |
| animateNetwork(); | |
| switchTab('sources'); | |
| } else { | |
| alert('Failed to import model. The file may be corrupted or in the wrong format.'); | |
| } | |
| // Clear the file input for future imports | |
| document.getElementById('import-file').value = ''; | |
| }; | |
| reader.readAsText(file); | |
| } | |
| // Initialize help system | |
| function initializeHelpSystem() { | |
| // Toggle help overlay | |
| document.getElementById('help-toggle').addEventListener('click', () => { | |
| document.getElementById('help-overlay').classList.remove('hidden'); | |
| }); | |
| document.getElementById('help-close').addEventListener('click', () => { | |
| document.getElementById('help-overlay').classList.add('hidden'); | |
| }); | |
| // Help navigation | |
| document.querySelectorAll('.help-step').forEach(step => { | |
| step.addEventListener('click', () => { | |
| switchHelpSection(step.dataset.step); | |
| }); | |
| }); | |
| // Next/prev navigation | |
| document.getElementById('next-help').addEventListener('click', () => { | |
| const currentIndex = helpSections.indexOf(currentHelpSection); | |
| const nextIndex = (currentIndex + 1) % helpSections.length; | |
| switchHelpSection(helpSections[nextIndex]); | |
| }); | |
| document.getElementById('prev-help').addEventListener('click', () => { | |
| const currentIndex = helpSections.indexOf(currentHelpSection); | |
| const prevIndex = (currentIndex - 1 + helpSections.length) % helpSections.length; | |
| switchHelpSection(helpSections[prevIndex]); | |
| }); | |
| // Keyboard shortcuts for help | |
| document.addEventListener('keydown', e => { | |
| // ESC to close help | |
| if (e.key === 'Escape') { | |
| document.getElementById('help-overlay').classList.add('hidden'); | |
| } | |
| // ? to open help | |
| if (e.key === '?' && !document.getElementById('help-overlay').classList.contains('hidden')) { | |
| document.getElementById('help-overlay').classList.add('hidden'); | |
| } else if (e.key === '?') { | |
| document.getElementById('help-overlay').classList.remove('hidden'); | |
| } | |
| }); | |
| } | |
| // Initialize the visualization | |
| window.addEventListener('load', () => { | |
| // Initialize 2D neural network visualization | |
| initializeNeuralNetwork(); | |
| animateNetwork(); | |
| // Initialize speech synthesis controller | |
| if (window.speechSynthesis) { | |
| speechSynth = new SpeechController(); | |
| } | |
| // Initialize speech recognition controller | |
| speechRecognition = new SpeechRecognitionController(); | |
| // Initialize image generator | |
| imageGenerator = new ImageGenerator(); | |
| // Initialize music generator | |
| musicGenerator = new MusicGenerator(); | |
| // Initialize help system | |
| initializeHelpSystem(); | |
| // Handle window resize | |
| window.addEventListener('resize', () => { | |
| initializeNeuralNetwork(); | |
| }); | |
| // Tab switching listeners | |
| document.getElementById('tab-prompt').addEventListener('click', () => switchTab('prompt')); | |
| document.getElementById('tab-create').addEventListener('click', () => switchTab('create')); | |
| document.getElementById('tab-web').addEventListener('click', () => switchTab('web')); | |
| document.getElementById('tab-data').addEventListener('click', () => switchTab('data')); | |
| document.getElementById('tab-sources').addEventListener('click', () => switchTab('sources')); | |
| // Creation type switching listeners | |
| document.getElementById('create-type-image').addEventListener('click', () => switchCreationType('image')); | |
| document.getElementById('create-type-music').addEventListener('click', () => switchCreationType('music')); | |
| // Submit button click | |
| document.getElementById('submit-btn').addEventListener('click', processPrompt); | |
| // Enter key press | |
| document.getElementById('prompt-input').addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| processPrompt(); | |
| } | |
| }); | |
| // Web URL fetch button | |
| document.getElementById('fetch-btn').addEventListener('click', processWebContent); | |
| // URL input enter key | |
| document.getElementById('url-input').addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') { | |
| processWebContent(); | |
| } | |
| }); | |
| // File upload handler | |
| document.getElementById('file-upload').addEventListener('change', handleFileUpload); | |
| // Training button click | |
| document.getElementById('train-btn').addEventListener('click', addTrainingData); | |
| // Reset button click | |
| document.getElementById('reset-btn').addEventListener('click', resetModel); | |
| // Export model button | |
| document.getElementById('export-btn').addEventListener('click', exportModelData); | |
| // Import model file handler | |
| document.getElementById('import-file').addEventListener('change', importModelData); | |
| // Set seed for music generator with same value as image generator | |
| musicGenerator.seed = imageGenerator.seed; | |
| // Image seed change should update music seed too | |
| document.getElementById('image-seed').addEventListener('change', (e) => { | |
| musicGenerator.seed = parseInt(e.target.value); | |
| }); | |
| // Add keyboard shortcut for microphone | |
| document.addEventListener('keydown', (e) => { | |
| // Ctrl+M to toggle microphone | |
| if (e.ctrlKey && e.key === 'm') { | |
| if (speechRecognition) { | |
| if (speechRecognition.isListening) { | |
| speechRecognition.stop(); | |
| } else { | |
| speechRecognition.start(); | |
| } | |
| e.preventDefault(); | |
| } | |
| } | |
| // Space to play/pause music when in music tab | |
| if (e.code === 'Space' && !document.getElementById('panel-create').classList.contains('hidden') && | |
| !document.getElementById('creation-music').classList.contains('hidden') && | |
| !document.activeElement.tagName.match(/INPUT|TEXTAREA/)) { | |
| if (musicGenerator.isPlaying) { | |
| musicGenerator.stopMusic(); | |
| } else { | |
| musicGenerator.playMusic(); | |
| } | |
| e.preventDefault(); | |
| } | |
| // Ctrl+S to save image/music | |
| if (e.ctrlKey && e.key === 's' && !document.getElementById('panel-create').classList.contains('hidden')) { | |
| if (!document.getElementById('creation-image').classList.contains('hidden')) { | |
| if (imageGenerator.hasImage) { | |
| imageGenerator.downloadImage(); | |
| e.preventDefault(); | |
| } | |
| } | |
| } | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment