Spaces:
Running
Running
| <html lang="ru"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>🤗 AI Hub (Без ключей)</title> | |
| <style> | |
| :root { --bg: #0b0f1a; --card: #131828; --border: #1e2640; --accent: #7c3aed; --accent2: #06b6d4; --accent3: #f59e0b; --text: #e2e8f0; --muted: #64748b; --radius: 14px; } | |
| * { box-sizing: border-box; margin: 0; padding: 0; } | |
| body { font-family: 'Segoe UI', system-ui, sans-serif; background: var(--bg); color: var(--text); min-height: 100vh; padding-bottom: 4rem; } | |
| .header { background: linear-gradient(135deg, #1a1040 0%, #0b0f1a 50%, #0a1920 100%); padding: 2.5rem 1.5rem 2rem; text-align: center; border-bottom: 1px solid var(--border); } | |
| .header h1 { font-size: 2rem; background: linear-gradient(135deg, var(--accent), var(--accent2), var(--accent3)); -webkit-background-clip: text; -webkit-text-fill-color: transparent; margin-bottom: .3rem; } | |
| .header p { color: var(--muted); font-size: .9rem; max-width: 600px; margin: 0 auto; } | |
| .tabs { display: flex; justify-content: center; gap: .5rem; padding: 1.5rem 1rem; flex-wrap: wrap; } | |
| .tab-btn { padding: .7rem 1.8rem; border-radius: 50px; border: 1px solid var(--border); background: transparent; color: var(--muted); font-size: .95rem; font-weight: 600; cursor: pointer; transition: all .25s; } | |
| .tab-btn:hover { color: var(--text); border-color: var(--muted); } | |
| .tab-btn.active { background: var(--accent); border-color: var(--accent); color: #fff; box-shadow: 0 0 20px rgba(124,58,237,.35); } | |
| .tab-btn.active.img-tab { background: var(--accent2); border-color: var(--accent2); box-shadow: 0 0 20px rgba(6,182,212,.35); } | |
| .tab-btn.active.aud-tab { background: var(--accent3); border-color: var(--accent3); box-shadow: 0 0 20px rgba(245,158,11,.35); } | |
| .panel { display: none; max-width: 800px; margin: 0 auto; padding: 0 1.5rem; } | |
| .panel.active { display: block; } | |
| .panel-card { background: var(--card); border: 1px solid var(--border); border-radius: var(--radius); padding: 2rem; margin-bottom: 1.5rem; } | |
| .panel-card h2 { font-size: 1.3rem; margin-bottom: .3rem; } | |
| .panel-card .desc { color: var(--muted); font-size: .85rem; margin-bottom: 1.2rem; } | |
| textarea, .input-field { width: 100%; padding: 1rem; border-radius: var(--radius); border: 1px solid var(--border); background: #0f1322; color: var(--text); font-size: .95rem; resize: vertical; outline: none; transition: border .2s; } | |
| textarea:focus, .input-field:focus { border-color: var(--accent); } | |
| .options-row { display: flex; gap: .8rem; margin-top: 1rem; flex-wrap: wrap; align-items: center; } | |
| .options-row label { font-size: .85rem; color: var(--muted); } | |
| .options-row select { padding: .5rem .8rem; border-radius: 8px; border: 1px solid var(--border); background: #0f1322; color: var(--text); font-size: .85rem; outline: none; } | |
| .generate-btn { margin-top: 1rem; padding: .85rem 2.5rem; border-radius: var(--radius); border: none; font-size: 1rem; font-weight: 700; cursor: pointer; color: #fff; transition: transform .15s, box-shadow .25s; } | |
| .generate-btn:hover { transform: translateY(-2px); } | |
| .btn-text { background: linear-gradient(135deg, #7c3aed, #a855f7); } | |
| .btn-img { background: linear-gradient(135deg, #0891b2, #06b6d4); } | |
| .btn-aud { background: linear-gradient(135deg, #d97706, #f59e0b); } | |
| .output-box { margin-top: 1.5rem; padding: 1.5rem; border-radius: var(--radius); background: #0f1322; border: 1px solid var(--border); min-height: 80px; white-space: pre-wrap; line-height: 1.7; font-size: .95rem; position: relative; } | |
| .output-box img { max-width: 100%; border-radius: 10px; margin-top: .5rem; } | |
| .output-placeholder { color: var(--muted); font-style: italic; } | |
| .copy-btn { position: absolute; top: .8rem; right: .8rem; padding: .4rem .8rem; border-radius: 8px; border: 1px solid var(--border); background: var(--card); color: var(--muted); font-size: .75rem; cursor: pointer; } | |
| .copy-btn:hover { color: var(--text); } | |
| .spinner { display: inline-block; width: 18px; height: 18px; border: 3px solid rgba(255,255,255,.2); border-top-color: #fff; border-radius: 50%; animation: spin .6s linear infinite; vertical-align: middle; margin-right: .5rem; } | |
| @keyframes spin { to { transform: rotate(360deg); } } | |
| .status { margin-top: .8rem; font-size: .85rem; color: var(--muted); } | |
| .status.error { color: #ef4444; } | |
| .status.ok { color: #22c55e; } | |
| .note { background: rgba(245,158,11,.1); border: 1px solid var(--accent3); color: var(--accent3); padding: .8rem; border-radius: 8px; font-size: .8rem; margin-top: 1rem; } | |
| @media (max-width: 600px) { .header h1 { font-size: 1.6rem; } .panel-card { padding: 1.3rem; } } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="header"> | |
| <h1>🤗 AI Hub</h1> | |
| <p>Текст, изображения и аудио без API-ключей. Работает через бесплатные публичные сервисы и встроенные возможности браузера.</p> | |
| </div> | |
| <div class="tabs"> | |
| <button class="tab-btn active" onclick="switchTab('text', this)">✍️ Текст</button> | |
| <button class="tab-btn img-tab" onclick="switchTab('img', this)">🎨 Изображение</button> | |
| <button class="tab-btn aud-tab" onclick="switchTab('aud', this)">🎵 Аудио</button> | |
| </div> | |
| <!-- TEXT --> | |
| <div class="panel active" id="panel-text"> | |
| <div class="panel-card"> | |
| <h2>✍️ Генерация текста</h2> | |
| <p class="desc">Использует Pollinations.ai (бесплатно, без регистрации)</p> | |
| <textarea id="text-prompt" placeholder="Введите запрос… Например: Напиши короткое стихотворение о космосе"></textarea> | |
| <div class="options-row"> | |
| <label>Модель:</label> | |
| <select id="text-model"> | |
| <option value="openai">OpenAI (похоже на GPT)</option> | |
| <option value="mistral">Mistral</option> | |
| <option value="llama">Llama 3</option> | |
| </select> | |
| </div> | |
| <button class="generate-btn btn-text" onclick="generateText()">⚡ Сгенерировать</button> | |
| <div class="status" id="text-status"></div> | |
| </div> | |
| <div class="output-box" id="text-output"> | |
| <span class="output-placeholder">Здесь появится текст…</span> | |
| <button class="copy-btn" onclick="copyText()">📋 Копировать</button> | |
| </div> | |
| </div> | |
| <!-- IMAGE --> | |
| <div class="panel" id="panel-img"> | |
| <div class="panel-card"> | |
| <h2>🎨 Генерация изображений</h2> | |
| <p class="desc">Pollinations Image Generator (бесплатно, без водяных знаков)</p> | |
| <textarea id="img-prompt" placeholder="Опишите картинку… Например: Cyberpunk city at sunset, neon lights, rain reflections, cinematic style" style="min-height:80px"></textarea> | |
| <div class="options-row"> | |
| <label>Размер:</label> | |
| <select id="img-size"> | |
| <option value="1024x1024">1024×1024</option> | |
| <option value="1280x720">1280×720 (широкий)</option> | |
| <option value="720x1280">720×1280 (вертикальный)</option> | |
| </select> | |
| <label>Стиль:</label> | |
| <select id="img-style"> | |
| <option value="">Авто</option> | |
| <option value="photorealistic">Фотореализм</option> | |
| <option value="anime">Аниме</option> | |
| <option value="digital art">Цифровой арт</option> | |
| <option value="oil painting">Масло</option> | |
| </select> | |
| </div> | |
| <button class="generate-btn btn-img" onclick="generateImage()">🖼️ Создать</button> | |
| <div class="status" id="img-status"></div> | |
| </div> | |
| <div class="output-box" id="img-output"> | |
| <span class="output-placeholder">Здесь появится изображение…</span> | |
| </div> | |
| </div> | |
| <!-- AUDIO --> | |
| <div class="panel" id="panel-aud"> | |
| <div class="panel-card"> | |
| <h2>🎵 Аудио (Без облака)</h2> | |
| <p class="desc">Синтез речи через браузер + процедурная музыка через Web Audio API</p> | |
| <select class="input-field" id="aud-mode" onchange="toggleAudMode()" style="margin-bottom:1rem"> | |
| <option value="tts">🗣️ Озвучка текста (TTS)</option> | |
| <option value="music">🎹 Генерация музыки</option> | |
| </select> | |
| <div id="aud-tts-section"> | |
| <textarea id="aud-tts-prompt" placeholder="Введите текст для озвучки…"></textarea> | |
| <div class="options-row"> | |
| <label>Язык:</label> | |
| <select id="tts-lang"> | |
| <option value="ru-RU">Русский</option> | |
| <option value="en-US">English</option> | |
| <option value="de-DE">Deutsch</option> | |
| <option value="fr-FR">Français</option> | |
| </select> | |
| <label>Скорость:</label> | |
| <select id="tts-rate"> | |
| <option value="0.8">Медленно</option> | |
| <option value="1" selected>Нормально</option> | |
| <option value="1.2">Быстро</option> | |
| </select> | |
| </div> | |
| <button class="generate-btn btn-aud" onclick="speakText()">🔊 Озвучить</button> | |
| </div> | |
| <div id="aud-music-section" style="display:none"> | |
| <textarea id="aud-music-prompt" placeholder="Настроение (влияет на гамму и темп): например: грустное, эпичное, танцевальное, расслабленное" style="min-height:80px"></textarea> | |
| <div class="options-row"> | |
| <label>Длительность:</label> | |
| <select id="music-duration"> | |
| <option value="5">5 сек</option> | |
| <option value="10" selected>10 сек</option> | |
| <option value="20">20 сек</option> | |
| </select> | |
| </div> | |
| <button class="generate-btn btn-aud" onclick="generateMusic()">🎼 Играть</button> | |
| <button class="generate-btn btn-aud" style="background:#ef4444" onclick="stopMusic()">⏹ Стоп</button> | |
| </div> | |
| <div class="note">⚠️ Генерация музыки работает локально через Web Audio API. Это не нейросеть, но создаёт уникальные мелодии алгоритмически.</div> | |
| <div class="status" id="aud-status"></div> | |
| </div> | |
| <div class="output-box" id="aud-output"> | |
| <span class="output-placeholder">Результат появится здесь…</span> | |
| </div> | |
| </div> | |
| <script> | |
| // ── Tabs ── | |
| function switchTab(name, btn) { | |
| document.querySelectorAll('.panel').forEach(p => p.classList.remove('active')); | |
| document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active')); | |
| document.getElementById('panel-' + name).classList.add('active'); | |
| btn.classList.add('active'); | |
| } | |
| function setStatus(id, msg, cls = '') { | |
| const el = document.getElementById(id); | |
| el.textContent = msg; | |
| el.className = 'status ' + cls; | |
| } | |
| function setLoading(btn, loading) { | |
| if (loading) { | |
| btn.disabled = true; | |
| btn.dataset.orig = btn.innerHTML; | |
| btn.innerHTML = '<span class="spinner"></span> Загрузка…'; | |
| } else { | |
| btn.disabled = false; | |
| btn.innerHTML = btn.dataset.orig; | |
| } | |
| } | |
| function copyText() { | |
| const text = document.getElementById('text-output').innerText.replace('📋 Копировать', '').trim(); | |
| navigator.clipboard.writeText(text); | |
| } | |
| // ═══════════════════ TEXT ═══════════════════ | |
| async function generateText() { | |
| const prompt = document.getElementById('text-prompt').value.trim(); | |
| if (!prompt) return alert('Введите запрос!'); | |
| const btn = event.target; | |
| const model = document.getElementById('text-model').value; | |
| setLoading(btn, true); | |
| setStatus('text-status', '⏳ Отправка запроса…'); | |
| try { | |
| const res = await fetch(`https://text.pollinations.ai/${encodeURIComponent(prompt)}?model=${model}&seed=${Math.floor(Math.random()*999999)}`); | |
| if (!res.ok) throw new Error('Ошибка сети'); | |
| const text = await res.text(); | |
| document.getElementById('text-output').innerHTML = text.replace(/\n/g, '<br>') + '<button class="copy-btn" onclick="copyText()">📋 Копировать</button>'; | |
| setStatus('text-status', '✅ Готово!', 'ok'); | |
| } catch (e) { | |
| setStatus('text-status', '❌ ' + e.message, 'error'); | |
| } finally { | |
| setLoading(btn, false); | |
| } | |
| } | |
| // ═══════════════════ IMAGE ═══════════════════ | |
| function generateImage() { | |
| const prompt = document.getElementById('img-prompt').value.trim(); | |
| if (!prompt) return alert('Введите описание!'); | |
| const btn = event.target; | |
| const size = document.getElementById('img-size').value; | |
| const style = document.getElementById('img-style').value; | |
| const [w, h] = size.split('x'); | |
| setLoading(btn, true); | |
| setStatus('img-status', '⏳ Генерация изображения…'); | |
| const fullPrompt = style ? `${prompt}, ${style}` : prompt; | |
| const url = `https://image.pollinations.ai/prompt/${encodeURIComponent(fullPrompt)}?width=${w}&height=${h}&seed=${Math.floor(Math.random()*999999)}&nologo=true`; | |
| const img = new Image(); | |
| img.onload = () => { | |
| document.getElementById('img-output').innerHTML = `<img src="${url}" alt="AI Image"><br><a href="${url}" download="ai_image.png" style="color:var(--accent2);display:inline-block;margin-top:.8rem">⬇️ Скачать PNG</a>`; | |
| setStatus('img-status', '✅ Готово!', 'ok'); | |
| setLoading(btn, false); | |
| }; | |
| img.onerror = () => { | |
| setStatus('img-status', '❌ Ошибка загрузки изображения', 'error'); | |
| setLoading(btn, false); | |
| }; | |
| img.src = url; | |
| } | |
| // ═══════════════════ AUDIO ═══════════════════ | |
| function toggleAudMode() { | |
| const mode = document.getElementById('aud-mode').value; | |
| document.getElementById('aud-tts-section').style.display = mode === 'tts' ? 'block' : 'none'; | |
| document.getElementById('aud-music-section').style.display = mode === 'music' ? 'block' : 'none'; | |
| } | |
| function speakText() { | |
| const text = document.getElementById('aud-tts-prompt').value.trim(); | |
| if (!text) return alert('Введите текст!'); | |
| const utterance = new SpeechSynthesisUtterance(text); | |
| utterance.lang = document.getElementById('tts-lang').value; | |
| utterance.rate = parseFloat(document.getElementById('tts-rate').value); | |
| window.speechSynthesis.speak(utterance); | |
| setStatus('aud-status', '🔊 Воспроизведение…', 'ok'); | |
| } | |
| let audioCtx, musicTimeout; | |
| function generateMusic() { | |
| const prompt = document.getElementById('aud-music-prompt').value.toLowerCase(); | |
| const duration = parseInt(document.getElementById('music-duration').value); | |
| audioCtx = new (window.AudioContext || window.webkitAudioContext)(); | |
| const masterGain = audioCtx.createGain(); | |
| masterGain.gain.value = 0.3; | |
| masterGain.connect(audioCtx.destination); | |
| // Анализ настроения | |
| let scale, tempo, waveType; | |
| if (/груст|sad|minor/i.test(prompt)) { scale = [0,2,3,5,7,8,10]; tempo = 0.4; waveType = 'sine'; } | |
| else if (/эпич|epic|hero/i.test(prompt)) { scale = [0,2,4,5,7,9,11]; tempo = 0.3; waveType = 'sawtooth'; } | |
| else if (/танц|dance|happy|upbeat/i.test(prompt)) { scale = [0,4,5,7,9]; tempo = 0.2; waveType = 'triangle'; } | |
| else { scale = [0,2,4,7,9]; tempo = 0.35; waveType = 'sine'; } | |
| const baseFreq = 130.81; // C3 | |
| let noteIndex = 0; | |
| let currentTime = 0; | |
| function playNote() { | |
| if (currentTime > duration) return; | |
| const note = scale[noteIndex % scale.length]; | |
| const osc = audioCtx.createOscillator(); | |
| const gain = audioCtx.createGain(); | |
| osc.type = waveType; | |
| osc.frequency.value = baseFreq * Math.pow(2, (note + Math.floor(noteIndex/7)*12)/12); | |
| gain.gain.setValueAtTime(0, audioCtx.currentTime); | |
| gain.gain.linearRampToValueAtTime(0.5, audioCtx.currentTime + 0.05); | |
| gain.gain.exponentialRampToValueAtTime(0.01, audioCtx.currentTime + tempo * 1.5); | |
| osc.connect(gain).connect(masterGain); | |
| osc.start(audioCtx.currentTime); | |
| osc.stop(audioCtx.currentTime + tempo * 2); | |
| noteIndex++; | |
| currentTime += tempo; | |
| musicTimeout = setTimeout(playNote, tempo * 1000); | |
| } | |
| playNote(); | |
| setStatus('aud-status', '🎼 Играет…', 'ok'); | |
| } | |
| function stopMusic() { | |
| if (audioCtx) audioCtx.close(); | |
| if (musicTimeout) clearTimeout(musicTimeout); | |
| setStatus('aud-status', '⏹ Остановлено'); | |
| } | |
| </script> | |
| </body> | |
| </html> |